diff options
Diffstat (limited to 'js/dojo/dojox/drawing/manager/Stencil.js')
| -rw-r--r-- | js/dojo/dojox/drawing/manager/Stencil.js | 645 |
1 files changed, 645 insertions, 0 deletions
diff --git a/js/dojo/dojox/drawing/manager/Stencil.js b/js/dojo/dojox/drawing/manager/Stencil.js new file mode 100644 index 0000000..17e7a82 --- /dev/null +++ b/js/dojo/dojox/drawing/manager/Stencil.js @@ -0,0 +1,645 @@ +//>>built +// wrapped by build app +define("dojox/drawing/manager/Stencil", ["dijit","dojo","dojox"], function(dijit,dojo,dojox){ +dojo.provide("dojox.drawing.manager.Stencil"); + +(function(){ + var surface, surfaceNode; + dojox.drawing.manager.Stencil = dojox.drawing.util.oo.declare( + // summary: + // The main class for tracking Stencils that are cretaed, added, + // selected, or deleted. Also handles selections, multiple + // selections, adding and removing from selections, and dragging + // selections. It's this class that triggers the anchors to + // appear on a Stencil and whther there are anchor on a multiple + // select or not (currently not) + // + function(options){ + // + // TODO: mixin props + // + surface = options.surface; + this.canvas = options.canvas; + + this.defaults = dojox.drawing.defaults.copy(); + this.undo = options.undo; + this.mouse = options.mouse; + this.keys = options.keys; + this.anchors = options.anchors; + this.stencils = {}; + this.selectedStencils = {}; + this._mouseHandle = this.mouse.register(this); + + dojo.connect(this.keys, "onArrow", this, "onArrow"); + dojo.connect(this.keys, "onEsc", this, "deselect"); + dojo.connect(this.keys, "onDelete", this, "onDelete"); + + }, + { + _dragBegun: false, + _wasDragged:false, + _secondClick:false, + _isBusy:false, + + setRecentStencil: function(stencil){ + // summary: + // Keeps track of the most recent stencil interacted + // with, whether created or selected. + this.recent = stencil; + }, + + getRecentStencil: function(){ + // summary: + // Returns the stencil most recently interacted + // with whether it's last created or last selected + return this.recent; + }, + + register: function(/*Object*/stencil){ + // summary: + // Key method for adding Stencils. Stencils + // can be added to the canvas without adding + // them to this, but they won't have selection + // or drag ability. + // + console.log("Selection.register ::::::", stencil.id); + if(stencil.isText && !stencil.editMode && stencil.deleteEmptyCreate && !stencil.getText()){ + // created empty text field + // defaults say to delete + console.warn("EMPTY CREATE DELETE", stencil); + stencil.destroy(); + return false; + } + + this.stencils[stencil.id] = stencil; + this.setRecentStencil(stencil); + + if(stencil.execText){ + if(stencil._text && !stencil.editMode){ + console.log("select text"); + this.selectItem(stencil); + } + stencil.connect("execText", this, function(){ + if(stencil.isText && stencil.deleteEmptyModify && !stencil.getText()){ + console.warn("EMPTY MOD DELETE", stencil); + // text deleted + // defaults say to delete + this.deleteItem(stencil); + }else if(stencil.selectOnExec){ + this.selectItem(stencil); + } + }); + } + + stencil.connect("deselect", this, function(){ + if(!this._isBusy && this.isSelected(stencil)){ + // called from within stencil. do action. + this.deselectItem(stencil); + } + }); + + stencil.connect("select", this, function(){ + if(!this._isBusy && !this.isSelected(stencil)){ + // called from within stencil. do action. + this.selectItem(stencil); + } + }); + + return stencil; + }, + unregister: function(/*Object*/stencil){ + // summary: + // Method for removing Stencils from the manager. + // This doesn't delete them, only removes them from + // the list. + // + console.log("Selection.unregister ::::::", stencil.id, "sel:", stencil.selected); + if(stencil){ + stencil.selected && this.onDeselect(stencil); + delete this.stencils[stencil.id]; + } + }, + + onArrow: function(/*Key Event*/evt){ + // summary: + // Moves selection based on keyboard arrow keys + // + // FIXME: Check constraints + if(this.hasSelected()){ + this.saveThrottledState(); + this.group.applyTransform({dx:evt.x, dy: evt.y}); + } + }, + + _throttleVrl:null, + _throttle: false, + throttleTime:400, + _lastmxx:-1, + _lastmxy:-1, + saveMoveState: function(){ + // summary: + // Internal. Used for the prototype undo stack. + // Saves selection position. + // + var mx = this.group.getTransform(); + if(mx.dx == this._lastmxx && mx.dy == this._lastmxy){ return; } + this._lastmxx = mx.dx; + this._lastmxy = mx.dy; + //console.warn("SAVE MOVE!", mx.dx, mx.dy); + this.undo.add({ + before:dojo.hitch(this.group, "setTransform", mx) + }); + }, + + saveThrottledState: function(){ + // summary: + // Internal. Used for the prototype undo stack. + // Prevents an undo point on every mouse move. + // Only does a point when the mouse hesitates. + // + clearTimeout(this._throttleVrl); + clearInterval(this._throttleVrl); + this._throttleVrl = setTimeout(dojo.hitch(this, function(){ + this._throttle = false; + this.saveMoveState(); + }), this.throttleTime); + if(this._throttle){ return; } + this._throttle = true; + + this.saveMoveState(); + + }, + unDelete: function(/*Array*/stencils){ + // summary: + // Undeletes a stencil. Used in undo stack. + // + console.log("unDelete:", stencils); + for(var s in stencils){ + stencils[s].render(); + this.onSelect(stencils[s]); + } + }, + onDelete: function(/*Boolean*/noundo){ + // summary: + // Event fired on deletion of a stencil + // + console.log("Stencil onDelete", noundo); + if(noundo!==true){ + this.undo.add({ + before:dojo.hitch(this, "unDelete", this.selectedStencils), + after:dojo.hitch(this, "onDelete", true) + }); + } + this.withSelected(function(m){ + this.anchors.remove(m); + var id = m.id; + console.log("delete:", m); + m.destroy(); + delete this.stencils[id]; + }); + this.selectedStencils = {}; + }, + + deleteItem: function(/*Object*/stencil){ + // summary: + // Deletes a stencil. + // NOTE: supports limited undo. + // + // manipulating the selection to fire onDelete properly + if(this.hasSelected()){ + // there is a selection + var sids = []; + for(var m in this.selectedStencils){ + if(this.selectedStencils.id == stencil.id){ + if(this.hasSelected()==1){ + // the deleting stencil is the only one selected + this.onDelete(); + return; + } + }else{ + sids.push(this.selectedStencils.id); + } + } + // remove selection, delete, restore selection + this.deselect(); + this.selectItem(stencil); + this.onDelete(); + dojo.forEach(sids, function(id){ + this.selectItem(id); + }, this); + }else{ + // there is not a selection. select it, delete it + this.selectItem(stencil); + // now delete selection + this.onDelete(); + } + }, + + removeAll: function(){ + // summary: + // Deletes all Stencils on the canvas. + + this.selectAll(); + this._isBusy = true; + this.onDelete(); + this.stencils = {}; + this._isBusy = false; + }, + + setSelectionGroup: function(){ + // summary: + // Internal. Creates a new selection group + // used to hold selected stencils. + // + this.withSelected(function(m){ + this.onDeselect(m, true); + }); + + if(this.group){ + surface.remove(this.group); + this.group.removeShape(); + } + this.group = surface.createGroup(); + this.group.setTransform({dx:0, dy: 0}); + + this.withSelected(function(m){ + this.group.add(m.container); + m.select(); + }); + }, + + setConstraint: function(){ + // summary: + // Internal. Gets all selected stencils' coordinates + // and determines how far left and up the selection + // can go without going below zero + // + var t = Infinity, l = Infinity; + this.withSelected(function(m){ + var o = m.getBounds(); + t = Math.min(o.y1, t); + l = Math.min(o.x1, l); + }); + this.constrain = {l:-l, t:-t}; + }, + + + + onDeselect: function(stencil, keepObject){ + // summary: + // Event fired on deselection of a stencil + // + if(!keepObject){ + delete this.selectedStencils[stencil.id]; + } + //console.log('onDeselect, keep:', keepObject, "stencil:", stencil.type) + + this.anchors.remove(stencil); + + surface.add(stencil.container); + stencil.selected && stencil.deselect(); + stencil.applyTransform(this.group.getTransform()); + }, + + deselectItem: function(/*Object*/stencil){ + // summary: + // Deselect passed stencil + // + // note: just keeping with standardized methods + this.onDeselect(stencil); + }, + + deselect: function(){ // all stencils + // summary: + // Deselect all stencils + // + this.withSelected(function(m){ + this.onDeselect(m); + }); + this._dragBegun = false; + this._wasDragged = false; + }, + + onSelect: function(/*Object*/stencil){ + // summary: + // Event fired on selection of a stencil + // + //console.log("stencil.onSelect", stencil); + if(!stencil){ + console.error("null stencil is not selected:", this.stencils) + } + if(this.selectedStencils[stencil.id]){ return; } + this.selectedStencils[stencil.id] = stencil; + this.group.add(stencil.container); + stencil.select(); + if(this.hasSelected()==1){ + this.anchors.add(stencil, this.group); + } + }, + + selectAll: function(){ + // summary: + // Selects all items + this._isBusy = true; + for(var m in this.stencils){ + //if(!this.stencils[m].selected){ + this.selectItem(m); + //} + } + this._isBusy = false; + }, + + selectItem: function(/*String|Object*/ idOrItem){ + // summary: + // Method used to select a stencil. + // + var id = typeof(idOrItem)=="string" ? idOrItem : idOrItem.id; + var stencil = this.stencils[id]; + this.setSelectionGroup(); + this.onSelect(stencil); + this.group.moveToFront(); + this.setConstraint(); + }, + + onLabelDoubleClick: function(/*EventObject*/obj){ + // summary: + // Event to connect a textbox to + // for label edits + console.info("mgr.onLabelDoubleClick:", obj); + if(this.selectedStencils[obj.id]){ + this.deselect(); + } + }, + + onStencilDoubleClick: function(/*EventObject*/obj){ + // summary: + // Event fired on the double-click of a stencil + // + console.info("mgr.onStencilDoubleClick:", obj); + if(this.selectedStencils[obj.id]){ + if(this.selectedStencils[obj.id].edit){ + console.info("Mgr Stencil Edit -> ", this.selectedStencils[obj.id]); + var m = this.selectedStencils[obj.id]; + // deselect must happen first to set the transform + // then edit knows where to set the text box + m.editMode = true; + this.deselect(); + m.edit(); + } + } + + }, + + onAnchorUp: function(){ + // summary: + // Event fire on mouseup off of an anchor point + this.setConstraint(); + }, + + onStencilDown: function(/*EventObject*/obj, evt){ + // summary: + // Event fired on mousedown on a stencil + // + console.info(" >>> onStencilDown:", obj.id, this.keys.meta); + if(!this.stencils[obj.id]){ return; } + this.setRecentStencil(this.stencils[obj.id]); + this._isBusy = true; + + + if(this.selectedStencils[obj.id] && this.keys.meta){ + if(dojo.isMac && this.keys.cmmd){ + // block context menu + + } + console.log(" shift remove"); + this.onDeselect(this.selectedStencils[obj.id]); + if(this.hasSelected()==1){ + this.withSelected(function(m){ + this.anchors.add(m, this.group); + }); + } + this.group.moveToFront(); + this.setConstraint(); + return; + + }else if(this.selectedStencils[obj.id]){ + console.log(" clicked on selected"); + // clicking on same selected item(s) + // RESET OFFSETS + var mx = this.group.getTransform(); + this._offx = obj.x - mx.dx; + this._offy = obj.y - mx.dy; + return; + + }else if(!this.keys.meta){ + + console.log(" deselect all"); + this.deselect(); + + }else{ + // meta-key add + //console.log("reset sel and add stencil") + } + console.log(" add stencil to selection"); + // add a stencil + this.selectItem(obj.id); + + mx = this.group.getTransform(); + this._offx = obj.x - mx.dx; + this._offy = obj.y - mx.dx; + + this.orgx = obj.x; + this.orgy = obj.y; + + this._isBusy = false; + + // TODO: + // dojo.style(surfaceNode, "cursor", "pointer"); + + // TODO: + this.undo.add({ + before:function(){ + + }, + after: function(){ + + } + }); + }, + + onLabelDown: function(/*EventObject*/obj, evt){ + // summary: + // Event fired on mousedown of a stencil's label + // Because it's an annotation the id will be the + // master stencil. + //console.info("===============>>>Label click: ",obj, " evt: ",evt); + this.onStencilDown(obj,evt); + }, + + onStencilUp: function(/*EventObject*/obj){ + // summary: + // Event fired on mouseup off of a stencil + // + }, + + onLabelUp: function(/*EventObject*/obj){ + this.onStencilUp(obj); + }, + + onStencilDrag: function(/*EventObject*/obj){ + // summary: + // Event fired on every mousemove of a stencil drag + // + if(!this._dragBegun){ + // bug, in FF anyway - first mouse move shows x=0 + // the 'else' fixes it + this.onBeginDrag(obj); + this._dragBegun = true; + }else{ + this.saveThrottledState(); + + var x = obj.x - obj.last.x, + y = obj.y - obj.last.y, + c = this.constrain, + mz = this.defaults.anchors.marginZero; + + + x = obj.x - this._offx; + y = obj.y - this._offy; + + if(x < c.l + mz){ + x = c.l + mz; + } + if(y < c.t + mz){ + y = c.t + mz; + } + + this.group.setTransform({ + dx: x, + dy: y + }); + + + } + }, + + onLabelDrag: function(/*EventObject*/obj){ + this.onStencilDrag(obj); + }, + + onDragEnd: function(/*EventObject*/obj){ + // summary: + // Event fired at the end of a stencil drag + // + this._dragBegun = false; + }, + onBeginDrag: function(/*EventObject*/obj){ + // summary: + // Event fired at the beginning of a stencil drag + // + this._wasDragged = true; + }, + + onDown: function(/*EventObject*/obj){ + // summary: + // Event fired on mousedown on the canvas + // + this.deselect(); + }, + + + onStencilOver: function(obj){ + // summary: + // This changes the cursor when hovering over + // a selectable stencil. + //console.log("OVER") + dojo.style(obj.id, "cursor", "move"); + }, + + onStencilOut: function(obj){ + // summary: + // This restores the cursor. + //console.log("OUT") + dojo.style(obj.id, "cursor", "crosshair"); + }, + + exporter: function(){ + // summary: + // Collects all Stencil data and returns an + // Array of objects. + var items = []; + for(var m in this.stencils){ + this.stencils[m].enabled && items.push(this.stencils[m].exporter()); + } + return items; // Array + }, + + listStencils: function(){ + return this.stencils; + }, + + toSelected: function(/*String*/func){ + // summary: + // Convenience function calls function *within* + // all selected stencils + var args = Array.prototype.slice.call(arguments).splice(1); + for(var m in this.selectedStencils){ + var item = this.selectedStencils[m]; + item[func].apply(item, args); + } + }, + + withSelected: function(/*Function*/func){ + // summary: + // Convenience function calls function on + // all selected stencils + var f = dojo.hitch(this, func); + for(var m in this.selectedStencils){ + f(this.selectedStencils[m]); + } + }, + + withUnselected: function(/*Function*/func){ + // summary: + // Convenience function calls function on + // all stencils that are not selected + var f = dojo.hitch(this, func); + for(var m in this.stencils){ + !this.stencils[m].selected && f(this.stencils[m]); + } + }, + + withStencils: function(/*Function*/func){ + // summary: + // Convenience function calls function on + // all stencils + var f = dojo.hitch(this, func); + for(var m in this.stencils){ + f(this.stencils[m]); + } + }, + + hasSelected: function(){ + // summary: + // Returns number of selected (generally used + // as truthy or falsey) + // + // FIXME: should be areSelected? + var ln = 0; + for(var m in this.selectedStencils){ ln++; } + return ln; // Number + }, + + isSelected: function(/*Object*/stencil){ + // summary: + // Returns if passed stencil is selected or not + // based on internal collection, not on stencil + // boolean + return !!this.selectedStencils[stencil.id]; // Boolean + } + } + + ); +})(); + +}); |
