[483] | 1 | define(["dojo", "dijit/registry", "../util/oo", "../manager/_registry", "../stencil/Text"], |
---|
| 2 | function(dojo, dijit, oo, registry, StencilText){ |
---|
| 3 | |
---|
| 4 | var conEdit; |
---|
| 5 | dojo.addOnLoad(function(){ |
---|
| 6 | // In order to use VML in IE, it's necessary to remove the |
---|
| 7 | // DOCTYPE. But this has the side effect that causes a bug |
---|
| 8 | // where contenteditable divs cannot be made dynamically. |
---|
| 9 | // The solution is to include one in the main document |
---|
| 10 | // that can be appended and removed as necessary: |
---|
| 11 | // <div id="conEdit" contenteditable="true"></div> |
---|
| 12 | |
---|
| 13 | // console.log("Removing conedit"); |
---|
| 14 | conEdit = dojo.byId("conEdit"); |
---|
| 15 | if(!conEdit){ |
---|
| 16 | console.error("A contenteditable div is missing from the main document. See 'dojox.drawing.tools.TextBlock'") |
---|
| 17 | }else{ |
---|
| 18 | conEdit.parentNode.removeChild(conEdit); |
---|
| 19 | } |
---|
| 20 | }); |
---|
| 21 | |
---|
| 22 | var TextBlock = oo.declare( |
---|
| 23 | // TODO - disable zoom while showing? |
---|
| 24 | |
---|
| 25 | // FIXME: |
---|
| 26 | // Handles width: auto, align:middle, etc. but for |
---|
| 27 | // display only, edit is out of whack |
---|
| 28 | |
---|
| 29 | StencilText, |
---|
| 30 | function(options){ |
---|
| 31 | // summary: |
---|
| 32 | // constructor |
---|
| 33 | |
---|
| 34 | if(options.data){ |
---|
| 35 | var d = options.data; |
---|
| 36 | var text = d.text ? this.typesetter(d.text) : d.text; |
---|
| 37 | var w = !d.width ? this.style.text.minWidth : d.width=="auto" ? "auto" : Math.max(d.width, this.style.text.minWidth); |
---|
| 38 | var h = this._lineHeight; |
---|
| 39 | |
---|
| 40 | if(text && w=="auto"){ |
---|
| 41 | var o = this.measureText(this.cleanText(text, false), w); |
---|
| 42 | w = o.w; |
---|
| 43 | h = o.h; |
---|
| 44 | }else{ |
---|
| 45 | // w = this.style.text.minWidth; |
---|
| 46 | this._text = ""; |
---|
| 47 | } |
---|
| 48 | |
---|
| 49 | this.points = [ |
---|
| 50 | {x:d.x, y:d.y}, |
---|
| 51 | {x:d.x+w, y:d.y}, |
---|
| 52 | {x:d.x+w, y:d.y+h}, |
---|
| 53 | {x:d.x, y:d.y+h} |
---|
| 54 | ]; |
---|
| 55 | |
---|
| 56 | if(d.showEmpty || text){ |
---|
| 57 | this.editMode = true; |
---|
| 58 | |
---|
| 59 | |
---|
| 60 | dojo.disconnect(this._postRenderCon); |
---|
| 61 | this._postRenderCon = null; |
---|
| 62 | this.connect(this, "render", this, "onRender", true); |
---|
| 63 | |
---|
| 64 | if(d.showEmpty){ |
---|
| 65 | this._text = text || ""; |
---|
| 66 | this.edit(); |
---|
| 67 | }else if(text && d.editMode){ |
---|
| 68 | this._text = ""; |
---|
| 69 | this.edit(); |
---|
| 70 | }else if(text){ |
---|
| 71 | this.render(text); |
---|
| 72 | } |
---|
| 73 | setTimeout(dojo.hitch(this, function(){ |
---|
| 74 | this.editMode = false; |
---|
| 75 | }),100) |
---|
| 76 | |
---|
| 77 | }else{ |
---|
| 78 | // Why make it if it won't render... |
---|
| 79 | this.render(); |
---|
| 80 | } |
---|
| 81 | |
---|
| 82 | }else{ |
---|
| 83 | this.connectMouse(); |
---|
| 84 | this._postRenderCon = dojo.connect(this, "render", this, "_onPostRender"); |
---|
| 85 | } |
---|
| 86 | //console.log("TextBlock:", this.id) |
---|
| 87 | }, |
---|
| 88 | { |
---|
| 89 | // summary: |
---|
| 90 | // A tool to create text fields on a canvas. |
---|
| 91 | // description: |
---|
| 92 | // Extends stencil.Text by adding an HTML layer that |
---|
| 93 | // can be dragged out to a certain size, and accept |
---|
| 94 | // a text entry. Will wrap text to the width of the |
---|
| 95 | // html field. |
---|
| 96 | // When created programmtically, use 'auto' to shrink |
---|
| 97 | // the width to the size of the text. Use line breaks |
---|
| 98 | // ( \n ) to create new lines. |
---|
| 99 | |
---|
| 100 | draws:true, |
---|
| 101 | baseRender:false, |
---|
| 102 | type:"dojox.drawing.tools.TextBlock", |
---|
| 103 | _caretStart: 0, |
---|
| 104 | _caretEnd: 0, |
---|
| 105 | _blockExec: false, |
---|
| 106 | |
---|
| 107 | /*===== |
---|
| 108 | StencilData: { |
---|
| 109 | // summary: |
---|
| 110 | // The data used to create the dojox.gfx Text |
---|
| 111 | // x: Number |
---|
| 112 | // Left point x |
---|
| 113 | // y: Number |
---|
| 114 | // Top point y |
---|
| 115 | // width: Number|String? |
---|
| 116 | // Optional width of Text. Not required but recommended. |
---|
| 117 | // for auto-sizing, use 'auto' |
---|
| 118 | // height: Number? |
---|
| 119 | // Optional height of Text. If not provided, _lineHeight is used. |
---|
| 120 | // text: String |
---|
| 121 | // The string content. If not provided, may auto-delete depending on defaults. |
---|
| 122 | }, |
---|
| 123 | =====*/ |
---|
| 124 | |
---|
| 125 | // selectOnExec: Boolean |
---|
| 126 | // Whether the Stencil is selected when the text field |
---|
| 127 | // is executed or not |
---|
| 128 | selectOnExec:true, |
---|
| 129 | |
---|
| 130 | // showEmpty: Boolean |
---|
| 131 | // If true and there is no text in the data, the TextBlock |
---|
| 132 | // Is displayed and focused and awaits input. |
---|
| 133 | showEmpty: false, |
---|
| 134 | |
---|
| 135 | onDrag: function(/*EventObject*/obj){ |
---|
| 136 | if(!this.parentNode){ |
---|
| 137 | this.showParent(obj); |
---|
| 138 | } |
---|
| 139 | var s = this._startdrag, e = obj.page; |
---|
| 140 | this._box.left = (s.x < e.x ? s.x : e.x); |
---|
| 141 | this._box.top = s.y; |
---|
| 142 | this._box.width = (s.x < e.x ? e.x-s.x : s.x-e.x) + this.style.text.pad; |
---|
| 143 | |
---|
| 144 | dojo.style(this.parentNode, this._box.toPx()); |
---|
| 145 | }, |
---|
| 146 | |
---|
| 147 | onUp: function(/*EventObject*/obj){ |
---|
| 148 | if(!this._downOnCanvas){ return; } |
---|
| 149 | this._downOnCanvas = false; |
---|
| 150 | |
---|
| 151 | var c = dojo.connect(this, "render", this, function(){ |
---|
| 152 | dojo.disconnect(c); |
---|
| 153 | this.onRender(this); |
---|
| 154 | |
---|
| 155 | }); |
---|
| 156 | this.editMode = true; |
---|
| 157 | this.showParent(obj); |
---|
| 158 | this.created = true; |
---|
| 159 | this.createTextField(); |
---|
| 160 | this.connectTextField(); |
---|
| 161 | }, |
---|
| 162 | |
---|
| 163 | showParent: function(/*EventObject*/obj){ |
---|
| 164 | // summary: |
---|
| 165 | // Internal. Builds the parent node for the |
---|
| 166 | // contenteditable HTML node. |
---|
| 167 | |
---|
| 168 | if(this.parentNode){ return; } |
---|
| 169 | var x = obj.pageX || 10; |
---|
| 170 | var y = obj.pageY || 10; |
---|
| 171 | this.parentNode = dojo.doc.createElement("div"); |
---|
| 172 | this.parentNode.id = this.id; |
---|
| 173 | var d = this.style.textMode.create; |
---|
| 174 | this._box = { |
---|
| 175 | left:x, |
---|
| 176 | top:y, |
---|
| 177 | width:obj.width || 1, |
---|
| 178 | height:obj.height && obj.height>8 ? obj.height : this._lineHeight, |
---|
| 179 | border:d.width+"px "+d.style+" "+d.color, |
---|
| 180 | position:"absolute", |
---|
| 181 | zIndex:500, |
---|
| 182 | toPx: function(){ |
---|
| 183 | var o = {}; |
---|
| 184 | for(var nm in this){ |
---|
| 185 | o[nm] = typeof(this[nm])=="number" && nm!="zIndex" ? this[nm] + "px" : this[nm]; |
---|
| 186 | } |
---|
| 187 | return o; |
---|
| 188 | } |
---|
| 189 | }; |
---|
| 190 | |
---|
| 191 | dojo.style(this.parentNode, this._box); |
---|
| 192 | |
---|
| 193 | document.body.appendChild(this.parentNode); |
---|
| 194 | }, |
---|
| 195 | createTextField: function(/*String*/txt){ |
---|
| 196 | // summary: |
---|
| 197 | // Internal. Inserts the contenteditable HTML node |
---|
| 198 | // into its parent node, and styles it. |
---|
| 199 | |
---|
| 200 | // style parent |
---|
| 201 | var d = this.style.textMode.edit; |
---|
| 202 | this._box.border = d.width+"px "+d.style+" "+d.color; |
---|
| 203 | this._box.height = "auto"; |
---|
| 204 | this._box.width = Math.max(this._box.width, this.style.text.minWidth*this.mouse.zoom); |
---|
| 205 | dojo.style(this.parentNode, this._box.toPx()); |
---|
| 206 | // style input |
---|
| 207 | this.parentNode.appendChild(conEdit); |
---|
| 208 | dojo.style(conEdit, { |
---|
| 209 | height: txt ? "auto" : this._lineHeight+"px", |
---|
| 210 | fontSize:(this.textSize/this.mouse.zoom)+"px", |
---|
| 211 | fontFamily:this.style.text.family |
---|
| 212 | }); |
---|
| 213 | // FIXME: |
---|
| 214 | // In Safari, if the txt ends with '&' it gets stripped |
---|
| 215 | conEdit.innerHTML = txt || ""; |
---|
| 216 | |
---|
| 217 | return conEdit; //HTMLNode |
---|
| 218 | }, |
---|
| 219 | connectTextField: function(){ |
---|
| 220 | // summary: |
---|
| 221 | // Internal. Creates the connections to the |
---|
| 222 | // contenteditable HTML node. |
---|
| 223 | |
---|
| 224 | if(this._textConnected){ return; } // good ol' IE and its double events |
---|
| 225 | // FIXME: |
---|
| 226 | // Ouch-getting greekPalette by id. At the minimum this should |
---|
| 227 | // be from the plugin manager |
---|
| 228 | var greekPalette = dijit.byId("greekPalette"); |
---|
| 229 | var greekHelp = greekPalette==undefined ? false : true; |
---|
| 230 | if(greekHelp){ |
---|
| 231 | //set it up |
---|
| 232 | dojo.mixin(greekPalette,{ |
---|
| 233 | _pushChangeTo: conEdit, |
---|
| 234 | _textBlock: this |
---|
| 235 | }); |
---|
| 236 | }; |
---|
| 237 | |
---|
| 238 | this._textConnected = true; |
---|
| 239 | this._dropMode = false; |
---|
| 240 | this.mouse.setEventMode("TEXT"); |
---|
| 241 | this.keys.editMode(true); |
---|
| 242 | var kc1, kc2, kc3, kc4, self = this, _autoSet = false, |
---|
| 243 | exec = function(){ |
---|
| 244 | if(self._dropMode){ return; } |
---|
| 245 | dojo.forEach([kc1,kc2,kc3,kc4], function(c){ |
---|
| 246 | dojo.disconnect(c) |
---|
| 247 | }); |
---|
| 248 | self._textConnected = false; |
---|
| 249 | self.keys.editMode(false); |
---|
| 250 | self.mouse.setEventMode(); |
---|
| 251 | self.execText(); |
---|
| 252 | }; |
---|
| 253 | |
---|
| 254 | kc1 = dojo.connect(conEdit, "keyup", this, function(evt){ |
---|
| 255 | // if text is empty, we need a height so the field's height |
---|
| 256 | // doesn't collapse |
---|
| 257 | if(dojo.trim(conEdit.innerHTML) && !_autoSet){ |
---|
| 258 | dojo.style(conEdit, "height", "auto"); _autoSet = true; |
---|
| 259 | }else if(dojo.trim(conEdit.innerHTML).length<2 && _autoSet){ |
---|
| 260 | dojo.style(conEdit, "height", this._lineHeight+"px"); _autoSet = false; |
---|
| 261 | } |
---|
| 262 | |
---|
| 263 | if(!this._blockExec){ |
---|
| 264 | if(evt.keyCode==13 || evt.keyCode==27){ |
---|
| 265 | dojo.stopEvent(evt); |
---|
| 266 | exec(); |
---|
| 267 | } |
---|
| 268 | } else { |
---|
| 269 | if(evt.keyCode==dojo.keys.SPACE){ |
---|
| 270 | dojo.stopEvent(evt); |
---|
| 271 | greekHelp && greekPalette.onCancel(); |
---|
| 272 | } |
---|
| 273 | } |
---|
| 274 | }); |
---|
| 275 | kc2 = dojo.connect(conEdit, "keydown", this, function(evt){ |
---|
| 276 | if(evt.keyCode==13 || evt.keyCode==27){ // TODO: make escape an option |
---|
| 277 | dojo.stopEvent(evt); |
---|
| 278 | } |
---|
| 279 | // if backslash, user is inputting a special character |
---|
| 280 | // This gives popup help. |
---|
| 281 | if(evt.keyCode==220){ |
---|
| 282 | if(!greekHelp){ |
---|
| 283 | console.info("For greek letter assistance instantiate: dojox.drawing.plugins.drawing.GreekPalette"); |
---|
| 284 | return; |
---|
| 285 | } |
---|
| 286 | dojo.stopEvent(evt); |
---|
| 287 | this.getSelection(conEdit); |
---|
| 288 | // Differences in how browsers handle events made it necessary |
---|
| 289 | // to stop the evt and add the backslash here. |
---|
| 290 | this.insertText(conEdit,"\\"); |
---|
| 291 | this._dropMode = true; |
---|
| 292 | this._blockExec = true; |
---|
| 293 | greekPalette.show({ |
---|
| 294 | around:this.parentNode, |
---|
| 295 | orient:{'BL':'TL'} |
---|
| 296 | }); |
---|
| 297 | } |
---|
| 298 | if(!this._dropMode){ |
---|
| 299 | this._blockExec = false; |
---|
| 300 | } else { |
---|
| 301 | // Controls for when we have a character helper and it's active |
---|
| 302 | switch(evt.keyCode){ |
---|
| 303 | case dojo.keys.UP_ARROW: |
---|
| 304 | case dojo.keys.DOWN_ARROW: |
---|
| 305 | case dojo.keys.LEFT_ARROW: |
---|
| 306 | case dojo.keys.RIGHT_ARROW: |
---|
| 307 | dojo.stopEvent(evt); |
---|
| 308 | greekPalette._navigateByArrow(evt); |
---|
| 309 | break; |
---|
| 310 | case dojo.keys.ENTER: |
---|
| 311 | dojo.stopEvent(evt); |
---|
| 312 | greekPalette._onCellClick(evt); |
---|
| 313 | break; |
---|
| 314 | case dojo.keys.BACKSPACE: |
---|
| 315 | case dojo.keys.DELETE: |
---|
| 316 | dojo.stopEvent(evt); |
---|
| 317 | greekPalette.onCancel(); |
---|
| 318 | break; |
---|
| 319 | } |
---|
| 320 | } |
---|
| 321 | |
---|
| 322 | }); |
---|
| 323 | |
---|
| 324 | kc3 = dojo.connect(document, "mouseup", this, function(evt){ |
---|
| 325 | // note: _onAnchor means an anchor has been clicked upon |
---|
| 326 | if(!this._onAnchor && evt.target.id != "conEdit"){ |
---|
| 327 | dojo.stopEvent(evt); |
---|
| 328 | exec(); |
---|
| 329 | }else if(evt.target.id == "conEdit" && conEdit.innerHTML == ""){ |
---|
| 330 | // wonky stuff happens when you click on the |
---|
| 331 | // field when its empty. |
---|
| 332 | conEdit.blur(); |
---|
| 333 | setTimeout(function(){ |
---|
| 334 | conEdit.focus(); |
---|
| 335 | },200) |
---|
| 336 | } |
---|
| 337 | }); |
---|
| 338 | |
---|
| 339 | this.createAnchors(); |
---|
| 340 | |
---|
| 341 | kc4 = dojo.connect(this.mouse, "setZoom", this, function(evt){ |
---|
| 342 | exec(); |
---|
| 343 | }); |
---|
| 344 | |
---|
| 345 | |
---|
| 346 | conEdit.focus(); |
---|
| 347 | |
---|
| 348 | this.onDown = function(){}; |
---|
| 349 | this.onDrag = function(){}; |
---|
| 350 | |
---|
| 351 | setTimeout(dojo.hitch(this, function(){ |
---|
| 352 | // once again for Silverlight: |
---|
| 353 | conEdit.focus(); |
---|
| 354 | |
---|
| 355 | // this is a pretty odd chunk of code here. |
---|
| 356 | // specifcally need to overwrite old onUp |
---|
| 357 | // however, this still gets called. its |
---|
| 358 | // not disconnecting. |
---|
| 359 | this.onUp = function(){ |
---|
| 360 | if(!self._onAnchor && this.parentNode){ |
---|
| 361 | self.disconnectMouse(); |
---|
| 362 | exec(); |
---|
| 363 | self.onUp = function(){} |
---|
| 364 | } |
---|
| 365 | } |
---|
| 366 | }), 500); |
---|
| 367 | }, |
---|
| 368 | |
---|
| 369 | |
---|
| 370 | execText: function(){ |
---|
| 371 | // summary: |
---|
| 372 | // Internal. Method fired when text is executed, |
---|
| 373 | // via mouse-click-off, ESC key or Enter key. |
---|
| 374 | |
---|
| 375 | var d = dojo.marginBox(this.parentNode); |
---|
| 376 | var w = Math.max(d.w, this.style.text.minWidth); |
---|
| 377 | |
---|
| 378 | var txt = this.cleanText(conEdit.innerHTML, true); |
---|
| 379 | conEdit.innerHTML = ""; |
---|
| 380 | conEdit.blur(); |
---|
| 381 | this.destroyAnchors(); |
---|
| 382 | |
---|
| 383 | // need to convert characters before measuring width. |
---|
| 384 | txt = this.typesetter(txt); |
---|
| 385 | |
---|
| 386 | var o = this.measureText(txt, w); |
---|
| 387 | var sc = this.mouse.scrollOffset(); |
---|
| 388 | var org = this.mouse.origin; |
---|
| 389 | |
---|
| 390 | var x = this._box.left + sc.left - org.x; |
---|
| 391 | var y = this._box.top + sc.top - org.y; |
---|
| 392 | |
---|
| 393 | x *= this.mouse.zoom; |
---|
| 394 | y *= this.mouse.zoom; |
---|
| 395 | w *= this.mouse.zoom; |
---|
| 396 | o.h *= this.mouse.zoom; |
---|
| 397 | |
---|
| 398 | |
---|
| 399 | this.points = [ |
---|
| 400 | {x:x, y:y}, |
---|
| 401 | {x:x+w, y:y}, |
---|
| 402 | {x:x+w, y:y+o.h}, |
---|
| 403 | {x:x, y:y+o.h} |
---|
| 404 | ]; |
---|
| 405 | this.editMode = false; |
---|
| 406 | |
---|
| 407 | |
---|
| 408 | console.log("EXEC TEXT::::", this._postRenderCon); |
---|
| 409 | |
---|
| 410 | if(!o.text){ |
---|
| 411 | this._text = ""; |
---|
| 412 | this._textArray = []; |
---|
| 413 | } |
---|
| 414 | // Only for Combo objects (vectors, rectangle, or ellipse). |
---|
| 415 | this.render(o.text); |
---|
| 416 | this.onChangeText(this.getText()); |
---|
| 417 | }, |
---|
| 418 | |
---|
| 419 | edit: function(){ |
---|
| 420 | // summary: |
---|
| 421 | // Internal? |
---|
| 422 | // Method used to instantiate the contenteditable HTML node. |
---|
| 423 | |
---|
| 424 | this.editMode = true; |
---|
| 425 | var text = this.getText() || ""; |
---|
| 426 | console.log("EDIT TEXT:",text, " ",text.replace("/n", " ")); |
---|
| 427 | // NOTE: no mouse obj |
---|
| 428 | if(this.parentNode || !this.points){ return; } |
---|
| 429 | var d = this.pointsToData(); |
---|
| 430 | |
---|
| 431 | var sc = this.mouse.scrollOffset(); |
---|
| 432 | var org = this.mouse.origin; |
---|
| 433 | |
---|
| 434 | var obj = { |
---|
| 435 | pageX: (d.x ) / this.mouse.zoom - sc.left + org.x, |
---|
| 436 | pageY: (d.y ) / this.mouse.zoom- sc.top + org.y, |
---|
| 437 | width:d.width / this.mouse.zoom, |
---|
| 438 | height:d.height / this.mouse.zoom |
---|
| 439 | }; |
---|
| 440 | |
---|
| 441 | this.remove(this.shape, this.hit); |
---|
| 442 | this.showParent(obj); |
---|
| 443 | this.createTextField(text.replace("/n", " ")); |
---|
| 444 | this.connectTextField(); |
---|
| 445 | if(text){ |
---|
| 446 | //setTimeout(dojo.hitch(this, function(){ |
---|
| 447 | this.setSelection(conEdit, "end"); |
---|
| 448 | //}), 500) |
---|
| 449 | } |
---|
| 450 | }, |
---|
| 451 | cleanText: function(/*String*/txt, /*Boolean*/removeBreaks){ |
---|
| 452 | // summary: |
---|
| 453 | // Cleans text. Strings HTML chars and double spaces |
---|
| 454 | // and optionally removes line breaks. |
---|
| 455 | var replaceHtmlCodes = function(str){ |
---|
| 456 | var chars = { |
---|
| 457 | "<":"<", |
---|
| 458 | ">":">", |
---|
| 459 | "&":"&" |
---|
| 460 | }; |
---|
| 461 | for(var nm in chars){ |
---|
| 462 | str = str.replace(new RegExp(nm, "gi"), chars[nm]) |
---|
| 463 | } |
---|
| 464 | return str |
---|
| 465 | }; |
---|
| 466 | |
---|
| 467 | if(removeBreaks){ |
---|
| 468 | dojo.forEach(['<br>', '<br/>', '<br />', '\\n', '\\r'], function(br){ |
---|
| 469 | txt = txt.replace(new RegExp(br, 'gi'), " "); |
---|
| 470 | }); |
---|
| 471 | } |
---|
| 472 | txt = txt.replace(/ /g, " "); |
---|
| 473 | txt = replaceHtmlCodes(txt); |
---|
| 474 | txt = dojo.trim(txt); |
---|
| 475 | // remove double spaces, since SVG doesn't show them anyway |
---|
| 476 | txt = txt.replace(/\s{2,}/g, " "); |
---|
| 477 | return txt; //String |
---|
| 478 | }, |
---|
| 479 | |
---|
| 480 | measureText: function(/* String */ str, /* ? Number */width){ |
---|
| 481 | // summary: |
---|
| 482 | // Mechanism for measuring text. |
---|
| 483 | // SVG nor VML have a way of determining the width or |
---|
| 484 | // height of a block of text. This method creates an |
---|
| 485 | // HTML text block and those measurements are used for |
---|
| 486 | // displaying the SVG/VML text. |
---|
| 487 | // str: String |
---|
| 488 | // The text to display and measure. |
---|
| 489 | // width: [optional] Number |
---|
| 490 | // If the width is not provided, it will be assumed |
---|
| 491 | // that the text is one line and the width will be |
---|
| 492 | // measured and the _lineHeight used for th height. |
---|
| 493 | // If width is provided, word-wrap is assumed, and |
---|
| 494 | // line breaks will be inserted into the text at each |
---|
| 495 | // point where a word wraps in the HTML. The height is |
---|
| 496 | // then measured. |
---|
| 497 | |
---|
| 498 | var r = "(<br\\s*/*>)|(\\n)|(\\r)"; |
---|
| 499 | this.showParent({width:width || "auto", height:"auto"}); |
---|
| 500 | this.createTextField(str); |
---|
| 501 | var txt = ""; |
---|
| 502 | var el = conEdit; |
---|
| 503 | el.innerHTML = "X"; |
---|
| 504 | var h = dojo.marginBox(el).h; |
---|
| 505 | |
---|
| 506 | el.innerHTML = str; |
---|
| 507 | |
---|
| 508 | if(!width || new RegExp(r, "gi").test(str)){ |
---|
| 509 | // has line breaks in text |
---|
| 510 | txt = str.replace(new RegExp(r, "gi"), "\n"); |
---|
| 511 | el.innerHTML = str.replace(new RegExp(r, "gi"), "<br/>"); |
---|
| 512 | |
---|
| 513 | }else if(dojo.marginBox(el).h == h){ |
---|
| 514 | // one line |
---|
| 515 | txt = str; |
---|
| 516 | |
---|
| 517 | }else{ |
---|
| 518 | // text wraps |
---|
| 519 | var ar = str.split(" "); |
---|
| 520 | var strAr = [[]]; |
---|
| 521 | var line = 0; |
---|
| 522 | el.innerHTML = ""; |
---|
| 523 | while(ar.length){ |
---|
| 524 | var word = ar.shift(); |
---|
| 525 | el.innerHTML += word+" "; //urk, always an extra space |
---|
| 526 | if(dojo.marginBox(el).h > h){ |
---|
| 527 | line++; |
---|
| 528 | strAr[line] = []; |
---|
| 529 | el.innerHTML = word+" "; |
---|
| 530 | } |
---|
| 531 | strAr[line].push(word) |
---|
| 532 | } |
---|
| 533 | |
---|
| 534 | dojo.forEach(strAr, function(ar, i){ |
---|
| 535 | strAr[i] = ar.join(" "); |
---|
| 536 | }); |
---|
| 537 | txt = strAr.join("\n"); |
---|
| 538 | |
---|
| 539 | // get the resultant height |
---|
| 540 | el.innerHTML = txt.replace("\n", "<br/>"); |
---|
| 541 | |
---|
| 542 | } |
---|
| 543 | |
---|
| 544 | var dim = dojo.marginBox(el); |
---|
| 545 | |
---|
| 546 | conEdit.parentNode.removeChild(conEdit); |
---|
| 547 | dojo.destroy(this.parentNode); |
---|
| 548 | this.parentNode = null; |
---|
| 549 | |
---|
| 550 | return {h:dim.h, w:dim.w, text:txt}; //Object |
---|
| 551 | }, |
---|
| 552 | |
---|
| 553 | _downOnCanvas:false, |
---|
| 554 | onDown: function(/*EventObject*/obj){ |
---|
| 555 | this._startdrag = { |
---|
| 556 | x: obj.pageX, |
---|
| 557 | y: obj.pageY |
---|
| 558 | }; |
---|
| 559 | dojo.disconnect(this._postRenderCon); |
---|
| 560 | this._postRenderCon = null; |
---|
| 561 | this._downOnCanvas = true; |
---|
| 562 | }, |
---|
| 563 | |
---|
| 564 | createAnchors: function(){ |
---|
| 565 | // summary: |
---|
| 566 | // Internal. Creates HTML nodes at each corner |
---|
| 567 | // of the contenteditable div. These nodes are |
---|
| 568 | // draggable and will resize the div horizontally. |
---|
| 569 | |
---|
| 570 | this._anchors = {}; |
---|
| 571 | var self = this; |
---|
| 572 | var d = this.style.anchors, |
---|
| 573 | b = d.width, |
---|
| 574 | w = d.size-b*2, |
---|
| 575 | h = d.size-b*2, |
---|
| 576 | p = (d.size)/2*-1 + "px"; |
---|
| 577 | |
---|
| 578 | var s = { |
---|
| 579 | position:"absolute", |
---|
| 580 | width:w+"px", |
---|
| 581 | height:h+"px", |
---|
| 582 | backgroundColor:d.fill, |
---|
| 583 | border:b+"px " + d.style + " "+d.color |
---|
| 584 | }; |
---|
| 585 | if(dojo.isIE){ |
---|
| 586 | s.paddingLeft = w + "px"; |
---|
| 587 | s.fontSize = w + "px" |
---|
| 588 | } |
---|
| 589 | var ss = [ |
---|
| 590 | {top: p, left:p}, |
---|
| 591 | {top:p, right:p}, |
---|
| 592 | {bottom:p, right:p}, |
---|
| 593 | {bottom:p,left:p} |
---|
| 594 | ]; |
---|
| 595 | for(var i=0;i<4;i++){ |
---|
| 596 | var isLeft = (i==0) || (i==3); |
---|
| 597 | var id = this.util.uid(isLeft ? "left_anchor" : "right_anchor"); |
---|
| 598 | |
---|
| 599 | var a = dojo.create("div", {id:id}, this.parentNode); |
---|
| 600 | dojo.style(a, dojo.mixin(dojo.clone(s), ss[i])); |
---|
| 601 | |
---|
| 602 | var md, mm, mu; |
---|
| 603 | var md = dojo.connect(a, "mousedown", this, function(evt){ |
---|
| 604 | isLeft = evt.target.id.indexOf("left")>-1; |
---|
| 605 | self._onAnchor = true; |
---|
| 606 | var orgX = evt.pageX; |
---|
| 607 | var orgW = this._box.width; |
---|
| 608 | dojo.stopEvent(evt); |
---|
| 609 | |
---|
| 610 | |
---|
| 611 | mm = dojo.connect(document, "mousemove", this, function(evt){ |
---|
| 612 | var x = evt.pageX; |
---|
| 613 | if(isLeft){ |
---|
| 614 | this._box.left = x; |
---|
| 615 | this._box.width = orgW + orgX - x; |
---|
| 616 | }else{ |
---|
| 617 | this._box.width = x + orgW - orgX; |
---|
| 618 | } |
---|
| 619 | dojo.style(this.parentNode, this._box.toPx()); |
---|
| 620 | }); |
---|
| 621 | |
---|
| 622 | mu = dojo.connect(document, "mouseup", this, function(evt){ |
---|
| 623 | orgX = this._box.left; |
---|
| 624 | orgW = this._box.width; |
---|
| 625 | dojo.disconnect(mm); |
---|
| 626 | dojo.disconnect(mu); |
---|
| 627 | self._onAnchor = false; |
---|
| 628 | conEdit.focus(); |
---|
| 629 | dojo.stopEvent(evt); |
---|
| 630 | }); |
---|
| 631 | }); |
---|
| 632 | |
---|
| 633 | this._anchors[id] = { |
---|
| 634 | a:a, |
---|
| 635 | cons:[md] |
---|
| 636 | } |
---|
| 637 | } |
---|
| 638 | }, |
---|
| 639 | |
---|
| 640 | destroyAnchors: function(){ |
---|
| 641 | // summary: |
---|
| 642 | // Internal. Destroys HTML anchors. |
---|
| 643 | for(var n in this._anchors){ |
---|
| 644 | dojo.forEach(this._anchors[n].con, dojo.disconnect, dojo); |
---|
| 645 | dojo.destroy(this._anchors[n].a); |
---|
| 646 | }; |
---|
| 647 | }, |
---|
| 648 | |
---|
| 649 | setSavedCaret: function(val){ |
---|
| 650 | // summary: |
---|
| 651 | // Internal, called when caret needs to |
---|
| 652 | // be moved into position after text is added |
---|
| 653 | this._caretStart = this._caretEnd = val; |
---|
| 654 | }, |
---|
| 655 | |
---|
| 656 | getSavedCaret: function(){ |
---|
| 657 | return {start: this._caretStart, end: this._caretEnd} |
---|
| 658 | }, |
---|
| 659 | |
---|
| 660 | insertText: function(node,val){ |
---|
| 661 | // summary: |
---|
| 662 | // Uses saved caret position to insert text |
---|
| 663 | // into position and place caret at the end of |
---|
| 664 | // insertion |
---|
| 665 | |
---|
| 666 | var t, text = node.innerHTML; |
---|
| 667 | var caret = this.getSavedCaret(); |
---|
| 668 | |
---|
| 669 | text = text.replace(/ /g, " "); |
---|
| 670 | t = text.substr(0,caret.start) + val + text.substr(caret.end); |
---|
| 671 | t = this.cleanText(t,true); |
---|
| 672 | this.setSavedCaret(Math.min(t.length,(caret.end + val.length))); |
---|
| 673 | node.innerHTML = t; |
---|
| 674 | this.setSelection(node,"stored"); |
---|
| 675 | }, |
---|
| 676 | |
---|
| 677 | getSelection: function(node){ |
---|
| 678 | // summary: |
---|
| 679 | // This gets and stores the caret position |
---|
| 680 | // in the contentEditable div (conEdit). |
---|
| 681 | // NOTE: Doesn't work with html nodes inside |
---|
| 682 | // the div. |
---|
| 683 | |
---|
| 684 | var start, end; |
---|
| 685 | if(dojo.doc.selection){ |
---|
| 686 | var r = dojo.doc.selection.createRange(); |
---|
| 687 | var rs = dojo.body().createTextRange(); |
---|
| 688 | rs.moveToElementText(node); |
---|
| 689 | var re = rs.duplicate(); |
---|
| 690 | rs.moveToBookmark(r.getBookmark()); |
---|
| 691 | re.setEndPoint('EndToStart', rs); |
---|
| 692 | start = this._caretStart = re.text.length; |
---|
| 693 | end = this._caretEnd = re.text.length+r.text.length; |
---|
| 694 | console.warn("Caret start: ",start," end: ",end," length: ",re.text.length," text: ",re.text); |
---|
| 695 | } else { |
---|
| 696 | this._caretStart = dojo.global.getSelection().getRangeAt(node).startOffset; |
---|
| 697 | this._caretEnd = dojo.global.getSelection().getRangeAt(node).endOffset; |
---|
| 698 | console.log("Caret start: ", this._caretStart," end: ", this._caretEnd); |
---|
| 699 | } |
---|
| 700 | }, |
---|
| 701 | |
---|
| 702 | setSelection: function(node, what){ |
---|
| 703 | // summary: |
---|
| 704 | // Used for placing the cursor during edits and character help. |
---|
| 705 | // Takes the values: end, beg, start, all or any numerical value |
---|
| 706 | // (in which case the number will constitute the caret position) |
---|
| 707 | |
---|
| 708 | console.warn("setSelection:"); |
---|
| 709 | if(dojo.doc.selection){ // IE |
---|
| 710 | var rs = dojo.body().createTextRange(); |
---|
| 711 | rs.moveToElementText(node); |
---|
| 712 | |
---|
| 713 | switch(what){ |
---|
| 714 | case "end": |
---|
| 715 | rs.collapse(false); |
---|
| 716 | break; |
---|
| 717 | case "beg" || "start": |
---|
| 718 | rs.collapse(); |
---|
| 719 | break; |
---|
| 720 | case "all": |
---|
| 721 | rs.collapse(); |
---|
| 722 | rs.moveStart("character", 0); |
---|
| 723 | rs.moveEnd("character",node.text.length); |
---|
| 724 | break; |
---|
| 725 | case "stored": |
---|
| 726 | rs.collapse(); |
---|
| 727 | var dif = this._caretStart-this._caretEnd; |
---|
| 728 | //console.log("start: ",this._caretStart, " end: ",this._caretEnd," dif: ",dif); |
---|
| 729 | rs.moveStart("character",this._caretStart); |
---|
| 730 | rs.moveEnd("character",dif); |
---|
| 731 | break; |
---|
| 732 | } |
---|
| 733 | rs.select(); |
---|
| 734 | |
---|
| 735 | }else{ |
---|
| 736 | var getAllChildren = function(node, children){ |
---|
| 737 | children = children || []; |
---|
| 738 | for(var i=0;i<node.childNodes.length; i++){ |
---|
| 739 | var n = node.childNodes[i]; |
---|
| 740 | if(n.nodeType==3){ |
---|
| 741 | children.push(n); |
---|
| 742 | }else if(n.tagName && n.tagName.toLowerCase()=="img"){ |
---|
| 743 | children.push(n); |
---|
| 744 | } |
---|
| 745 | |
---|
| 746 | if(n.childNodes && n.childNodes.length){ |
---|
| 747 | getAllChildren(n, children); |
---|
| 748 | } |
---|
| 749 | } |
---|
| 750 | return children; |
---|
| 751 | }; |
---|
| 752 | console.log("ff node:", node); |
---|
| 753 | node.focus(); |
---|
| 754 | var selection = dojo.global.getSelection(); |
---|
| 755 | selection.removeAllRanges(); |
---|
| 756 | var r = dojo.doc.createRange(); |
---|
| 757 | var nodes = getAllChildren(node); |
---|
| 758 | switch(what){ |
---|
| 759 | case "end": |
---|
| 760 | console.log("len:", nodes[nodes.length - 1].textContent.length); |
---|
| 761 | r.setStart(nodes[nodes.length - 1], nodes[nodes.length - 1].textContent.length); |
---|
| 762 | r.setEnd(nodes[nodes.length - 1], nodes[nodes.length - 1].textContent.length); |
---|
| 763 | break; |
---|
| 764 | case "beg" || "start": |
---|
| 765 | r.setStart(nodes[0], 0); |
---|
| 766 | r.setEnd(nodes[0], 0); |
---|
| 767 | break; |
---|
| 768 | case "all": |
---|
| 769 | r.setStart(nodes[0], 0); |
---|
| 770 | r.setEnd(nodes[nodes.length - 1], nodes[nodes.length - 1].textContent.length); |
---|
| 771 | break; |
---|
| 772 | case "stored": |
---|
| 773 | console.log("Caret start: ",this._caretStart," caret end: ",this._caretEnd); |
---|
| 774 | r.setStart(nodes[0], this._caretStart); |
---|
| 775 | r.setEnd(nodes[0], this._caretEnd); |
---|
| 776 | } |
---|
| 777 | selection.addRange(r); |
---|
| 778 | console.log("sel ", what, " on ", node); |
---|
| 779 | } |
---|
| 780 | } |
---|
| 781 | } |
---|
| 782 | ); |
---|
| 783 | dojo.setObject("dojox.drawing.tools.TextBlock", TextBlock); |
---|
| 784 | TextBlock.setup = { |
---|
| 785 | name:"dojox.drawing.tools.TextBlock", |
---|
| 786 | tooltip:"Text Tool", |
---|
| 787 | iconClass:"iconText" |
---|
| 788 | }; |
---|
| 789 | registry.register(TextBlock.setup, "tool"); |
---|
| 790 | |
---|
| 791 | return TextBlock; |
---|
| 792 | }); |
---|