1 /* 2 * Copyright (C) 2008, 2009 Mihai Şucan 3 * 4 * This file is part of PaintWeb. 5 * 6 * PaintWeb is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * PaintWeb is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with PaintWeb. If not, see <http://www.gnu.org/licenses/>. 18 * 19 * $URL: http://code.google.com/p/paintweb $ 20 * $Date: 2009-11-07 18:13:21 +0200 $ 21 */ 22 23 /** 24 * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a> 25 * @fileOverview The default PaintWeb interface code. 26 */ 27 28 /** 29 * @class The default PaintWeb interface. 30 * 31 * @param {PaintWeb} app Reference to the main paint application object. 32 */ 33 pwlib.gui = function (app) { 34 var _self = this, 35 config = app.config, 36 doc = app.doc, 37 lang = app.lang, 38 MathRound = Math.round, 39 pwlib = window.pwlib, 40 appEvent = pwlib.appEvent, 41 win = app.win; 42 43 this.app = app; 44 this.idPrefix = 'paintweb' + app.UID + '_', 45 this.classPrefix = 'paintweb_'; 46 47 /** 48 * Holds references to DOM elements. 49 * @type Object 50 */ 51 this.elems = {}; 52 53 /** 54 * Holds references to input elements associated to the PaintWeb configuration 55 * properties. 56 * @type Object 57 */ 58 this.inputs = {}; 59 60 /** 61 * Holds references to DOM elements associated to configuration values. 62 * @type Object 63 */ 64 this.inputValues = {}; 65 66 /** 67 * Holds references to DOM elements associated to color configuration 68 * properties. 69 * 70 * @type Object 71 * @see pwlib.guiColorInput 72 */ 73 this.colorInputs = {}; 74 75 /** 76 * Holds references to DOM elements associated to each tool registered in the 77 * current PaintWeb application instance. 78 * 79 * @private 80 * @type Object 81 */ 82 this.tools = {}; 83 84 /** 85 * Holds references to DOM elements associated to PaintWeb commands. 86 * 87 * @private 88 * @type Object 89 */ 90 this.commands = {}; 91 92 /** 93 * Holds references to floating panels GUI components. 94 * 95 * @type Object 96 * @see pwlib.guiFloatingPanel 97 */ 98 this.floatingPanels = {zIndex_: 0}; 99 100 /** 101 * Holds references to tab panel GUI components. 102 * 103 * @type Object 104 * @see pwlib.guiTabPanel 105 */ 106 this.tabPanels = {}; 107 108 /** 109 * Holds an instance of the guiResizer object attached to the Canvas. 110 * 111 * @private 112 * @type pwlib.guiResizer 113 */ 114 this.canvasResizer = null; 115 116 /** 117 * Holds an instance of the guiResizer object attached to the viewport 118 * element. 119 * 120 * @private 121 * @type pwlib.guiResizer 122 */ 123 this.viewportResizer = null; 124 125 /** 126 * Holds tab configuration information for most drawing tools. 127 * 128 * @private 129 * @type Object 130 */ 131 this.toolTabConfig = { 132 bcurve: { 133 lineTab: true, 134 shapeType: true, 135 lineWidth: true, 136 lineWidthLabel: lang.inputs.borderWidth, 137 lineCap: true 138 }, 139 ellipse: { 140 lineTab: true, 141 shapeType: true, 142 lineWidth: true, 143 lineWidthLabel: lang.inputs.borderWidth 144 }, 145 rectangle: { 146 lineTab: true, 147 shapeType: true, 148 lineWidth: true, 149 lineWidthLabel: lang.inputs.borderWidth, 150 lineJoin: true 151 }, 152 polygon: { 153 lineTab: true, 154 shapeType: true, 155 lineWidth: true, 156 lineWidthLabel: lang.inputs.borderWidth, 157 lineJoin: true, 158 lineCap: true, 159 miterLimit: true 160 }, 161 eraser: { 162 lineTab: true, 163 lineWidth: true, 164 lineWidthLabel: lang.inputs.eraserSize, 165 lineJoin: true, 166 lineCap: true, 167 miterLimit: true 168 }, 169 pencil: { 170 lineTab: true, 171 lineWidth: true, 172 lineWidthLabel: lang.inputs.pencilSize, 173 lineJoin: true, 174 lineCap: true, 175 miterLimit: true 176 }, 177 line: { 178 lineTab: true, 179 lineWidth: true, 180 lineWidthLabel: lang.inputs.line.lineWidth, 181 lineJoin: true, 182 lineCap: true, 183 miterLimit: true 184 }, 185 text: { 186 lineTab: true, 187 lineTabLabel: lang.tabs.main.textBorder, 188 shapeType: true, 189 lineWidth: true, 190 lineWidthLabel: lang.inputs.borderWidth 191 } 192 }; 193 194 /** 195 * Initialize the PaintWeb interface. 196 * 197 * @param {Document|String} markup The interface markup loaded and parsed as 198 * DOM Document object. Optionally, the value can be a string holding the 199 * interface markup (this is used when PaintWeb is packaged). 200 * 201 * @returns {Boolean} True if the initialization was successful, or false if 202 * not. 203 */ 204 this.init = function (markup) { 205 // Make sure the user nicely waits for PaintWeb to load, without seeing 206 // much. 207 var placeholder = config.guiPlaceholder, 208 placeholderStyle = placeholder.style; 209 210 placeholderStyle.display = 'none'; 211 placeholderStyle.height = '1px'; 212 placeholderStyle.overflow = 'hidden'; 213 placeholderStyle.position = 'absolute'; 214 placeholderStyle.visibility = 'hidden'; 215 216 placeholder.className += ' ' + this.classPrefix + 'placeholder'; 217 if (!placeholder.tabIndex || placeholder.tabIndex == -1) { 218 placeholder.tabIndex = 1; 219 } 220 221 if (!this.initImportDoc(markup)) { 222 app.initError(lang.guiMarkupImportFailed); 223 return false; 224 } 225 markup = null; 226 227 if (!this.initParseMarkup()) { 228 app.initError(lang.guiMarkupParseFailed); 229 return false; 230 } 231 232 if (!this.initCanvas() || 233 !this.initImageZoom() || 234 !this.initSelectionTool() || 235 !this.initTextTool() || 236 !this.initKeyboardShortcuts()) { 237 return false; 238 } 239 240 // Setup the main tabbed panel. 241 var panel = this.tabPanels.main; 242 if (!panel) { 243 app.initError(lang.noMainTabPanel); 244 return false; 245 } 246 247 // Hide the "Shadow" tab if the drawing of shadows is not supported. 248 if (!app.shadowSupported && 'shadow' in panel.tabs) { 249 panel.tabHide('shadow'); 250 } 251 252 if (!('viewport' in this.elems)) { 253 app.initError(lang.missingViewport); 254 return false; 255 } 256 257 // Setup the GUI dimensions . 258 this.elems.viewport.style.height = config.viewportHeight; 259 placeholderStyle.width = config.viewportWidth; 260 261 // Setup the Canvas resizer. 262 var resizeHandle = this.elems.canvasResizer; 263 if (!resizeHandle) { 264 app.initError(lang.missingCanvasResizer); 265 return false; 266 } 267 resizeHandle.title = lang.guiCanvasResizer; 268 resizeHandle.replaceChild(doc.createTextNode(resizeHandle.title), 269 resizeHandle.firstChild); 270 resizeHandle.addEventListener('mouseover', this.item_mouseover, false); 271 resizeHandle.addEventListener('mouseout', this.item_mouseout, false); 272 273 this.canvasResizer = new pwlib.guiResizer(this, resizeHandle, 274 this.elems.canvasContainer); 275 276 this.canvasResizer.events.add('guiResizeStart', this.canvasResizeStart); 277 this.canvasResizer.events.add('guiResizeEnd', this.canvasResizeEnd); 278 279 // Setup the viewport resizer. 280 var resizeHandle = this.elems.viewportResizer; 281 if (!resizeHandle) { 282 app.initError(lang.missingViewportResizer); 283 return false; 284 } 285 resizeHandle.title = lang.guiViewportResizer; 286 resizeHandle.replaceChild(doc.createTextNode(resizeHandle.title), 287 resizeHandle.firstChild); 288 resizeHandle.addEventListener('mouseover', this.item_mouseover, false); 289 resizeHandle.addEventListener('mouseout', this.item_mouseout, false); 290 291 this.viewportResizer = new pwlib.guiResizer(this, resizeHandle, 292 this.elems.viewport); 293 294 this.viewportResizer.dispatchMouseMove = true; 295 this.viewportResizer.events.add('guiResizeMouseMove', 296 this.viewportResizeMouseMove); 297 this.viewportResizer.events.add('guiResizeEnd', this.viewportResizeEnd); 298 299 if ('statusMessage' in this.elems) { 300 this.elems.statusMessage._prevText = false; 301 } 302 303 // Update the version string in Help. 304 if ('version' in this.elems) { 305 this.elems.version.appendChild(doc.createTextNode(app.toString())); 306 } 307 308 // Update the image dimensions in the GUI. 309 var imageSize = this.elems.imageSize; 310 if (imageSize) { 311 imageSize.replaceChild(doc.createTextNode(app.image.width + 'x' 312 + app.image.height), imageSize.firstChild); 313 } 314 315 // Add application-wide event listeners. 316 app.events.add('canvasSizeChange', this.canvasSizeChange); 317 app.events.add('commandRegister', this.commandRegister); 318 app.events.add('commandUnregister', this.commandUnregister); 319 app.events.add('configChange', this.configChangeHandler); 320 app.events.add('imageSizeChange', this.imageSizeChange); 321 app.events.add('imageZoom', this.imageZoom); 322 app.events.add('appInit', this.appInit); 323 app.events.add('shadowAllow', this.shadowAllow); 324 app.events.add('toolActivate', this.toolActivate); 325 app.events.add('toolRegister', this.toolRegister); 326 app.events.add('toolUnregister', this.toolUnregister); 327 328 // Make sure the historyUndo and historyRedo command elements are 329 // synchronized with the application history state. 330 if ('historyUndo' in this.commands && 'historyRedo' in this.commands) { 331 app.events.add('historyUpdate', this.historyUpdate); 332 } 333 334 app.commandRegister('about', this.commandAbout); 335 336 return true; 337 }; 338 339 /** 340 * Initialize the Canvas elements. 341 * 342 * @private 343 * @returns {Boolean} True if the initialization was successful, or false if 344 * not. 345 */ 346 this.initCanvas = function () { 347 var canvasContainer = this.elems.canvasContainer, 348 layerCanvas = app.layer.canvas, 349 layerContext = app.layer.context, 350 layerStyle = layerCanvas.style, 351 bufferCanvas = app.buffer.canvas; 352 353 if (!canvasContainer) { 354 app.initError(lang.missingCanvasContainer); 355 return false; 356 } 357 358 var containerStyle = canvasContainer.style; 359 360 canvasContainer.className = this.classPrefix + 'canvasContainer'; 361 layerCanvas.className = this.classPrefix + 'layerCanvas'; 362 bufferCanvas.className = this.classPrefix + 'bufferCanvas'; 363 364 containerStyle.width = layerStyle.width; 365 containerStyle.height = layerStyle.height; 366 if (!config.checkersBackground || pwlib.browser.olpcxo) { 367 containerStyle.backgroundImage = 'none'; 368 } 369 370 canvasContainer.appendChild(layerCanvas); 371 canvasContainer.appendChild(bufferCanvas); 372 373 // Make sure the selection transparency input checkbox is disabled if the 374 // putImageData and getImageData methods are unsupported. 375 if ('selection_transparent' in this.inputs && (!layerContext.putImageData || 376 !layerContext.getImageData)) { 377 this.inputs.selection_transparent.disabled = true; 378 this.inputs.selection_transparent.checked = true; 379 } 380 381 return true; 382 }; 383 384 /** 385 * Import the DOM nodes from the interface DOM document. All the nodes are 386 * inserted into the {@link PaintWeb.config.guiPlaceholder} element. 387 * 388 * <p>Elements which have the ID attribute will have the attribute renamed to 389 * <code>data-pwId</code>. 390 * 391 * <p>Input elements which have the ID attribute will have their attribute 392 * updated to be unique for the current PaintWeb instance. 393 * 394 * @private 395 * 396 * @param {Document|String} markup The source DOM document to import the nodes 397 * from. Optionally, this parameter can be a string which holds the interface 398 * markup. 399 * 400 * @returns {Boolean} True if the initialization was successful, or false if 401 * not. 402 */ 403 this.initImportDoc = function (markup) { 404 // I could use some XPath here, but for the sake of compatibility I don't. 405 var destElem = config.guiPlaceholder, 406 elType = Node.ELEMENT_NODE, 407 elem, root, nodes, n, tag, isInput; 408 409 if (typeof markup === 'string') { 410 elem = doc.createElement('div'); 411 elem.innerHTML = markup; 412 root = elem.firstChild; 413 } else { 414 root = markup.documentElement; 415 } 416 markup = null; 417 418 nodes = root.getElementsByTagName('*'); 419 n = nodes.length; 420 421 // Change all the id attributes to be data-pwId attributes. 422 // Input elements have their ID updated to be unique for the current 423 // PaintWeb instance. 424 for (var i = 0; i < n; i++) { 425 elem = nodes[i]; 426 if (elem.nodeType !== elType) { 427 continue; 428 } 429 tag = elem.tagName.toLowerCase(); 430 isInput = tag === 'input' || tag === 'select' || tag === 'textarea'; 431 432 if (elem.id) { 433 elem.setAttribute('data-pwId', elem.id); 434 435 if (isInput) { 436 elem.id = this.idPrefix + elem.id; 437 } else { 438 elem.removeAttribute('id'); 439 } 440 } 441 442 // label elements have their "for" attribute updated as well. 443 if (tag === 'label' && elem.htmlFor) { 444 elem.htmlFor = this.idPrefix + elem.htmlFor; 445 } 446 } 447 448 // Import all the nodes. 449 n = root.childNodes.length; 450 for (var i = 0; i < n; i++) { 451 destElem.appendChild(doc.importNode(root.childNodes[i], true)); 452 } 453 454 return true; 455 }; 456 457 /** 458 * Parse the interface markup. The layout file can have custom 459 * PaintWeb-specific attributes. 460 * 461 * <p>Elements with the <code>data-pwId</code> attribute are added to the 462 * {@link pwlib.gui#elems} object. 463 * 464 * <p>Elements having the <code>data-pwCommand</code> attribute are added to 465 * the {@link pwlib.gui#commands} object. 466 * 467 * <p>Elements having the <code>data-pwTool</code> attribute are added to the 468 * {@link pwlib.gui#tools} object. 469 * 470 * <p>Elements having the <code>data-pwTabPanel</code> attribute are added to 471 * the {@link pwlib.gui#tabPanels} object. These become interactive GUI 472 * components (see {@link pwlib.guiTabPanel}). 473 * 474 * <p>Elements having the <code>data-pwFloatingPanel</code> attribute are 475 * added to the {@link pwlib.gui#floatingPanels} object. These become 476 * interactive GUI components (see {@link pwlib.guiFloatingPanel}). 477 * 478 * <p>Elements having the <code>data-pwConfig</code> attribute are added to 479 * the {@link pwlib.gui#inputs} object. These become interactive GUI 480 * components which allow users to change configuration options. 481 * 482 * <p>Elements having the <code>data-pwConfigValue</code> attribute are added 483 * to the {@link pwlib.gui#inputValues} object. These can only be child nodes 484 * of elements which have the <code>data-pwConfig</code> attribute. Each such 485 * element is considered an icon. Anchor elements are appended to ensure 486 * keyboard accessibility. 487 * 488 * <p>Elements having the <code>data-pwConfigToggle</code> attribute are added 489 * to the {@link pwlib.gui#inputs} object. These become interactive GUI 490 * components which toggle the boolean value of the configuration property 491 * they are associated to. 492 * 493 * <p>Elements having the <code>data-pwColorInput</code> attribute are added 494 * to the {@link pwlib.gui#colorInputs} object. These become color picker 495 * inputs which are associated to the configuration property given as the 496 * attribute value. (see {@link pwlib.guiColorInput}) 497 * 498 * @returns {Boolean} True if the parsing was successful, or false if not. 499 */ 500 this.initParseMarkup = function () { 501 var nodes = config.guiPlaceholder.getElementsByTagName('*'), 502 elType = Node.ELEMENT_NODE, 503 elem, tag, isInput, tool, tabPanel, floatingPanel, cmd, id, cfgAttr, 504 colorInput; 505 506 // Store references to important elements and parse PaintWeb-specific 507 // attributes. 508 for (var i = 0; i < nodes.length; i++) { 509 elem = nodes[i]; 510 if (elem.nodeType !== elType) { 511 continue; 512 } 513 tag = elem.tagName.toLowerCase(); 514 isInput = tag === 'input' || tag === 'select' || tag === 'textarea'; 515 516 // Store references to commands. 517 cmd = elem.getAttribute('data-pwCommand'); 518 if (cmd && !(cmd in this.commands)) { 519 elem.className += ' ' + this.classPrefix + 'command'; 520 this.commands[cmd] = elem; 521 } 522 523 // Store references to tools. 524 tool = elem.getAttribute('data-pwTool'); 525 if (tool && !(tool in this.tools)) { 526 elem.className += ' ' + this.classPrefix + 'tool'; 527 this.tools[tool] = elem; 528 } 529 530 // Create tab panels. 531 tabPanel = elem.getAttribute('data-pwTabPanel'); 532 if (tabPanel) { 533 this.tabPanels[tabPanel] = new pwlib.guiTabPanel(this, elem); 534 } 535 536 // Create floating panels. 537 floatingPanel = elem.getAttribute('data-pwFloatingPanel'); 538 if (floatingPanel) { 539 this.floatingPanels[floatingPanel] = new pwlib.guiFloatingPanel(this, 540 elem); 541 } 542 543 cfgAttr = elem.getAttribute('data-pwConfig'); 544 if (cfgAttr) { 545 if (isInput) { 546 this.initConfigInput(elem, cfgAttr); 547 } else { 548 this.initConfigIcons(elem, cfgAttr); 549 } 550 } 551 552 cfgAttr = elem.getAttribute('data-pwConfigToggle'); 553 if (cfgAttr) { 554 this.initConfigToggle(elem, cfgAttr); 555 } 556 557 // elem.hasAttribute() fails in webkit (tested with chrome and safari 4) 558 if (elem.getAttribute('data-pwColorInput')) { 559 colorInput = new pwlib.guiColorInput(this, elem); 560 this.colorInputs[colorInput.id] = colorInput; 561 } 562 563 id = elem.getAttribute('data-pwId'); 564 if (id) { 565 elem.className += ' ' + this.classPrefix + id; 566 567 // Store a reference to the element. 568 if (isInput && !cfgAttr) { 569 this.inputs[id] = elem; 570 } else if (!isInput) { 571 this.elems[id] = elem; 572 } 573 } 574 } 575 576 return true; 577 }; 578 579 /** 580 * Initialize an input element associated to a configuration property. 581 * 582 * @private 583 * 584 * @param {Element} elem The DOM element which is associated to the 585 * configuration property. 586 * 587 * @param {String} cfgAttr The configuration attribute. This tells the 588 * configuration group and property to which the DOM element is attached to. 589 */ 590 this.initConfigInput = function (input, cfgAttr) { 591 var cfgNoDots = cfgAttr.replace('.', '_'), 592 cfgArray = cfgAttr.split('.'), 593 cfgProp = cfgArray.pop(), 594 cfgGroup = cfgArray.join('.'), 595 cfgGroupRef = config, 596 langGroup = lang.inputs, 597 labelElem = input.parentNode; 598 599 for (var i = 0, n = cfgArray.length; i < n; i++) { 600 cfgGroupRef = cfgGroupRef[cfgArray[i]]; 601 langGroup = langGroup[cfgArray[i]]; 602 } 603 604 input._pwConfigProperty = cfgProp; 605 input._pwConfigGroup = cfgGroup; 606 input._pwConfigGroupRef = cfgGroupRef; 607 input.title = langGroup[cfgProp + 'Title'] || langGroup[cfgProp]; 608 input.className += ' ' + this.classPrefix + 'cfg_' + cfgNoDots; 609 610 this.inputs[cfgNoDots] = input; 611 612 if (labelElem.tagName.toLowerCase() !== 'label') { 613 labelElem = labelElem.getElementsByTagName('label')[0]; 614 } 615 616 if (input.type === 'checkbox' || labelElem.htmlFor) { 617 labelElem.replaceChild(doc.createTextNode(langGroup[cfgProp]), 618 labelElem.lastChild); 619 } else { 620 labelElem.replaceChild(doc.createTextNode(langGroup[cfgProp]), 621 labelElem.firstChild); 622 } 623 624 if (input.type === 'checkbox') { 625 input.checked = cfgGroupRef[cfgProp]; 626 } else { 627 input.value = cfgGroupRef[cfgProp]; 628 } 629 630 input.addEventListener('input', this.configInputChange, false); 631 input.addEventListener('change', this.configInputChange, false); 632 }; 633 634 /** 635 * Initialize an HTML element associated to a configuration property, and all 636 * of its own sub-elements associated to configuration values. Each element 637 * that has the <var>data-pwConfigValue</var> attribute is considered an icon. 638 * 639 * @private 640 * 641 * @param {Element} elem The DOM element which is associated to the 642 * configuration property. 643 * 644 * @param {String} cfgAttr The configuration attribute. This tells the 645 * configuration group and property to which the DOM element is attached to. 646 */ 647 this.initConfigIcons = function (input, cfgAttr) { 648 var cfgNoDots = cfgAttr.replace('.', '_'), 649 cfgArray = cfgAttr.split('.'), 650 cfgProp = cfgArray.pop(), 651 cfgGroup = cfgArray.join('.'), 652 cfgGroupRef = config, 653 langGroup = lang.inputs; 654 655 for (var i = 0, n = cfgArray.length; i < n; i++) { 656 cfgGroupRef = cfgGroupRef[cfgArray[i]]; 657 langGroup = langGroup[cfgArray[i]]; 658 } 659 660 input._pwConfigProperty = cfgProp; 661 input._pwConfigGroup = cfgGroup; 662 input._pwConfigGroupRef = cfgGroupRef; 663 input.title = langGroup[cfgProp + 'Title'] || langGroup[cfgProp]; 664 input.className += ' ' + this.classPrefix + 'cfg_' + cfgNoDots; 665 666 this.inputs[cfgNoDots] = input; 667 668 var labelElem = input.getElementsByTagName('p')[0]; 669 labelElem.replaceChild(doc.createTextNode(langGroup[cfgProp]), 670 labelElem.firstChild); 671 672 var elem, anchor, val, 673 className = ' ' + this.classPrefix + 'configActive'; 674 nodes = input.getElementsByTagName('*'), 675 elType = Node.ELEMENT_NODE; 676 677 for (var i = 0; i < nodes.length; i++) { 678 elem = nodes[i]; 679 if (elem.nodeType !== elType) { 680 continue; 681 } 682 683 val = elem.getAttribute('data-pwConfigValue'); 684 if (!val) { 685 continue; 686 } 687 688 anchor = doc.createElement('a'); 689 anchor.href = '#'; 690 anchor.title = langGroup[cfgProp + '_' + val]; 691 anchor.appendChild(doc.createTextNode(anchor.title)); 692 693 elem.className += ' ' + this.classPrefix + cfgProp + '_' + val 694 + ' ' + this.classPrefix + 'icon'; 695 elem._pwConfigParent = input; 696 697 if (cfgGroupRef[cfgProp] == val) { 698 elem.className += className; 699 } 700 701 anchor.addEventListener('click', this.configValueClick, false); 702 anchor.addEventListener('mouseover', this.item_mouseover, false); 703 anchor.addEventListener('mouseout', this.item_mouseout, false); 704 705 elem.replaceChild(anchor, elem.firstChild); 706 707 this.inputValues[cfgGroup + '_' + cfgProp + '_' + val] = elem; 708 } 709 }; 710 711 /** 712 * Initialize an HTML element associated to a boolean configuration property. 713 * 714 * @private 715 * 716 * @param {Element} elem The DOM element which is associated to the 717 * configuration property. 718 * 719 * @param {String} cfgAttr The configuration attribute. This tells the 720 * configuration group and property to which the DOM element is attached to. 721 */ 722 this.initConfigToggle = function (input, cfgAttr) { 723 var cfgNoDots = cfgAttr.replace('.', '_'), 724 cfgArray = cfgAttr.split('.'), 725 cfgProp = cfgArray.pop(), 726 cfgGroup = cfgArray.join('.'), 727 cfgGroupRef = config, 728 langGroup = lang.inputs; 729 730 for (var i = 0, n = cfgArray.length; i < n; i++) { 731 cfgGroupRef = cfgGroupRef[cfgArray[i]]; 732 langGroup = langGroup[cfgArray[i]]; 733 } 734 735 input._pwConfigProperty = cfgProp; 736 input._pwConfigGroup = cfgGroup; 737 input._pwConfigGroupRef = cfgGroupRef; 738 input.className += ' ' + this.classPrefix + 'cfg_' + cfgNoDots 739 + ' ' + this.classPrefix + 'icon'; 740 741 if (cfgGroupRef[cfgProp]) { 742 input.className += ' ' + this.classPrefix + 'configActive'; 743 } 744 745 var anchor = doc.createElement('a'); 746 anchor.href = '#'; 747 anchor.title = langGroup[cfgProp + 'Title'] || langGroup[cfgProp]; 748 anchor.appendChild(doc.createTextNode(langGroup[cfgProp])); 749 750 anchor.addEventListener('click', this.configToggleClick, false); 751 anchor.addEventListener('mouseover', this.item_mouseover, false); 752 anchor.addEventListener('mouseout', this.item_mouseout, false); 753 754 input.replaceChild(anchor, input.firstChild); 755 756 this.inputs[cfgNoDots] = input; 757 }; 758 759 /** 760 * Initialize the image zoom input. 761 * 762 * @private 763 * @returns {Boolean} True if the initialization was successful, or false if 764 * not. 765 */ 766 this.initImageZoom = function () { 767 var input = this.inputs.imageZoom; 768 if (!input) { 769 return true; // allow layouts without the zoom input 770 } 771 772 input.value = 100; 773 input._old_value = 100; 774 775 // Override the attributes, based on the settings. 776 input.setAttribute('step', config.imageZoomStep * 100); 777 input.setAttribute('max', config.imageZoomMax * 100); 778 input.setAttribute('min', config.imageZoomMin * 100); 779 780 var changeFn = function () { 781 app.imageZoomTo(parseInt(this.value) / 100); 782 }; 783 784 input.addEventListener('change', changeFn, false); 785 input.addEventListener('input', changeFn, false); 786 787 // Update some language strings 788 789 var label = input.parentNode; 790 if (label.tagName.toLowerCase() === 'label') { 791 label.replaceChild(doc.createTextNode(lang.imageZoomLabel), 792 label.firstChild); 793 } 794 795 var elem = this.elems.statusZoom; 796 if (!elem) { 797 return true; 798 } 799 800 elem.title = lang.imageZoomTitle; 801 802 return true; 803 }; 804 805 /** 806 * Initialize GUI elements associated to selection tool options and commands. 807 * 808 * @private 809 * @returns {Boolean} True if the initialization was successful, or false if 810 * not. 811 */ 812 this.initSelectionTool = function () { 813 var classDisabled = ' ' + this.classPrefix + 'disabled', 814 cut = this.commands.selectionCut, 815 copy = this.commands.selectionCopy, 816 paste = this.commands.clipboardPaste; 817 818 if (paste) { 819 app.events.add('clipboardUpdate', this.clipboardUpdate); 820 paste.className += classDisabled; 821 822 } 823 824 if (cut && copy) { 825 app.events.add('selectionChange', this.selectionChange); 826 cut.className += classDisabled; 827 copy.className += classDisabled; 828 } 829 830 var selTab_cmds = ['selectionCut', 'selectionCopy', 'clipboardPaste'], 831 anchor, elem, cmd; 832 833 for (var i = 0, n = selTab_cmds.length; i < n; i++) { 834 cmd = selTab_cmds[i]; 835 elem = this.elems['selTab_' + cmd]; 836 if (!elem) { 837 continue; 838 } 839 840 anchor = doc.createElement('a'); 841 anchor.title = lang.commands[cmd]; 842 anchor.href = '#'; 843 anchor.appendChild(doc.createTextNode(anchor.title)); 844 anchor.addEventListener('click', this.commandClick, false); 845 846 elem.className += classDisabled + ' ' + this.classPrefix + 'command' 847 + ' ' + this.classPrefix + 'cmd_' + cmd; 848 elem.setAttribute('data-pwCommand', cmd); 849 elem.replaceChild(anchor, elem.firstChild); 850 } 851 852 var selCrop = this.commands.selectionCrop, 853 selFill = this.commands.selectionFill, 854 selDelete = this.commands.selectionDelete; 855 856 selCrop.className += classDisabled; 857 selFill.className += classDisabled; 858 selDelete.className += classDisabled; 859 860 return true; 861 }; 862 863 /** 864 * Initialize GUI elements associated to text tool options. 865 * 866 * @private 867 * @returns {Boolean} True if the initialization was successful, or false if 868 * not. 869 */ 870 this.initTextTool = function () { 871 if ('textString' in this.inputs) { 872 this.inputs.textString.value = lang.inputs.text.textString_value; 873 } 874 875 if (!('text_fontFamily' in this.inputs) || !('text' in config) || 876 !('fontFamilies' in config.text)) { 877 return true; 878 } 879 880 var option, input = this.inputs.text_fontFamily; 881 for (var i = 0, n = config.text.fontFamilies.length; i < n; i++) { 882 option = doc.createElement('option'); 883 option.value = config.text.fontFamilies[i]; 884 option.appendChild(doc.createTextNode(option.value)); 885 input.appendChild(option); 886 887 if (option.value === config.text.fontFamily) { 888 input.selectedIndex = i; 889 input.value = option.value; 890 } 891 } 892 893 option = doc.createElement('option'); 894 option.value = '+'; 895 option.appendChild(doc.createTextNode(lang.inputs.text.fontFamily_add)); 896 input.appendChild(option); 897 898 return true; 899 }; 900 901 /** 902 * Initialize the keyboard shortcuts. Basically, this updates various strings 903 * to ensure the user interface is informational. 904 * 905 * @private 906 * @returns {Boolean} True if the initialization was successful, or false if 907 * not. 908 */ 909 this.initKeyboardShortcuts = function () { 910 var kid = null, kobj = null; 911 912 for (kid in config.keys) { 913 kobj = config.keys[kid]; 914 915 if ('toolActivate' in kobj && kobj.toolActivate in lang.tools) { 916 lang.tools[kobj.toolActivate] += ' [ ' + kid + ' ]'; 917 } 918 919 if ('command' in kobj && kobj.command in lang.commands) { 920 lang.commands[kobj.command] += ' [ ' + kid + ' ]'; 921 } 922 } 923 924 return true; 925 }; 926 927 /** 928 * The <code>appInit</code> event handler. This method is invoked once 929 * PaintWeb completes all the loading. 930 * 931 * <p>This method dispatches the {@link pwlib.appEvent.guiShow} application 932 * event. 933 * 934 * @private 935 * @param {pwlib.appEvent.appInit} ev The application event object. 936 */ 937 this.appInit = function (ev) { 938 // Initialization was not successful ... 939 if (ev.state !== PaintWeb.INIT_DONE) { 940 return; 941 } 942 943 // Make sure the Hand tool is enabled/disabled as needed. 944 if ('hand' in _self.tools) { 945 app.events.add('canvasSizeChange', _self.toolHandStateChange); 946 app.events.add('viewportSizeChange', _self.toolHandStateChange); 947 _self.toolHandStateChange(ev); 948 } 949 950 // Make PaintWeb visible. 951 var placeholder = config.guiPlaceholder, 952 placeholderStyle = placeholder.style; 953 954 // We do not reset the display property. We leave this for the stylesheet. 955 placeholderStyle.height = ''; 956 placeholderStyle.overflow = ''; 957 placeholderStyle.position = ''; 958 placeholderStyle.visibility = ''; 959 960 var cs = win.getComputedStyle(placeholder, null); 961 962 // Do not allow the static positioning for the PaintWeb placeholder. 963 // Usually, the GUI requires absolute/relative positioning. 964 if (cs.position === 'static') { 965 placeholderStyle.position = 'relative'; 966 } 967 968 placeholder.focus(); 969 970 app.events.dispatch(new appEvent.guiShow()); 971 }; 972 973 /** 974 * The <code>guiResizeStart</code> event handler for the Canvas resize 975 * operation. 976 * @private 977 */ 978 this.canvasResizeStart = function () { 979 this.resizeHandle.style.visibility = 'hidden'; 980 981 // ugly... 982 this.timeout_ = setTimeout(function () { 983 _self.statusShow('guiCanvasResizerActive', true); 984 clearTimeout(_self.canvasResizer.timeout_); 985 delete _self.canvasResizer.timeout_; 986 }, 400); 987 }; 988 989 /** 990 * The <code>guiResizeEnd</code> event handler for the Canvas resize 991 * operation. 992 * 993 * @private 994 * @param {pwlib.appEvent.guiResizeEnd} ev The application event object. 995 */ 996 this.canvasResizeEnd = function (ev) { 997 this.resizeHandle.style.visibility = ''; 998 999 app.imageCrop(0, 0, MathRound(ev.width / app.image.canvasScale), 1000 MathRound(ev.height / app.image.canvasScale)); 1001 1002 if (this.timeout_) { 1003 clearTimeout(this.timeout_); 1004 delete this.timeout_; 1005 } else { 1006 _self.statusShow(-1); 1007 } 1008 }; 1009 1010 /** 1011 * The <code>guiResizeMouseMove</code> event handler for the viewport resize 1012 * operation. 1013 * 1014 * @private 1015 * @param {pwlib.appEvent.guiResizeMouseMove} ev The application event object. 1016 */ 1017 this.viewportResizeMouseMove = function (ev) { 1018 config.guiPlaceholder.style.width = ev.width + 'px'; 1019 }; 1020 1021 /** 1022 * The <code>guiResizeEnd</code> event handler for the viewport resize 1023 * operation. 1024 * 1025 * @private 1026 * @param {pwlib.appEvent.guiResizeEnd} ev The application event object. 1027 */ 1028 this.viewportResizeEnd = function (ev) { 1029 _self.elems.viewport.style.width = ''; 1030 _self.resizeTo(ev.width + 'px', ev.height + 'px'); 1031 config.guiPlaceholder.focus(); 1032 }; 1033 1034 /** 1035 * The <code>mouseover</code> event handler for all tools, commands and icons. 1036 * This simply shows the title / text content of the element in the GUI status 1037 * bar. 1038 * 1039 * @see pwlib.gui#statusShow The method used for displaying the message in the 1040 * GUI status bar. 1041 */ 1042 this.item_mouseover = function () { 1043 if (this.title || this.textConent) { 1044 _self.statusShow(this.title || this.textContent, true); 1045 } 1046 }; 1047 1048 /** 1049 * The <code>mouseout</code> event handler for all tools, commands and icons. 1050 * This method simply resets the GUI status bar to the previous message it was 1051 * displaying before the user hovered the current element. 1052 * 1053 * @see pwlib.gui#statusShow The method used for displaying the message in the 1054 * GUI status bar. 1055 */ 1056 this.item_mouseout = function () { 1057 _self.statusShow(-1); 1058 }; 1059 1060 /** 1061 * Show a message in the status bar. 1062 * 1063 * @param {String|Number} msg The message ID you want to display. The ID 1064 * should be available in the {@link PaintWeb.lang.status} object. If the 1065 * value is -1 then the previous non-temporary message will be displayed. If 1066 * the ID is not available in the language file, then the string is shown 1067 * as-is. 1068 * 1069 * @param {Boolean} [temporary=false] Tells if the message is temporary or 1070 * not. 1071 */ 1072 this.statusShow = function (msg, temporary) { 1073 var elem = this.elems.statusMessage; 1074 if (msg === -1 && elem._prevText === false) { 1075 return false; 1076 } 1077 1078 if (msg === -1) { 1079 msg = elem._prevText; 1080 } 1081 1082 if (msg in lang.status) { 1083 msg = lang.status[msg]; 1084 } 1085 1086 if (!temporary) { 1087 elem._prevText = msg; 1088 } 1089 1090 if (elem.firstChild) { 1091 elem.removeChild(elem.firstChild); 1092 } 1093 1094 win.status = msg; 1095 1096 if (msg) { 1097 elem.appendChild(doc.createTextNode(msg)); 1098 } 1099 }; 1100 1101 /** 1102 * The "About" command. This method displays the "About" panel. 1103 */ 1104 this.commandAbout = function () { 1105 _self.floatingPanels.about.toggle(); 1106 }; 1107 1108 /** 1109 * The <code>click</code> event handler for the tool DOM elements. 1110 * 1111 * @private 1112 * 1113 * @param {Event} ev The DOM Event object. 1114 * 1115 * @see PaintWeb#toolActivate to activate a drawing tool. 1116 */ 1117 this.toolClick = function (ev) { 1118 app.toolActivate(this.parentNode.getAttribute('data-pwTool'), ev); 1119 ev.preventDefault(); 1120 }; 1121 1122 /** 1123 * The <code>toolActivate</code> application event handler. This method 1124 * provides visual feedback for the activation of a new drawing tool. 1125 * 1126 * @private 1127 * 1128 * @param {pwlib.appEvent.toolActivate} ev The application event object. 1129 * 1130 * @see PaintWeb#toolActivate the method which allows you to activate 1131 * a drawing tool. 1132 */ 1133 this.toolActivate = function (ev) { 1134 var tabAnchor, 1135 tabActive = _self.tools[ev.id], 1136 tabConfig = _self.toolTabConfig[ev.id] || {}, 1137 tabPanel = _self.tabPanels.main, 1138 lineTab = tabPanel.tabs.line, 1139 shapeType = _self.inputs.shapeType, 1140 lineWidth = _self.inputs.line_lineWidth, 1141 lineCap = _self.inputs.line_lineCap, 1142 lineJoin = _self.inputs.line_lineJoin, 1143 miterLimit = _self.inputs.line_miterLimit, 1144 lineWidthLabel = null; 1145 1146 tabActive.className += ' ' + _self.classPrefix + 'toolActive'; 1147 tabActive.firstChild.focus(); 1148 1149 if ((ev.id + 'Active') in lang.status) { 1150 _self.statusShow(ev.id + 'Active'); 1151 } 1152 1153 // show/hide the shapeType input config. 1154 if (shapeType) { 1155 if (tabConfig.shapeType) { 1156 shapeType.style.display = ''; 1157 } else { 1158 shapeType.style.display = 'none'; 1159 } 1160 } 1161 1162 if (ev.prevId) { 1163 var prevTab = _self.tools[ev.prevId], 1164 prevTabConfig = _self.toolTabConfig[ev.prevId] || {}; 1165 1166 prevTab.className = prevTab.className. 1167 replace(' ' + _self.classPrefix + 'toolActive', ''); 1168 1169 // hide the line tab 1170 if (prevTabConfig.lineTab && lineTab) { 1171 tabPanel.tabHide('line'); 1172 lineTab.container.className = lineTab.container.className. 1173 replace(' ' + _self.classPrefix + 'main_line_' + ev.prevId, 1174 ' ' + _self.classPrefix + 'main_line'); 1175 } 1176 1177 // hide the tab for the current tool. 1178 if (ev.prevId in tabPanel.tabs) { 1179 tabPanel.tabHide(ev.prevId); 1180 } 1181 } 1182 1183 // Change the label of the lineWidth input element. 1184 if (tabConfig.lineWidthLabel) { 1185 lineWidthLabel = lineWidth.parentNode; 1186 lineWidthLabel.replaceChild(doc.createTextNode(tabConfig.lineWidthLabel), 1187 lineWidthLabel.firstChild); 1188 1189 } 1190 1191 if (lineJoin) { 1192 if (tabConfig.lineJoin) { 1193 lineJoin.style.display = ''; 1194 } else { 1195 lineJoin.style.display = 'none'; 1196 } 1197 } 1198 1199 if (lineCap) { 1200 if (tabConfig.lineCap) { 1201 lineCap.style.display = ''; 1202 } else { 1203 lineCap.style.display = 'none'; 1204 } 1205 } 1206 1207 if (miterLimit) { 1208 if (tabConfig.miterLimit) { 1209 miterLimit.parentNode.parentNode.style.display = ''; 1210 } else { 1211 miterLimit.parentNode.parentNode.style.display = 'none'; 1212 } 1213 } 1214 1215 if (lineWidth) { 1216 if (tabConfig.lineWidth) { 1217 lineWidth.parentNode.parentNode.style.display = ''; 1218 } else { 1219 lineWidth.parentNode.parentNode.style.display = 'none'; 1220 } 1221 } 1222 1223 // show the line tab, if configured 1224 if (tabConfig.lineTab && 'line' in tabPanel.tabs) { 1225 tabAnchor = lineTab.button.firstChild; 1226 tabAnchor.title = tabConfig.lineTabLabel || lang.tabs.main[ev.id]; 1227 tabAnchor.replaceChild(doc.createTextNode(tabAnchor.title), 1228 tabAnchor.firstChild); 1229 1230 if (ev.id !== 'line') { 1231 lineTab.container.className = lineTab.container.className. 1232 replace(' ' + _self.classPrefix + 'main_line', ' ' + _self.classPrefix 1233 + 'main_line_' + ev.id); 1234 } 1235 1236 tabPanel.tabShow('line'); 1237 } 1238 1239 // show the tab for the current tool, if there's one. 1240 if (ev.id in tabPanel.tabs) { 1241 tabPanel.tabShow(ev.id); 1242 } 1243 }; 1244 1245 /** 1246 * The <code>toolRegister</code> application event handler. This method adds 1247 * the new tool into the GUI. 1248 * 1249 * @private 1250 * 1251 * @param {pwlib.appEvent.toolRegister} ev The application event object. 1252 * 1253 * @see PaintWeb#toolRegister the method which allows you to register new 1254 * tools. 1255 */ 1256 this.toolRegister = function (ev) { 1257 var attr = null, elem = null, anchor = null; 1258 1259 if (ev.id in _self.tools) { 1260 elem = _self.tools[ev.id]; 1261 attr = elem.getAttribute('data-pwTool'); 1262 if (attr && attr !== ev.id) { 1263 attr = null; 1264 elem = null; 1265 delete _self.tools[ev.id]; 1266 } 1267 } 1268 1269 // Create a new element if there's none already associated to the tool ID. 1270 if (!elem) { 1271 elem = doc.createElement('li'); 1272 } 1273 1274 if (!attr) { 1275 elem.setAttribute('data-pwTool', ev.id); 1276 } 1277 1278 elem.className += ' ' + _self.classPrefix + 'tool_' + ev.id; 1279 1280 // Append an anchor element which holds the locale string. 1281 anchor = doc.createElement('a'); 1282 anchor.title = lang.tools[ev.id]; 1283 anchor.href = '#'; 1284 anchor.appendChild(doc.createTextNode(anchor.title)); 1285 1286 if (elem.firstChild) { 1287 elem.replaceChild(anchor, elem.firstChild); 1288 } else { 1289 elem.appendChild(anchor); 1290 } 1291 1292 anchor.addEventListener('click', _self.toolClick, false); 1293 anchor.addEventListener('mouseover', _self.item_mouseover, false); 1294 anchor.addEventListener('mouseout', _self.item_mouseout, false); 1295 1296 if (!(ev.id in _self.tools)) { 1297 _self.tools[ev.id] = elem; 1298 _self.elems.tools.appendChild(elem); 1299 } 1300 1301 // Disable the text tool icon if the Canvas Text API is not supported. 1302 if (ev.id === 'text' && !app.layer.context.fillText && 1303 !app.layer.context.mozPathText && elem) { 1304 elem.className += ' ' + _self.classPrefix + 'disabled'; 1305 anchor.title = lang.tools.textUnsupported; 1306 1307 anchor.removeEventListener('click', _self.toolClick, false); 1308 anchor.addEventListener('click', function (ev) { 1309 ev.preventDefault(); 1310 }, false); 1311 } 1312 }; 1313 1314 /** 1315 * The <code>toolUnregister</code> application event handler. This method the 1316 * tool element from the GUI. 1317 * 1318 * @param {pwlib.appEvent.toolUnregister} ev The application event object. 1319 * 1320 * @see PaintWeb#toolUnregister the method which allows you to unregister 1321 * tools. 1322 */ 1323 this.toolUnregister = function (ev) { 1324 if (ev.id in _self.tools) { 1325 _self.elems.tools.removeChild(_self.tools[ev.id]); 1326 delete _self.tools[ev.id]; 1327 } else { 1328 return; 1329 } 1330 }; 1331 1332 /** 1333 * The <code>click</code> event handler for the command DOM elements. 1334 * 1335 * @private 1336 * 1337 * @param {Event} ev The DOM Event object. 1338 * 1339 * @see PaintWeb#commandRegister to register a new command. 1340 */ 1341 this.commandClick = function (ev) { 1342 var cmd = this.parentNode.getAttribute('data-pwCommand'); 1343 if (cmd && cmd in app.commands) { 1344 app.commands[cmd].call(this, ev); 1345 } 1346 ev.preventDefault(); 1347 this.focus(); 1348 }; 1349 1350 /** 1351 * The <code>commandRegister</code> application event handler. GUI elements 1352 * associated to commands are updated to ensure proper user interaction. 1353 * 1354 * @private 1355 * 1356 * @param {pwlib.appEvent.commandRegister} ev The application event object. 1357 * 1358 * @see PaintWeb#commandRegister the method which allows you to register new 1359 * commands. 1360 */ 1361 this.commandRegister = function (ev) { 1362 var elem = _self.commands[ev.id], 1363 anchor = null; 1364 if (!elem) { 1365 return; 1366 } 1367 1368 elem.className += ' ' + _self.classPrefix + 'cmd_' + ev.id; 1369 1370 anchor = doc.createElement('a'); 1371 anchor.title = lang.commands[ev.id]; 1372 anchor.href = '#'; 1373 anchor.appendChild(doc.createTextNode(anchor.title)); 1374 1375 // Remove the text content and append the locale string associated to 1376 // current command inside an anchor element (for better keyboard 1377 // accessibility). 1378 if (elem.firstChild) { 1379 elem.removeChild(elem.firstChild); 1380 } 1381 elem.appendChild(anchor); 1382 1383 anchor.addEventListener('click', _self.commandClick, false); 1384 anchor.addEventListener('mouseover', _self.item_mouseover, false); 1385 anchor.addEventListener('mouseout', _self.item_mouseout, false); 1386 }; 1387 1388 /** 1389 * The <code>commandUnregister</code> application event handler. This method 1390 * simply removes all the user interactivity from the GUI element associated 1391 * to the command being unregistered. 1392 * 1393 * @private 1394 * 1395 * @param {pwlib.appEvent.commandUnregister} ev The application event object. 1396 * 1397 * @see PaintWeb#commandUnregister the method which allows you to unregister 1398 * commands. 1399 */ 1400 this.commandUnregister = function (ev) { 1401 var elem = _self.commands[ev.id], 1402 anchor = null; 1403 if (!elem) { 1404 return; 1405 } 1406 1407 elem.className = elem.className.replace(' ' + _self.classPrefix + 'cmd_' 1408 + ev.id, ''); 1409 1410 anchor = elem.firstChild; 1411 anchor.removeEventListener('click', this.commands[ev.id], false); 1412 anchor.removeEventListener('mouseover', _self.item_mouseover, false); 1413 anchor.removeEventListener('mouseout', _self.item_mouseout, false); 1414 1415 elem.removeChild(anchor); 1416 }; 1417 1418 /** 1419 * The <code>historyUpdate</code> application event handler. GUI elements 1420 * associated to the <code>historyUndo</code> and to the 1421 * <code>historyRedo</code> commands are updated such that they are either 1422 * enabled or disabled, depending on the current history position. 1423 * 1424 * @private 1425 * 1426 * @param {pwlib.appEvent.historyUpdate} ev The application event object. 1427 * 1428 * @see PaintWeb#historyGoto the method which allows you to go to different 1429 * history states. 1430 */ 1431 this.historyUpdate = function (ev) { 1432 var undoElem = _self.commands.historyUndo, 1433 undoState = false, 1434 redoElem = _self.commands.historyRedo, 1435 redoState = false, 1436 className = ' ' + _self.classPrefix + 'disabled', 1437 undoElemState = undoElem.className.indexOf(className) === -1, 1438 redoElemState = redoElem.className.indexOf(className) === -1; 1439 1440 if (ev.currentPos > 1) { 1441 undoState = true; 1442 } 1443 if (ev.currentPos < ev.states) { 1444 redoState = true; 1445 } 1446 1447 if (undoElemState !== undoState) { 1448 if (undoState) { 1449 undoElem.className = undoElem.className.replace(className, ''); 1450 } else { 1451 undoElem.className += className; 1452 } 1453 } 1454 1455 if (redoElemState !== redoState) { 1456 if (redoState) { 1457 redoElem.className = redoElem.className.replace(className, ''); 1458 } else { 1459 redoElem.className += className; 1460 } 1461 } 1462 }; 1463 1464 /** 1465 * The <code>imageSizeChange</code> application event handler. The GUI element 1466 * which displays the image dimensions is updated to display the new image 1467 * size. 1468 * 1469 * <p>Image size refers strictly to the dimensions of the image being edited 1470 * by the user, that's width and height. 1471 * 1472 * @private 1473 * @param {pwlib.appEvent.imageSizeChange} ev The application event object. 1474 */ 1475 this.imageSizeChange = function (ev) { 1476 var imageSize = _self.elems.imageSize; 1477 if (imageSize) { 1478 imageSize.replaceChild(doc.createTextNode(ev.width + 'x' + ev.height), 1479 imageSize.firstChild); 1480 } 1481 }; 1482 1483 /** 1484 * The <code>canvasSizeChange</code> application event handler. The Canvas 1485 * container element dimensions are updated to the new values, and the image 1486 * resize handle is positioned accordingly. 1487 * 1488 * <p>Canvas size refers strictly to the dimensions of the Canvas elements in 1489 * the browser, changed with CSS style properties, width and height. Scaling 1490 * of the Canvas elements is applied when the user zooms the image or when the 1491 * browser changes the render DPI / zoom. 1492 * 1493 * @private 1494 * @param {pwlib.appEvent.canvasSizeChange} ev The application event object. 1495 */ 1496 this.canvasSizeChange = function (ev) { 1497 var canvasContainer = _self.elems.canvasContainer, 1498 canvasResizer = _self.canvasResizer, 1499 className = ' ' + _self.classPrefix + 'disabled', 1500 resizeHandle = canvasResizer.resizeHandle; 1501 1502 // Update the Canvas container to be the same size as the Canvas elements. 1503 canvasContainer.style.width = ev.width + 'px'; 1504 canvasContainer.style.height = ev.height + 'px'; 1505 1506 resizeHandle.style.top = ev.height + 'px'; 1507 resizeHandle.style.left = ev.width + 'px'; 1508 }; 1509 1510 /** 1511 * The <code>imageZoom</code> application event handler. The GUI input element 1512 * which displays the image zoom level is updated to display the new value. 1513 * 1514 * @private 1515 * @param {pwlib.appEvent.imageZoom} ev The application event object. 1516 */ 1517 this.imageZoom = function (ev) { 1518 var elem = _self.inputs.imageZoom, 1519 val = MathRound(ev.zoom * 100); 1520 if (elem && elem.value != val) { 1521 elem.value = val; 1522 } 1523 }; 1524 1525 /** 1526 * The <code>configChange</code> application event handler. This method 1527 * ensures the GUI input elements stay up-to-date when some PaintWeb 1528 * configuration is modified. 1529 * 1530 * @private 1531 * @param {pwlib.appEvent.configChange} ev The application event object. 1532 */ 1533 this.configChangeHandler = function (ev) { 1534 var cfg = '', input; 1535 if (ev.group) { 1536 cfg = ev.group.replace('.', '_') + '_'; 1537 } 1538 cfg += ev.config; 1539 input = _self.inputs[cfg]; 1540 1541 // Handle changes for color inputs. 1542 if (!input && (input = _self.colorInputs[cfg])) { 1543 var color = ev.value.replace(/\s+/g, ''). 1544 replace(/^rgba\(/, '').replace(/\)$/, ''); 1545 1546 color = color.split(','); 1547 input.updateColor({ 1548 red: color[0] / 255, 1549 green: color[1] / 255, 1550 blue: color[2] / 255, 1551 alpha: color[3] 1552 }); 1553 1554 return; 1555 } 1556 1557 if (!input) { 1558 return; 1559 } 1560 1561 var tag = input.tagName.toLowerCase(), 1562 isInput = tag === 'select' || tag === 'input' || tag === 'textarea'; 1563 1564 if (isInput) { 1565 if (input.type === 'checkbox' && input.checked !== ev.value) { 1566 input.checked = ev.value; 1567 } 1568 if (input.type !== 'checkbox' && input.value !== ev.value) { 1569 input.value = ev.value; 1570 } 1571 1572 return; 1573 } 1574 1575 var classActive = ' ' + _self.className + 'configActive'; 1576 1577 if (input.hasAttribute('data-pwConfigToggle')) { 1578 var inputActive = input.className.indexOf(classActive) !== -1; 1579 1580 if (ev.value && !inputActive) { 1581 input.className += classActive; 1582 } else if (!ev.value && inputActive) { 1583 input.className = input.className.replace(classActive, ''); 1584 } 1585 } 1586 1587 var classActive = ' ' + _self.className + 'configActive', 1588 prevValElem = _self.inputValues[cfg + '_' + ev.previousValue], 1589 valElem = _self.inputValues[cfg + '_' + ev.value]; 1590 1591 if (prevValElem && prevValElem.className.indexOf(classActive) !== -1) { 1592 prevValElem.className = prevValElem.className.replace(classActive, ''); 1593 } 1594 1595 if (valElem && valElem.className.indexOf(classActive) === -1) { 1596 valElem.className += classActive; 1597 } 1598 }; 1599 1600 /** 1601 * The <code>click</code> event handler for DOM elements associated to 1602 * PaintWeb configuration values. These elements rely on parent elements which 1603 * are associated to configuration properties. 1604 * 1605 * <p>This method dispatches the {@link pwlib.appEvent.configChange} event. 1606 * 1607 * @private 1608 * @param {Event} ev The DOM Event object. 1609 */ 1610 this.configValueClick = function (ev) { 1611 var pNode = this.parentNode, 1612 input = pNode._pwConfigParent, 1613 val = pNode.getAttribute('data-pwConfigValue'); 1614 1615 if (!input || !input._pwConfigProperty) { 1616 return; 1617 } 1618 1619 ev.preventDefault(); 1620 1621 var className = ' ' + _self.classPrefix + 'configActive', 1622 groupRef = input._pwConfigGroupRef, 1623 group = input._pwConfigGroup, 1624 prop = input._pwConfigProperty, 1625 prevVal = groupRef[prop], 1626 prevValElem = _self.inputValues[group.replace('.', '_') + '_' + prop 1627 + '_' + prevVal]; 1628 1629 if (prevVal == val) { 1630 return; 1631 } 1632 1633 if (prevValElem && prevValElem.className.indexOf(className) !== -1) { 1634 prevValElem.className = prevValElem.className.replace(className, ''); 1635 } 1636 1637 groupRef[prop] = val; 1638 1639 if (pNode.className.indexOf(className) === -1) { 1640 pNode.className += className; 1641 } 1642 1643 app.events.dispatch(new appEvent.configChange(val, prevVal, prop, group, 1644 groupRef)); 1645 }; 1646 1647 /** 1648 * The <code>change</code> event handler for input elements associated to 1649 * PaintWeb configuration properties. 1650 * 1651 * <p>This method dispatches the {@link pwlib.appEvent.configChange} event. 1652 * 1653 * @private 1654 */ 1655 this.configInputChange = function () { 1656 if (!this._pwConfigProperty) { 1657 return; 1658 } 1659 1660 var val = this.type === 'checkbox' ? this.checked : this.value, 1661 groupRef = this._pwConfigGroupRef, 1662 group = this._pwConfigGroup, 1663 prop = this._pwConfigProperty, 1664 prevVal = groupRef[prop]; 1665 1666 if (this.getAttribute('type') === 'number') { 1667 val = parseInt(val); 1668 if (val != this.value) { 1669 this.value = val; 1670 } 1671 } 1672 1673 if (val == prevVal) { 1674 return; 1675 } 1676 1677 groupRef[prop] = val; 1678 1679 app.events.dispatch(new appEvent.configChange(val, prevVal, prop, group, 1680 groupRef)); 1681 }; 1682 1683 /** 1684 * The <code>click</code> event handler for DOM elements associated to boolean 1685 * configuration properties. These elements only toggle the true/false value 1686 * of the configuration property. 1687 * 1688 * <p>This method dispatches the {@link pwlib.appEvent.configChange} event. 1689 * 1690 * @private 1691 * @param {Event} ev The DOM Event object. 1692 */ 1693 this.configToggleClick = function (ev) { 1694 var className = ' ' + _self.classPrefix + 'configActive', 1695 pNode = this.parentNode, 1696 groupRef = pNode._pwConfigGroupRef, 1697 group = pNode._pwConfigGroup, 1698 prop = pNode._pwConfigProperty, 1699 elemActive = pNode.className.indexOf(className) !== -1; 1700 1701 ev.preventDefault(); 1702 1703 groupRef[prop] = !groupRef[prop]; 1704 1705 if (groupRef[prop] && !elemActive) { 1706 pNode.className += className; 1707 } else if (!groupRef[prop] && elemActive) { 1708 pNode.className = pNode.className.replace(className, ''); 1709 } 1710 1711 app.events.dispatch(new appEvent.configChange(groupRef[prop], 1712 !groupRef[prop], prop, group, groupRef)); 1713 }; 1714 1715 /** 1716 * The <code>shadowAllow</code> application event handler. This method 1717 * shows/hide the shadow tab when shadows are allowed/disallowed. 1718 * 1719 * @private 1720 * @param {pwlib.appEvent.shadowAllow} ev The application event object. 1721 */ 1722 this.shadowAllow = function (ev) { 1723 if ('shadow' in _self.tabPanels.main.tabs) { 1724 if (ev.allowed) { 1725 _self.tabPanels.main.tabShow('shadow'); 1726 } else { 1727 _self.tabPanels.main.tabHide('shadow'); 1728 } 1729 } 1730 }; 1731 1732 /** 1733 * The <code>clipboardUpdate</code> application event handler. The GUI element 1734 * associated to the <code>clipboardPaste</code> command is updated to be 1735 * disabled/enabled depending on the event. 1736 * 1737 * @private 1738 * @param {pwlib.appEvent.clipboardUpdate} ev The application event object. 1739 */ 1740 this.clipboardUpdate = function (ev) { 1741 var classDisabled = ' ' + _self.classPrefix + 'disabled', 1742 elem, elemEnabled, 1743 elems = [_self.commands.clipboardPaste, 1744 _self.elems.selTab_clipboardPaste]; 1745 1746 for (var i = 0, n = elems.length; i < n; i++) { 1747 elem = elems[i]; 1748 if (!elem) { 1749 continue; 1750 } 1751 1752 elemEnabled = elem.className.indexOf(classDisabled) === -1; 1753 1754 if (!ev.data && elemEnabled) { 1755 elem.className += classDisabled; 1756 } else if (ev.data && !elemEnabled) { 1757 elem.className = elem.className.replace(classDisabled, ''); 1758 } 1759 } 1760 }; 1761 1762 /** 1763 * The <code>selectionChange</code> application event handler. The GUI 1764 * elements associated to the <code>selectionCut</code> and 1765 * <code>selectionCopy</code> commands are updated to be disabled/enabled 1766 * depending on the event. 1767 * 1768 * @private 1769 * @param {pwlib.appEvent.selectionChange} ev The application event object. 1770 */ 1771 this.selectionChange = function (ev) { 1772 var classDisabled = ' ' + _self.classPrefix + 'disabled', 1773 elem, elemEnabled, 1774 elems = [_self.commands.selectionCut, _self.commands.selectionCopy, 1775 _self.elems.selTab_selectionCut, _self.elems.selTab_selectionCopy, 1776 _self.commands.selectionDelete, _self.commands.selectionFill, 1777 _self.commands.selectionCrop]; 1778 1779 for (var i = 0, n = elems.length; i < n; i++) { 1780 elem = elems[i]; 1781 if (!elem) { 1782 continue; 1783 } 1784 1785 elemEnabled = elem.className.indexOf(classDisabled) === -1; 1786 1787 if (ev.state === ev.STATE_NONE && elemEnabled) { 1788 elem.className += classDisabled; 1789 } else if (ev.state === ev.STATE_SELECTED && !elemEnabled) { 1790 elem.className = elem.className.replace(classDisabled, ''); 1791 } 1792 } 1793 }; 1794 1795 /** 1796 * Show the graphical user interface. 1797 * 1798 * <p>This method dispatches the {@link pwlib.appEvent.guiShow} application 1799 * event. 1800 */ 1801 this.show = function () { 1802 var placeholder = config.guiPlaceholder, 1803 className = this.classPrefix + 'placeholder', 1804 re = new RegExp('\\b' + className); 1805 1806 if (!re.test(placeholder.className)) { 1807 placeholder.className += ' ' + className; 1808 } 1809 1810 placeholder.focus(); 1811 1812 app.events.dispatch(new appEvent.guiShow()); 1813 }; 1814 1815 /** 1816 * Hide the graphical user interface. 1817 * 1818 * <p>This method dispatches the {@link pwlib.appEvent.guiHide} application 1819 * event. 1820 */ 1821 this.hide = function () { 1822 var placeholder = config.guiPlaceholder, 1823 re = new RegExp('\\b' + this.classPrefix + 'placeholder', 'g'); 1824 1825 placeholder.className = placeholder.className.replace(re, ''); 1826 1827 app.events.dispatch(new appEvent.guiHide()); 1828 }; 1829 1830 /** 1831 * The application destroy event handler. This method is invoked by the main 1832 * PaintWeb application when the instance is destroyed, for the purpose of 1833 * cleaning-up the GUI-related things from the document add by the current 1834 * instance. 1835 * 1836 * @private 1837 */ 1838 this.destroy = function () { 1839 var placeholder = config.guiPlaceholder; 1840 1841 while(placeholder.hasChildNodes()) { 1842 placeholder.removeChild(placeholder.firstChild); 1843 } 1844 }; 1845 1846 /** 1847 * Resize the PaintWeb graphical user interface. 1848 * 1849 * <p>This method dispatches the {@link pwlib.appEvent.configChange} event for 1850 * the "viewportWidth" and "viewportHeight" configuration properties. Both 1851 * properties are updated to hold the new values you give. 1852 * 1853 * <p>Once the GUI is resized, the {@link pwlib.appEvent.viewportSizeChange} 1854 * event is also dispatched. 1855 * 1856 * @param {String} width The new width you want. Make sure the value is a CSS 1857 * length, like "50%", "450px" or "30em". 1858 * 1859 * @param {String} height The new height you want. 1860 */ 1861 this.resizeTo = function (width, height) { 1862 if (!width || !height) { 1863 return; 1864 } 1865 1866 var width_old = config.viewportWidth, 1867 height_old = config.viewportHeight; 1868 1869 config.viewportWidth = width; 1870 config.viewportHeight = height; 1871 1872 app.events.dispatch(new appEvent.configChange(width, width_old, 1873 'viewportWidth', '', config)); 1874 1875 app.events.dispatch(new appEvent.configChange(height, height_old, 1876 'viewportHeight', '', config)); 1877 1878 config.guiPlaceholder.style.width = config.viewportWidth; 1879 this.elems.viewport.style.height = config.viewportHeight; 1880 1881 app.events.dispatch(new appEvent.viewportSizeChange(width, height)); 1882 }; 1883 1884 /** 1885 * The state change event handler for the Hand tool. This function 1886 * enables/disables the Hand tool by checking if the current image fits into 1887 * the viewport or not. 1888 * 1889 * <p>This function is invoked when one of the following application events is 1890 * dispatched: <code>viewportSizeChange</code>, <code>canvasSizeChange</code> 1891 * or <code>appInit</code. 1892 * 1893 * @private 1894 * @param 1895 * {pwlib.appEvent.viewportSizeChange|pwlib.appEvent.canvasSizeChange|pwlib.appEvent.appInit} 1896 * [ev] The application event object. 1897 */ 1898 this.toolHandStateChange = function (ev) { 1899 var cwidth = 0, 1900 cheight = 0, 1901 className = ' ' + _self.classPrefix + 'disabled', 1902 hand = _self.tools.hand, 1903 viewport = _self.elems.viewport; 1904 1905 if (!hand) { 1906 return; 1907 } 1908 1909 if (ev.type === 'canvasSizeChange') { 1910 cwidth = ev.width; 1911 cheight = ev.height; 1912 } else { 1913 var containerStyle = _self.elems.canvasContainer.style; 1914 cwidth = parseInt(containerStyle.width); 1915 cheight = parseInt(containerStyle.height); 1916 } 1917 1918 // FIXME: it should be noted that when PaintWeb loads, the entire GUI is 1919 // hidden, and win.getComputedStyle() style tells that the viewport 1920 // width/height is 0. 1921 cs = win.getComputedStyle(viewport, null); 1922 1923 var vwidth = parseInt(cs.width), 1924 vheight = parseInt(cs.height), 1925 enableHand = false, 1926 handState = hand.className.indexOf(className) === -1; 1927 1928 if (vheight < cheight || vwidth < cwidth) { 1929 enableHand = true; 1930 } 1931 1932 if (enableHand && !handState) { 1933 hand.className = hand.className.replace(className, ''); 1934 } else if (!enableHand && handState) { 1935 hand.className += className; 1936 } 1937 1938 if (!enableHand && app.tool && app.tool._id === 'hand' && 'prevTool' in 1939 app.tool) { 1940 app.toolActivate(app.tool.prevTool, ev); 1941 } 1942 }; 1943 }; 1944 1945 /** 1946 * @class A floating panel GUI element. 1947 * 1948 * @private 1949 * 1950 * @param {pwlib.gui} gui Reference to the PaintWeb GUI object. 1951 * 1952 * @param {Element} container Reference to the DOM element you want to transform 1953 * into a floating panel. 1954 */ 1955 pwlib.guiFloatingPanel = function (gui, container) { 1956 var _self = this, 1957 appEvent = pwlib.appEvent, 1958 cStyle = container.style, 1959 doc = gui.app.doc, 1960 guiPlaceholder = gui.app.config.guiPlaceholder, 1961 lang = gui.app.lang, 1962 panels = gui.floatingPanels, 1963 win = gui.app.win, 1964 zIndex_step = 200; 1965 1966 // These hold the mouse starting location during the drag operation. 1967 var mx, my; 1968 1969 // These hold the panel starting location during the drag operation. 1970 var ptop, pleft; 1971 1972 /** 1973 * Panel state: hidden. 1974 * @constant 1975 */ 1976 this.STATE_HIDDEN = 0; 1977 1978 /** 1979 * Panel state: visible. 1980 * @constant 1981 */ 1982 this.STATE_VISIBLE = 1; 1983 1984 /** 1985 * Panel state: minimized. 1986 * @constant 1987 */ 1988 this.STATE_MINIMIZED = 3; 1989 1990 /** 1991 * Panel state: the user is dragging the floating panel. 1992 * @constant 1993 */ 1994 this.STATE_DRAGGING = 4; 1995 1996 /** 1997 * Tells the state of the floating panel: hidden/minimized/visible or if it's 1998 * being dragged. 1999 * @type Number 2000 */ 2001 this.state = -1; 2002 2003 /** 2004 * Floating panel ID. This is the ID used in the 2005 * <var>data-pwFloatingPanel</var> element attribute. 2006 * @type String 2007 */ 2008 this.id = null; 2009 2010 /** 2011 * Reference to the floating panel element. 2012 * @type Element 2013 */ 2014 this.container = container; 2015 2016 /** 2017 * The viewport element. This element is the first parent element which has 2018 * the style.overflow set to "auto" or "scroll". 2019 * @type Element 2020 */ 2021 this.viewport = null; 2022 2023 /** 2024 * Custom application events interface. 2025 * @type pwlib.appEvents 2026 */ 2027 this.events = null; 2028 2029 /** 2030 * The panel content element. 2031 * @type Element 2032 */ 2033 this.content = null; 2034 2035 // The initial viewport scroll position. 2036 var vScrollLeft = 0, vScrollTop = 0, 2037 btn_close = null, btn_minimize = null; 2038 2039 /** 2040 * Initialize the floating panel. 2041 * @private 2042 */ 2043 function init () { 2044 _self.events = new pwlib.appEvents(_self); 2045 2046 _self.id = _self.container.getAttribute('data-pwFloatingPanel'); 2047 2048 var ttl = _self.container.getElementsByTagName('h1')[0], 2049 content = _self.container.getElementsByTagName('div')[0], 2050 cs = win.getComputedStyle(_self.container, null), 2051 zIndex = parseInt(cs.zIndex); 2052 2053 cStyle.zIndex = cs.zIndex; 2054 2055 if (zIndex > panels.zIndex_) { 2056 panels.zIndex_ = zIndex; 2057 } 2058 2059 _self.container.className += ' ' + gui.classPrefix + 'floatingPanel ' + 2060 gui.classPrefix + 'floatingPanel_' + _self.id; 2061 2062 // the content 2063 content.className += ' ' + gui.classPrefix + 'floatingPanel_content'; 2064 _self.content = content; 2065 2066 // setup the title element 2067 ttl.className += ' ' + gui.classPrefix + 'floatingPanel_title'; 2068 ttl.replaceChild(doc.createTextNode(lang.floatingPanels[_self.id]), 2069 ttl.firstChild); 2070 2071 ttl.addEventListener('mousedown', ev_mousedown, false); 2072 2073 // allow auto-hide for the panel 2074 if (_self.container.getAttribute('data-pwPanelHide') === 'true') { 2075 _self.hide(); 2076 } else { 2077 _self.state = _self.STATE_VISIBLE; 2078 } 2079 2080 // Find the viewport parent element. 2081 var pNode = _self.container.parentNode, 2082 found = null; 2083 2084 while (!found && pNode) { 2085 if (pNode.nodeName.toLowerCase() === 'html') { 2086 found = pNode; 2087 break; 2088 } 2089 2090 cs = win.getComputedStyle(pNode, null); 2091 if (cs && (cs.overflow === 'scroll' || cs.overflow === 'auto')) { 2092 found = pNode; 2093 } else { 2094 pNode = pNode.parentNode; 2095 } 2096 } 2097 2098 _self.viewport = found; 2099 2100 // add the panel minimize button. 2101 btn_minimize = doc.createElement('a'); 2102 btn_minimize.href = '#'; 2103 btn_minimize.title = lang.floatingPanelMinimize; 2104 btn_minimize.className = gui.classPrefix + 'floatingPanel_minimize'; 2105 btn_minimize.addEventListener('click', ev_minimize, false); 2106 btn_minimize.appendChild(doc.createTextNode(btn_minimize.title)); 2107 2108 _self.container.insertBefore(btn_minimize, content); 2109 2110 // add the panel close button. 2111 btn_close = doc.createElement('a'); 2112 btn_close.href = '#'; 2113 btn_close.title = lang.floatingPanelClose; 2114 btn_close.className = gui.classPrefix + 'floatingPanel_close'; 2115 btn_close.addEventListener('click', ev_close, false); 2116 btn_close.appendChild(doc.createTextNode(btn_close.title)); 2117 2118 _self.container.insertBefore(btn_close, content); 2119 2120 // setup the panel resize handle. 2121 if (_self.container.getAttribute('data-pwPanelResizable') === 'true') { 2122 var resizeHandle = doc.createElement('div'); 2123 resizeHandle.className = gui.classPrefix + 'floatingPanel_resizer'; 2124 _self.container.appendChild(resizeHandle); 2125 _self.resizer = new pwlib.guiResizer(gui, resizeHandle, _self.container); 2126 } 2127 }; 2128 2129 /** 2130 * The <code>click</code> event handler for the panel Minimize button element. 2131 * 2132 * <p>This method dispatches the {@link 2133 * pwlib.appEvent.guiFloatingPanelStateChange} application event. 2134 * 2135 * @private 2136 * @param {Event} ev The DOM Event object. 2137 */ 2138 function ev_minimize (ev) { 2139 ev.preventDefault(); 2140 this.focus(); 2141 2142 var classMinimized = ' ' + gui.classPrefix + 'floatingPanel_minimized'; 2143 2144 if (_self.state === _self.STATE_MINIMIZED) { 2145 _self.state = _self.STATE_VISIBLE; 2146 2147 this.title = lang.floatingPanelMinimize; 2148 this.className = gui.classPrefix + 'floatingPanel_minimize'; 2149 this.replaceChild(doc.createTextNode(this.title), this.firstChild); 2150 2151 if (_self.container.className.indexOf(classMinimized) !== -1) { 2152 _self.container.className 2153 = _self.container.className.replace(classMinimized, ''); 2154 } 2155 2156 } else if (_self.state === _self.STATE_VISIBLE) { 2157 _self.state = _self.STATE_MINIMIZED; 2158 2159 this.title = lang.floatingPanelRestore; 2160 this.className = gui.classPrefix + 'floatingPanel_restore'; 2161 this.replaceChild(doc.createTextNode(this.title), this.firstChild); 2162 2163 if (_self.container.className.indexOf(classMinimized) === -1) { 2164 _self.container.className += classMinimized; 2165 } 2166 } 2167 2168 _self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state)); 2169 2170 _self.bringOnTop(); 2171 }; 2172 2173 /** 2174 * The <code>click</code> event handler for the panel Close button element. 2175 * This hides the floating panel. 2176 * 2177 * <p>This method dispatches the {@link 2178 * pwlib.appEvent.guiFloatingPanelStateChange} application event. 2179 * 2180 * @private 2181 * @param {Event} ev The DOM Event object. 2182 */ 2183 function ev_close (ev) { 2184 ev.preventDefault(); 2185 _self.hide(); 2186 guiPlaceholder.focus(); 2187 }; 2188 2189 /** 2190 * The <code>mousedown</code> event handler. This is invoked when you start 2191 * dragging the floating panel. 2192 * 2193 * <p>This method dispatches the {@link 2194 * pwlib.appEvent.guiFloatingPanelStateChange} application event. 2195 * 2196 * @private 2197 * @param {Event} ev The DOM Event object. 2198 */ 2199 function ev_mousedown (ev) { 2200 _self.state = _self.STATE_DRAGGING; 2201 2202 mx = ev.clientX; 2203 my = ev.clientY; 2204 2205 var cs = win.getComputedStyle(_self.container, null); 2206 2207 ptop = parseInt(cs.top); 2208 pleft = parseInt(cs.left); 2209 2210 if (_self.viewport) { 2211 vScrollLeft = _self.viewport.scrollLeft; 2212 vScrollTop = _self.viewport.scrollTop; 2213 } 2214 2215 _self.bringOnTop(); 2216 2217 doc.addEventListener('mousemove', ev_mousemove, false); 2218 doc.addEventListener('mouseup', ev_mouseup, false); 2219 2220 _self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state)); 2221 2222 if (ev.preventDefault) { 2223 ev.preventDefault(); 2224 } 2225 }; 2226 2227 /** 2228 * The <code>mousemove</code> event handler. This performs the actual move of 2229 * the floating panel. 2230 * 2231 * @private 2232 * @param {Event} ev The DOM Event object. 2233 */ 2234 function ev_mousemove (ev) { 2235 var x = pleft + ev.clientX - mx, 2236 y = ptop + ev.clientY - my; 2237 2238 if (_self.viewport) { 2239 if (_self.viewport.scrollLeft !== vScrollLeft) { 2240 x += _self.viewport.scrollLeft - vScrollLeft; 2241 } 2242 if (_self.viewport.scrollTop !== vScrollTop) { 2243 y += _self.viewport.scrollTop - vScrollTop; 2244 } 2245 } 2246 2247 cStyle.left = x + 'px'; 2248 cStyle.top = y + 'px'; 2249 }; 2250 2251 /** 2252 * The <code>mouseup</code> event handler. This ends the panel drag operation. 2253 * 2254 * <p>This method dispatches the {@link 2255 * pwlib.appEvent.guiFloatingPanelStateChange} application event. 2256 * 2257 * @private 2258 * @param {Event} ev The DOM Event object. 2259 */ 2260 function ev_mouseup (ev) { 2261 if (_self.container.className.indexOf(' ' + gui.classPrefix 2262 + 'floatingPanel_minimized') !== -1) { 2263 _self.state = _self.STATE_MINIMIZED; 2264 } else { 2265 _self.state = _self.STATE_VISIBLE; 2266 } 2267 2268 doc.removeEventListener('mousemove', ev_mousemove, false); 2269 doc.removeEventListener('mouseup', ev_mouseup, false); 2270 2271 guiPlaceholder.focus(); 2272 _self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state)); 2273 }; 2274 2275 /** 2276 * Bring the panel to the top. This method makes sure the current floating 2277 * panel is visible. 2278 */ 2279 this.bringOnTop = function () { 2280 panels.zIndex_ += zIndex_step; 2281 cStyle.zIndex = panels.zIndex_; 2282 }; 2283 2284 /** 2285 * Hide the panel. 2286 * 2287 * <p>This method dispatches the {@link 2288 * pwlib.appEvent.guiFloatingPanelStateChange} application event. 2289 */ 2290 this.hide = function () { 2291 cStyle.display = 'none'; 2292 _self.state = _self.STATE_HIDDEN; 2293 _self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state)); 2294 }; 2295 2296 /** 2297 * Show the panel. 2298 * 2299 * <p>This method dispatches the {@link 2300 * pwlib.appEvent.guiFloatingPanelStateChange} application event. 2301 */ 2302 this.show = function () { 2303 if (_self.state === _self.STATE_VISIBLE) { 2304 return; 2305 } 2306 2307 cStyle.display = 'block'; 2308 _self.state = _self.STATE_VISIBLE; 2309 2310 var classMinimized = ' ' + gui.classPrefix + 'floatingPanel_minimized'; 2311 2312 if (_self.container.className.indexOf(classMinimized) !== -1) { 2313 _self.container.className 2314 = _self.container.className.replace(classMinimized, ''); 2315 2316 btn_minimize.className = gui.classPrefix + 'floatingPanel_minimize'; 2317 btn_minimize.title = lang.floatingPanelMinimize; 2318 btn_minimize.replaceChild(doc.createTextNode(btn_minimize.title), 2319 btn_minimize.firstChild); 2320 } 2321 2322 _self.events.dispatch(new appEvent.guiFloatingPanelStateChange(_self.state)); 2323 2324 _self.bringOnTop(); 2325 }; 2326 2327 /** 2328 * Toggle the panel visibility. 2329 * 2330 * <p>This method dispatches the {@link 2331 * pwlib.appEvent.guiFloatingPanelStateChange} application event. 2332 */ 2333 this.toggle = function () { 2334 if (_self.state === _self.STATE_VISIBLE || _self.state === 2335 _self.STATE_MINIMIZED) { 2336 _self.hide(); 2337 } else { 2338 _self.show(); 2339 } 2340 }; 2341 2342 init(); 2343 }; 2344 2345 /** 2346 * @class The state change event for the floating panel. This event is fired 2347 * when the floating panel changes its state. This event is not cancelable. 2348 * 2349 * @augments pwlib.appEvent 2350 * 2351 * @param {Number} state The floating panel state. 2352 */ 2353 pwlib.appEvent.guiFloatingPanelStateChange = function (state) { 2354 /** 2355 * Panel state: hidden. 2356 * @constant 2357 */ 2358 this.STATE_HIDDEN = 0; 2359 2360 /** 2361 * Panel state: visible. 2362 * @constant 2363 */ 2364 this.STATE_VISIBLE = 1; 2365 2366 /** 2367 * Panel state: minimized. 2368 * @constant 2369 */ 2370 this.STATE_MINIMIZED = 3; 2371 2372 /** 2373 * Panel state: the user is dragging the floating panel. 2374 * @constant 2375 */ 2376 this.STATE_DRAGGING = 4; 2377 2378 /** 2379 * The current floating panel state. 2380 * @type Number 2381 */ 2382 this.state = state; 2383 2384 pwlib.appEvent.call(this, 'guiFloatingPanelStateChange'); 2385 }; 2386 2387 /** 2388 * @class Resize handler. 2389 * 2390 * @private 2391 * 2392 * @param {pwlib.gui} gui Reference to the PaintWeb GUI object. 2393 * 2394 * @param {Element} resizeHandle Reference to the resize handle DOM element. 2395 * This is the element users will be able to drag to achieve the resize effect 2396 * on the <var>container</var> element. 2397 * 2398 * @param {Element} container Reference to the container DOM element. This is 2399 * the element users will be able to resize using the <var>resizeHandle</var> 2400 * element. 2401 */ 2402 pwlib.guiResizer = function (gui, resizeHandle, container) { 2403 var _self = this, 2404 cStyle = container.style, 2405 doc = gui.app.doc, 2406 guiResizeEnd = pwlib.appEvent.guiResizeEnd, 2407 guiResizeMouseMove = pwlib.appEvent.guiResizeMouseMove, 2408 guiResizeStart = pwlib.appEvent.guiResizeStart, 2409 win = gui.app.win; 2410 2411 /** 2412 * Custom application events interface. 2413 * @type pwlib.appEvents 2414 */ 2415 this.events = null; 2416 2417 /** 2418 * The resize handle DOM element. 2419 * @type Element 2420 */ 2421 this.resizeHandle = resizeHandle; 2422 2423 /** 2424 * The container DOM element. This is the element that's resized by the user 2425 * when he/she drags the resize handle. 2426 * @type Element 2427 */ 2428 this.container = container; 2429 2430 /** 2431 * The viewport element. This element is the first parent element which has 2432 * the style.overflow set to "auto" or "scroll". 2433 * @type Element 2434 */ 2435 this.viewport = null; 2436 2437 /** 2438 * Tells if the GUI resizer should dispatch the {@link 2439 * pwlib.appEvent.guiResizeMouseMove} application event when the user moves 2440 * the mouse during the resize operation. 2441 * 2442 * @type Boolean 2443 * @default false 2444 */ 2445 this.dispatchMouseMove = false; 2446 2447 /** 2448 * Tells if the user resizing the container now. 2449 * 2450 * @type Boolean 2451 * @default false 2452 */ 2453 this.resizing = false; 2454 2455 // The initial position of the mouse. 2456 var mx = 0, my = 0; 2457 2458 // The initial container dimensions. 2459 var cWidth = 0, cHeight = 0; 2460 2461 // The initial viewport scroll position. 2462 var vScrollLeft = 0, vScrollTop = 0; 2463 2464 /** 2465 * Initialize the resize functionality. 2466 * @private 2467 */ 2468 function init () { 2469 _self.events = new pwlib.appEvents(_self); 2470 resizeHandle.addEventListener('mousedown', ev_mousedown, false); 2471 2472 // Find the viewport parent element. 2473 var cs, pNode = _self.container.parentNode, 2474 found = null; 2475 while (!found && pNode) { 2476 if (pNode.nodeName.toLowerCase() === 'html') { 2477 found = pNode; 2478 break; 2479 } 2480 2481 cs = win.getComputedStyle(pNode, null); 2482 if (cs && (cs.overflow === 'scroll' || cs.overflow === 'auto')) { 2483 found = pNode; 2484 } else { 2485 pNode = pNode.parentNode; 2486 } 2487 } 2488 2489 _self.viewport = found; 2490 }; 2491 2492 /** 2493 * The <code>mousedown</code> event handler. This starts the resize operation. 2494 * 2495 * <p>This function dispatches the {@link pwlib.appEvent.guiResizeStart} 2496 * event. 2497 * 2498 * @private 2499 * @param {Event} ev The DOM Event object. 2500 */ 2501 function ev_mousedown (ev) { 2502 mx = ev.clientX; 2503 my = ev.clientY; 2504 2505 var cs = win.getComputedStyle(_self.container, null); 2506 cWidth = parseInt(cs.width); 2507 cHeight = parseInt(cs.height); 2508 2509 var cancel = _self.events.dispatch(new guiResizeStart(mx, my, cWidth, 2510 cHeight)); 2511 2512 if (cancel) { 2513 return; 2514 } 2515 2516 if (_self.viewport) { 2517 vScrollLeft = _self.viewport.scrollLeft; 2518 vScrollTop = _self.viewport.scrollTop; 2519 } 2520 2521 _self.resizing = true; 2522 doc.addEventListener('mousemove', ev_mousemove, false); 2523 doc.addEventListener('mouseup', ev_mouseup, false); 2524 2525 if (ev.preventDefault) { 2526 ev.preventDefault(); 2527 } 2528 2529 if (ev.stopPropagation) { 2530 ev.stopPropagation(); 2531 } 2532 }; 2533 2534 /** 2535 * The <code>mousemove</code> event handler. This performs the actual resizing 2536 * of the <var>container</var> element. 2537 * 2538 * @private 2539 * @param {Event} ev The DOM Event object. 2540 */ 2541 function ev_mousemove (ev) { 2542 var w = cWidth + ev.clientX - mx, 2543 h = cHeight + ev.clientY - my; 2544 2545 if (_self.viewport) { 2546 if (_self.viewport.scrollLeft !== vScrollLeft) { 2547 w += _self.viewport.scrollLeft - vScrollLeft; 2548 } 2549 if (_self.viewport.scrollTop !== vScrollTop) { 2550 h += _self.viewport.scrollTop - vScrollTop; 2551 } 2552 } 2553 2554 cStyle.width = w + 'px'; 2555 cStyle.height = h + 'px'; 2556 2557 if (_self.dispatchMouseMove) { 2558 _self.events.dispatch(new guiResizeMouseMove(ev.clientX, ev.clientY, w, 2559 h)); 2560 } 2561 }; 2562 2563 /** 2564 * The <code>mouseup</code> event handler. This ends the resize operation. 2565 * 2566 * <p>This function dispatches the {@link pwlib.appEvent.guiResizeEnd} event. 2567 * 2568 * @private 2569 * @param {Event} ev The DOM Event object. 2570 */ 2571 function ev_mouseup (ev) { 2572 var cancel = _self.events.dispatch(new guiResizeEnd(ev.clientX, ev.clientY, 2573 parseInt(cStyle.width), parseInt(cStyle.height))); 2574 2575 if (cancel) { 2576 return; 2577 } 2578 2579 _self.resizing = false; 2580 doc.removeEventListener('mousemove', ev_mousemove, false); 2581 doc.removeEventListener('mouseup', ev_mouseup, false); 2582 }; 2583 2584 init(); 2585 }; 2586 2587 /** 2588 * @class The GUI element resize start event. This event is cancelable. 2589 * 2590 * @augments pwlib.appEvent 2591 * 2592 * @param {Number} x The mouse location on the x-axis. 2593 * @param {Number} y The mouse location on the y-axis. 2594 * @param {Number} width The element width. 2595 * @param {Number} height The element height. 2596 */ 2597 pwlib.appEvent.guiResizeStart = function (x, y, width, height) { 2598 /** 2599 * The mouse location on the x-axis. 2600 * @type Number 2601 */ 2602 this.x = x; 2603 2604 /** 2605 * The mouse location on the y-axis. 2606 * @type Number 2607 */ 2608 this.y = y; 2609 2610 /** 2611 * The element width. 2612 * @type Number 2613 */ 2614 this.width = width; 2615 2616 /** 2617 * The element height. 2618 * @type Number 2619 */ 2620 this.height = height; 2621 2622 pwlib.appEvent.call(this, 'guiResizeStart', true); 2623 }; 2624 2625 /** 2626 * @class The GUI element resize end event. This event is cancelable. 2627 * 2628 * @augments pwlib.appEvent 2629 * 2630 * @param {Number} x The mouse location on the x-axis. 2631 * @param {Number} y The mouse location on the y-axis. 2632 * @param {Number} width The element width. 2633 * @param {Number} height The element height. 2634 */ 2635 pwlib.appEvent.guiResizeEnd = function (x, y, width, height) { 2636 /** 2637 * The mouse location on the x-axis. 2638 * @type Number 2639 */ 2640 this.x = x; 2641 2642 /** 2643 * The mouse location on the y-axis. 2644 * @type Number 2645 */ 2646 this.y = y; 2647 2648 /** 2649 * The element width. 2650 * @type Number 2651 */ 2652 this.width = width; 2653 2654 /** 2655 * The element height. 2656 * @type Number 2657 */ 2658 this.height = height; 2659 2660 pwlib.appEvent.call(this, 'guiResizeEnd', true); 2661 }; 2662 2663 /** 2664 * @class The GUI element resize mouse move event. This event is not cancelable. 2665 * 2666 * @augments pwlib.appEvent 2667 * 2668 * @param {Number} x The mouse location on the x-axis. 2669 * @param {Number} y The mouse location on the y-axis. 2670 * @param {Number} width The element width. 2671 * @param {Number} height The element height. 2672 */ 2673 pwlib.appEvent.guiResizeMouseMove = function (x, y, width, height) { 2674 /** 2675 * The mouse location on the x-axis. 2676 * @type Number 2677 */ 2678 this.x = x; 2679 2680 /** 2681 * The mouse location on the y-axis. 2682 * @type Number 2683 */ 2684 this.y = y; 2685 2686 /** 2687 * The element width. 2688 * @type Number 2689 */ 2690 this.width = width; 2691 2692 /** 2693 * The element height. 2694 * @type Number 2695 */ 2696 this.height = height; 2697 2698 pwlib.appEvent.call(this, 'guiResizeMouseMove'); 2699 }; 2700 2701 /** 2702 * @class The tabbed panel GUI component. 2703 * 2704 * @private 2705 * 2706 * @param {pwlib.gui} gui Reference to the PaintWeb GUI object. 2707 * 2708 * @param {Element} panel Reference to the panel DOM element. 2709 */ 2710 pwlib.guiTabPanel = function (gui, panel) { 2711 var _self = this, 2712 appEvent = pwlib.appEvent, 2713 doc = gui.app.doc, 2714 lang = gui.app.lang; 2715 2716 /** 2717 * Custom application events interface. 2718 * @type pwlib.appEvents 2719 */ 2720 this.events = null; 2721 2722 /** 2723 * Panel ID. The ID is the same as the data-pwTabPanel attribute value of the 2724 * panel DOM element . 2725 * 2726 * @type String. 2727 */ 2728 this.id = null; 2729 2730 /** 2731 * Holds references to the DOM element of each tab and tab button. 2732 * @type Object 2733 */ 2734 this.tabs = {}; 2735 2736 /** 2737 * Reference to the tab buttons DOM element. 2738 * @type Element 2739 */ 2740 this.tabButtons = null; 2741 2742 /** 2743 * The panel container DOM element. 2744 * @type Element 2745 */ 2746 this.container = panel; 2747 2748 /** 2749 * Holds the ID of the currently active tab. 2750 * @type String 2751 */ 2752 this.tabId = null; 2753 2754 /** 2755 * Holds the ID of the previously active tab. 2756 * 2757 * @private 2758 * @type String 2759 */ 2760 var prevTabId_ = null; 2761 2762 /** 2763 * Initialize the toolbar functionality. 2764 * @private 2765 */ 2766 function init () { 2767 _self.events = new pwlib.appEvents(_self); 2768 _self.id = _self.container.getAttribute('data-pwTabPanel'); 2769 2770 // Add two class names, the generic .paintweb_tabPanel and another class 2771 // name specific to the current tab panel: .paintweb_tabPanel_id. 2772 _self.container.className += ' ' + gui.classPrefix + 'tabPanel' 2773 + ' ' + gui.classPrefix + 'tabPanel_' + _self.id; 2774 2775 var tabButtons = doc.createElement('ul'), 2776 tabButton = null, 2777 tabDefault = _self.container.getAttribute('data-pwTabDefault') || null, 2778 childNodes = _self.container.childNodes, 2779 type = Node.ELEMENT_NODE, 2780 elem = null, 2781 tabId = null, 2782 anchor = null; 2783 2784 tabButtons.className = gui.classPrefix + 'tabsList'; 2785 2786 // Find all the tabs in the current panel container element. 2787 for (var i = 0; elem = childNodes[i]; i++) { 2788 if (elem.nodeType !== type) { 2789 continue; 2790 } 2791 2792 // A tab is any element with a given data-pwTab attribute. 2793 tabId = elem.getAttribute('data-pwTab'); 2794 if (!tabId) { 2795 continue; 2796 } 2797 2798 // two class names, the generic .paintweb_tab and the tab-specific class 2799 // name .paintweb_tabPanelId_tabId. 2800 elem.className += ' ' + gui.classPrefix + 'tab ' + gui.classPrefix 2801 + _self.id + '_' + tabId; 2802 2803 tabButton = doc.createElement('li'); 2804 tabButton._pwTab = tabId; 2805 2806 anchor = doc.createElement('a'); 2807 anchor.href = '#'; 2808 anchor.addEventListener('click', ev_tabClick, false); 2809 2810 if (_self.id in lang.tabs) { 2811 anchor.title = lang.tabs[_self.id][tabId + 'Title'] || 2812 lang.tabs[_self.id][tabId]; 2813 anchor.appendChild(doc.createTextNode(lang.tabs[_self.id][tabId])); 2814 } 2815 2816 if ((tabDefault && tabId === tabDefault) || 2817 (!tabDefault && !_self.tabId)) { 2818 _self.tabId = tabId; 2819 tabButton.className = gui.classPrefix + 'tabActive'; 2820 } else { 2821 prevTabId_ = tabId; 2822 elem.style.display = 'none'; 2823 } 2824 2825 // automatically hide the tab 2826 if (elem.getAttribute('data-pwTabHide') === 'true') { 2827 tabButton.style.display = 'none'; 2828 } 2829 2830 _self.tabs[tabId] = {container: elem, button: tabButton}; 2831 2832 tabButton.appendChild(anchor); 2833 tabButtons.appendChild(tabButton); 2834 } 2835 2836 _self.tabButtons = tabButtons; 2837 _self.container.appendChild(tabButtons); 2838 }; 2839 2840 /** 2841 * The <code>click</code> event handler for tab buttons. This function simply 2842 * activates the tab the user clicked. 2843 * 2844 * @private 2845 * @param {Event} ev The DOM Event object. 2846 */ 2847 function ev_tabClick (ev) { 2848 ev.preventDefault(); 2849 _self.tabActivate(this.parentNode._pwTab); 2850 }; 2851 2852 /** 2853 * Activate a tab by ID. 2854 * 2855 * <p>This method dispatches the {@link pwlib.appEvent.guiTabActivate} event. 2856 * 2857 * @param {String} tabId The ID of the tab you want to activate. 2858 * @returns {Boolean} True if the tab has been activated successfully, or 2859 * false if not. 2860 */ 2861 this.tabActivate = function (tabId) { 2862 if (!tabId || !(tabId in this.tabs)) { 2863 return false; 2864 } else if (tabId === this.tabId) { 2865 return true; 2866 } 2867 2868 var ev = new appEvent.guiTabActivate(tabId, this.tabId), 2869 cancel = this.events.dispatch(ev), 2870 elem = null, 2871 tabButton = null; 2872 2873 if (cancel) { 2874 return false; 2875 } 2876 2877 // Deactivate the currently active tab. 2878 if (this.tabId in this.tabs) { 2879 elem = this.tabs[this.tabId].container; 2880 elem.style.display = 'none'; 2881 tabButton = this.tabs[this.tabId].button; 2882 tabButton.className = ''; 2883 prevTabId_ = this.tabId; 2884 } 2885 2886 // Activate the new tab. 2887 elem = this.tabs[tabId].container; 2888 elem.style.display = ''; 2889 tabButton = this.tabs[tabId].button; 2890 tabButton.className = gui.classPrefix + 'tabActive'; 2891 tabButton.style.display = ''; // make sure the tab is not hidden 2892 tabButton.firstChild.focus(); 2893 this.tabId = tabId; 2894 2895 return true; 2896 }; 2897 2898 /** 2899 * Hide a tab by ID. 2900 * 2901 * @param {String} tabId The ID of the tab you want to hide. 2902 * @returns {Boolean} True if the tab has been hidden successfully, or false 2903 * if not. 2904 */ 2905 this.tabHide = function (tabId) { 2906 if (!(tabId in this.tabs)) { 2907 return false; 2908 } 2909 2910 if (this.tabId === tabId) { 2911 this.tabActivate(prevTabId_); 2912 } 2913 2914 this.tabs[tabId].button.style.display = 'none'; 2915 2916 return true; 2917 }; 2918 2919 /** 2920 * Show a tab by ID. 2921 * 2922 * @param {String} tabId The ID of the tab you want to show. 2923 * @returns {Boolean} True if the tab has been displayed successfully, or 2924 * false if not. 2925 */ 2926 this.tabShow = function (tabId) { 2927 if (!(tabId in this.tabs)) { 2928 return false; 2929 } 2930 2931 this.tabs[tabId].button.style.display = ''; 2932 2933 return true; 2934 }; 2935 2936 init(); 2937 }; 2938 2939 /** 2940 * @class The GUI tab activation event. This event is cancelable. 2941 * 2942 * @augments pwlib.appEvent 2943 * 2944 * @param {String} tabId The ID of the tab being activated. 2945 * @param {String} prevTabId The ID of the previously active tab. 2946 */ 2947 pwlib.appEvent.guiTabActivate = function (tabId, prevTabId) { 2948 /** 2949 * The ID of the tab being activated. 2950 * @type String 2951 */ 2952 this.tabId = tabId; 2953 2954 /** 2955 * The ID of the previously active tab. 2956 * @type String 2957 */ 2958 this.prevTabId = prevTabId; 2959 2960 pwlib.appEvent.call(this, 'guiTabActivate', true); 2961 }; 2962 2963 /** 2964 * @class The color input GUI component. 2965 * 2966 * @private 2967 * 2968 * @param {pwlib.gui} gui Reference to the PaintWeb GUI object. 2969 * 2970 * @param {Element} input Reference to the DOM input element. This can be 2971 * a span, a div, or any other tag. 2972 */ 2973 pwlib.guiColorInput = function (gui, input) { 2974 var _self = this, 2975 colormixer = null, 2976 config = gui.app.config, 2977 doc = gui.app.doc, 2978 MathRound = Math.round, 2979 lang = gui.app.lang; 2980 2981 /** 2982 * Color input ID. The ID is the same as the data-pwColorInput attribute value 2983 * of the DOM input element . 2984 * 2985 * @type String. 2986 */ 2987 this.id = null; 2988 2989 /** 2990 * The color input element DOM reference. 2991 * 2992 * @type Element 2993 */ 2994 this.input = input; 2995 2996 /** 2997 * The configuration property to which this color input is attached to. 2998 * @type String 2999 */ 3000 this.configProperty = null; 3001 3002 /** 3003 * The configuration group to which this color input is attached to. 3004 * @type String 3005 */ 3006 this.configGroup = null; 3007 3008 /** 3009 * Reference to the configuration object which holds the color input value. 3010 * @type String 3011 */ 3012 this.configGroupRef = null; 3013 3014 /** 3015 * Holds the current color displayed by the input. 3016 * 3017 * @type Object 3018 */ 3019 this.color = {red: 0, green: 0, blue: 0, alpha: 0}; 3020 3021 /** 3022 * Initialize the color input functionality. 3023 * @private 3024 */ 3025 function init () { 3026 var cfgAttr = _self.input.getAttribute('data-pwColorInput'), 3027 cfgNoDots = cfgAttr.replace('.', '_'), 3028 cfgArray = cfgAttr.split('.'), 3029 cfgProp = cfgArray.pop(), 3030 cfgGroup = cfgArray.join('.'), 3031 cfgGroupRef = config, 3032 langGroup = lang.inputs, 3033 labelElem = _self.input.parentNode, 3034 anchor = doc.createElement('a'), 3035 color; 3036 3037 for (var i = 0, n = cfgArray.length; i < n; i++) { 3038 cfgGroupRef = cfgGroupRef[cfgArray[i]]; 3039 langGroup = langGroup[cfgArray[i]]; 3040 } 3041 3042 _self.configProperty = cfgProp; 3043 _self.configGroup = cfgGroup; 3044 _self.configGroupRef = cfgGroupRef; 3045 3046 _self.id = cfgNoDots; 3047 3048 _self.input.className += ' ' + gui.classPrefix + 'colorInput' 3049 + ' ' + gui.classPrefix + _self.id; 3050 3051 labelElem.replaceChild(doc.createTextNode(langGroup[cfgProp]), 3052 labelElem.firstChild); 3053 3054 color = _self.configGroupRef[_self.configProperty]; 3055 color = color.replace(/\s+/g, '').replace(/^rgba\(/, '').replace(/\)$/, ''); 3056 color = color.split(','); 3057 _self.color.red = color[0] / 255; 3058 _self.color.green = color[1] / 255; 3059 _self.color.blue = color[2] / 255; 3060 _self.color.alpha = color[3]; 3061 3062 anchor.style.backgroundColor = 'rgb(' + color[0] + ',' + color[1] + ',' 3063 + color[2] + ')'; 3064 anchor.style.opacity = color[3]; 3065 3066 anchor.href = '#'; 3067 anchor.title = langGroup[cfgProp + 'Title'] || langGroup[cfgProp]; 3068 anchor.appendChild(doc.createTextNode(lang.inputs.colorInputAnchorContent)); 3069 anchor.addEventListener('click', ev_input_click, false); 3070 3071 _self.input.replaceChild(anchor, _self.input.firstChild); 3072 }; 3073 3074 /** 3075 * The <code>click</code> event handler for the color input element. This 3076 * function shows/hides the Color Mixer panel. 3077 * 3078 * @private 3079 * @param {Event} ev The DOM Event object. 3080 */ 3081 function ev_input_click (ev) { 3082 ev.preventDefault(); 3083 3084 if (!colormixer) { 3085 colormixer = gui.app.extensions.colormixer; 3086 } 3087 3088 if (!colormixer.targetInput || colormixer.targetInput.id !== _self.id) { 3089 colormixer.show({ 3090 id: _self.id, 3091 configProperty: _self.configProperty, 3092 configGroup: _self.configGroup, 3093 configGroupRef: _self.configGroupRef, 3094 show: colormixer_show, 3095 hide: colormixer_hide 3096 }, _self.color); 3097 3098 } else { 3099 colormixer.hide(); 3100 } 3101 }; 3102 3103 /** 3104 * The color mixer <code>show</code> event handler. This function is invoked 3105 * when the color mixer is shown. 3106 * @private 3107 */ 3108 function colormixer_show () { 3109 var classActive = ' ' + gui.classPrefix + 'colorInputActive', 3110 elemActive = _self.input.className.indexOf(classActive) !== -1; 3111 3112 if (!elemActive) { 3113 _self.input.className += classActive; 3114 } 3115 }; 3116 3117 /** 3118 * The color mixer <code>hide</code> event handler. This function is invoked 3119 * when the color mixer is hidden. 3120 * @private 3121 */ 3122 function colormixer_hide () { 3123 var classActive = ' ' + gui.classPrefix + 'colorInputActive', 3124 elemActive = _self.input.className.indexOf(classActive) !== -1; 3125 3126 if (elemActive) { 3127 _self.input.className = _self.input.className.replace(classActive, ''); 3128 } 3129 }; 3130 3131 /** 3132 * Update color. This method allows the change of the color values associated 3133 * to the current color input. 3134 * 3135 * <p>This method is used by the color picker tool and by the global GUI 3136 * <code>configChange</code> application event handler. 3137 * 3138 * @param {Object} color The new color values. The object must have four 3139 * properties: <var>red</var>, <var>green</var>, <var>blue</var> and 3140 * <var>alpha</var>. All values must be between 0 and 1. 3141 */ 3142 this.updateColor = function (color) { 3143 var anchor = _self.input.firstChild.style; 3144 3145 anchor.opacity = color.alpha; 3146 anchor.backgroundColor = 'rgb(' + MathRound(color.red * 255) + ',' + 3147 MathRound(color.green * 255) + ',' + 3148 MathRound(color.blue * 255) + ')'; 3149 _self.color.red = color.red; 3150 _self.color.green = color.green; 3151 _self.color.blue = color.blue; 3152 _self.color.alpha = color.alpha; 3153 }; 3154 3155 init(); 3156 }; 3157 3158 // vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix: 3159 3160