/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2006 by 1&1 Internet AG, Germany, http://www.1and1.org License: LGPL 2.1: http://www.gnu.org/licenses/lgpl.html Authors: * Sebastian Werner (wpbasti) * Andreas Ecker (ecker) ************************************************************************ */ /* ************************************************************************ #module(ui_dragdrop) ************************************************************************ */ /** * This manager (singleton) manage all drag and drop handling of a qx.core.Init instance. * * @event dragdrop {qx.event.type.DragEvent} * @event dragout {qx.event.type.DragEvent} * @event dragover {qx.event.type.DragEvent} * @event dragmove {qx.event.type.DragEvent} * @event dragstart {qx.event.type.DragEvent} * @event dragend {qx.event.type.DragEvent} */ qx.OO.defineClass("qx.event.handler.DragAndDropHandler", qx.manager.object.ObjectManager, function() { qx.core.Target.call(this); this._data = {}; this._actions = {}; this._cursors = {}; var vCursor; for (var vAction in this._actionNames) { vCursor = this._cursors[vAction] = new qx.ui.basic.Image(this._cursorPath + vAction + "." + this._cursorFormat); vCursor.setZIndex(1e8); } }); qx.OO.addProperty({ name : "sourceWidget", type : "object" }); qx.OO.addProperty({ name : "destinationWidget", type : "object" }); qx.OO.addProperty({ name : "cursor", type : "object" }); qx.OO.addProperty({ name : "currentAction", type : "string" }); qx.Proto._actionNames = { move : "move", copy : "copy", alias : "alias", nodrop : "nodrop" } qx.Proto._cursorPath = "widget/cursors/"; qx.Proto._cursorFormat = "gif"; qx.Proto._lastDestinationEvent = null; /* --------------------------------------------------------------------------- COMMON MODIFIER --------------------------------------------------------------------------- */ qx.Proto._modifyDestinationWidget = function(propValue, propOldValue, propData) { if (propValue) { propValue.dispatchEvent(new qx.event.type.DragEvent("dragdrop", this._lastDestinationEvent, propValue, this.getSourceWidget())); this._lastDestinationEvent = null; } return true; } /* --------------------------------------------------------------------------- DATA HANDLING --------------------------------------------------------------------------- */ /*! Add data of mimetype. #param vMimeType[String]: A valid mimetype #param vData[Any]: Any value for the mimetype */ qx.Proto.addData = function(vMimeType, vData) { this._data[vMimeType] = vData; } qx.Proto.getData = function(vMimeType) { return this._data[vMimeType]; } qx.Proto.clearData = function() { this._data = {}; } /* --------------------------------------------------------------------------- MIME TYPE HANDLING --------------------------------------------------------------------------- */ qx.Proto.getDropDataTypes = function() { var vDestination = this.getDestinationWidget(); var vDropTypes = []; // If there is not any destination, simple return if (!vDestination) { return vDropTypes; } // Search for matching mimetypes var vDropDataTypes = vDestination.getDropDataTypes(); for (var i=0, l=vDropDataTypes.length; i 5 || Math.abs(e.getScreenY() - this._dragCache.startScreenY) > 5) { // Fire dragstart event to finally allow the above if to handle next events this._dragCache.sourceWidget.dispatchEvent(new qx.event.type.DragEvent("dragstart", e, this._dragCache.sourceWidget), true); // Update status flag this._dragCache.hasFiredDragStart = true; // Look if handler become active if (this._dragCache.dragHandlerActive) { // Fire first user events this._fireUserEvents(this._dragCache.currentDropWidget, this._dragCache.sourceWidget, e); // Update status flags this._dragCache.currentDropWidget = this._dragCache.sourceWidget; // Activate capture for clientDocument qx.ui.core.ClientDocument.getInstance().setCapture(true); } } } } /*! Handle mouse up event. Normally this finalize the drag and drop event. */ qx.Proto._handleMouseUp = function(e) { // Return if dragCache was not filled before if (!this._dragCache) { return; } if (this._dragCache.dragHandlerActive) { this._endDrag(this.getDropTarget(e), e); } else { // Clear drag cache this._dragCache = null; } } /* --------------------------------------------------------------------------- HANDLER FOR KEY EVENTS --------------------------------------------------------------------------- */ /*! This wraps the key events to custom handlers. */ qx.Proto.handleKeyEvent = function(e) { if (!this._dragCache) { return; } switch (e.getType()) { case "keydown": this._handleKeyDown(e); return; case "keyup": this._handleKeyUp(e); return; } } qx.Proto._handleKeyDown = function(e) { // Stop Drag on Escape if (e.getKeyIdentifier() == "Escape") { this.cancelDrag(e); } // Update cursor and action on press of modifier keys else if (this.getCurrentAction() != null) { // TODO this doesn't work in WebKit because WebKit doesn't fire keyevents for modifier keys switch(e.getKeyIdentifier()) { case "Shift": case "Control": case "Alt": this.setAction(this._evalNewAction(e.getShiftKey(), e.getCtrlKey(), e.getAltKey())); this._renderCursor(); e.preventDefault(); } } } qx.Proto._handleKeyUp = function(e) { // TODO this doesn't work in WebKit because WebKit doesn't fire keyevents for modifier keys var bShiftPressed = e.getKeyIdentifier() == "Shift"; var bCtrlPressed = e.getKeyIdentifier() == "Control"; var bAltPressed = e.getKeyIdentifier() == "Alt"; if (bShiftPressed || bCtrlPressed || bAltPressed) { if (this.getCurrentAction() != null) { this.setAction(this._evalNewAction(!bShiftPressed && e.getShiftKey(), ! bCtrlPressed && e.getCtrlKey(), !bAltPressed && e.getAltKey())); this._renderCursor(); e.preventDefault(); } } } /* --------------------------------------------------------------------------- IMPLEMENTATION OF DRAG&DROP SESSION FINALISATION --------------------------------------------------------------------------- */ /*! Cancel current drag and drop session */ qx.Proto.cancelDrag = function(e) { this._endDrag(null, e); } qx.Proto.globalCancelDrag = function() { if (this._dragCache && this._dragCache.dragHandlerActive) { this._endDragCore(); } } /*! This will be called to the end of each drag and drop session */ qx.Proto._endDrag = function(currentDestinationWidget, e) { // Use given destination widget if (currentDestinationWidget) { this._lastDestinationEvent = e; this.setDestinationWidget(currentDestinationWidget); } // Dispatch dragend event this.getSourceWidget().dispatchEvent(new qx.event.type.DragEvent("dragend", e, this.getSourceWidget(), currentDestinationWidget), true); // Fire dragout event this._fireUserEvents(this._dragCache && this._dragCache.currentDropWidget, null, e); // Call helper this._endDragCore(); } qx.Proto._endDragCore = function() { // Remove cursor var oldCursor = this.getCursor(); if (oldCursor) { oldCursor._style.display = "none"; this.forceCursor(null); } // Reset drag cache for next drag and drop session if (this._dragCache) { this._dragCache.currentDropWidget = null; this._dragCache = null; } // Deactivate capture for clientDocument qx.ui.core.ClientDocument.getInstance().setCapture(false); // Cleanup data and actions this.clearData(); this.clearActions(); // Cleanup widgets this.setSourceWidget(null); this.setDestinationWidget(null); } /* --------------------------------------------------------------------------- IMPLEMENTATION OF CURSOR UPDATES --------------------------------------------------------------------------- */ /*! Select and setup the current used cursor */ qx.Proto._renderCursor = function() { var vNewCursor; var vOldCursor = this.getCursor(); switch(this.getCurrentAction()) { case this._actionNames.move: vNewCursor = this._cursors.move; break; case this._actionNames.copy: vNewCursor = this._cursors.copy; break; case this._actionNames.alias: vNewCursor = this._cursors.alias; break; default: vNewCursor = this._cursors.nodrop; } // Hide old cursor if (vNewCursor != vOldCursor && vOldCursor != null) { vOldCursor._style.display = "none"; } // Ensure that the cursor is created if (!vNewCursor._initialLayoutDone) { qx.ui.core.ClientDocument.getInstance().add(vNewCursor); qx.ui.core.Widget.flushGlobalQueues(); } // Apply position with runtime style (fastest qooxdoo method) vNewCursor._applyRuntimeLeft(this._dragCache.pageX + 5); vNewCursor._applyRuntimeTop(this._dragCache.pageY + 15); // Finally show new cursor if (vNewCursor != vOldCursor) { vNewCursor._style.display = ""; } // Store new cursor this.forceCursor(vNewCursor); } /* --------------------------------------------------------------------------- IMPLEMENTATION OF DROP TARGET VALIDATION --------------------------------------------------------------------------- */ qx.Proto.supportsDrop = function(vWidget) { var vTypes = vWidget.getDropDataTypes(); if (!vTypes) { return false; } for (var i=0; i