1 /* 2 * Copyright (C) 2008, 2009, 2010 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: 2010-06-26 20:35:34 +0300 $ 21 */ 22 23 /** 24 * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a> 25 * @fileOverview Minimal JavaScript library which provides functionality for 26 * cross-browser compatibility support. 27 */ 28 29 /** 30 * @namespace Holds methods and properties necessary throughout the entire 31 * application. 32 */ 33 var pwlib = {}; 34 35 /** 36 * @namespace Holds pre-packaged files. 37 * @type Object 38 */ 39 pwlib.fileCache = {}; 40 41 /** 42 * @namespace Holds the implementation of each drawing tool. 43 * 44 * @type Object 45 * 46 * @see PaintWeb#toolRegister Register a new drawing tool into a PaintWeb 47 * instance. 48 * @see PaintWeb#toolActivate Activate a drawing tool in a PaintWeb instance. 49 * @see PaintWeb#toolUnregister Unregister a drawing tool from a PaintWeb 50 * instance. 51 * 52 * @see PaintWeb.config.toolDefault The default tool being activated when 53 * a PaintWeb instance is initialized. 54 * @see PaintWeb.config.tools Holds the list of tools to be loaded automatically 55 * when a PaintWeb instance is initialized. 56 */ 57 pwlib.tools = {}; 58 59 /** 60 * @namespace Holds all the PaintWeb extensions. 61 * 62 * @type Object 63 * @see PaintWeb#extensionRegister Register a new extension into a PaintWeb 64 * instance. 65 * @see PaintWeb#extensionUnregister Unregister an extension from a PaintWeb 66 * instance. 67 * @see PaintWeb.config.extensions Holds the list of extensions to be loaded 68 * automatically when a PaintWeb instance is initialized. 69 */ 70 pwlib.extensions = {}; 71 72 /** 73 * This function extends objects. 74 * 75 * @example 76 * <code>var <var>obj1</var> = {a: 'a1', b: 'b1', d: 'd1'}, 77 * <var>obj2</var> = {a: 'a2', b: 'b2', c: 'c2'}; 78 * 79 * pwlib.extend(<var>obj1</var>, <var>obj2</var>);</code> 80 * 81 * // Now <var>obj1.c == 'c2'</var>, while <var>obj1.a</var>, <var>obj1.b</var> 82 * // and <var>obj1.d</var> remain the same. 83 * 84 * // If <code>pwlib.extend(true, <var>obj1</var>, <var>obj2</var>)</code> is 85 * // called, then <var>obj1.a</var>, <var>obj1.b</var>, <var>obj1.c</var> 86 * // become all the same as in <var>obj2</var>. 87 * 88 * @example 89 * <code>var <var>obj1</var> = {a: 'a1', b: 'b1', extend: pwlib.extend}; 90 * <var>obj1</var>.extend({c: 'c1', d: 'd1'});</code> 91 * 92 * // In this case the destination object which is to be extend is 93 * // <var>obj1</var>. 94 * 95 * @param {Boolean} [overwrite=false] If the first argument is a boolean, then 96 * it will be considered as a boolean flag for overwriting (or not) any existing 97 * methods and properties in the destination object. Thus, any method and 98 * property from the source object will take over those in the destination. The 99 * argument is optional, and if it's omitted, then no method/property will be 100 * overwritten. 101 * 102 * @param {Object} [destination=this] The second argument is the optional 103 * destination object: the object which will be extended. By default, the 104 * <var>this</var> object will be extended. 105 * 106 * @param {Object} source The third argument must provide list of methods and 107 * properties which will be added to the destination object. 108 */ 109 pwlib.extend = function () { 110 var name, src, sval, dval; 111 112 if (typeof arguments[0] === 'boolean') { 113 force = arguments[0]; 114 dest = arguments[1]; 115 src = arguments[2]; 116 } else { 117 force = false; 118 dest = arguments[0]; 119 src = arguments[1]; 120 } 121 122 if (typeof src === 'undefined') { 123 src = dest; 124 dest = this; 125 } 126 127 if (typeof dest === 'undefined') { 128 return; 129 } 130 131 for (name in src) { 132 sval = src[name]; 133 dval = dest[name]; 134 if (force || typeof dval === 'undefined') { 135 dest[name] = sval; 136 } 137 } 138 }; 139 140 /** 141 * Retrieve a string formatted with the provided variables. 142 * 143 * <p>The language string must be available in the global <var>lang</var> 144 * object. 145 * 146 * <p>The string can contain any number of variables in the form of 147 * <code>{var_name}</code>. 148 * 149 * @example 150 * lang.table_cells = "The table {name} has {n} cells."; 151 * 152 * // later ... 153 * console.log(pwlib.strf(lang.table_cells, {'name' : 'tbl1', 'n' : 11})); 154 * // The output is 'The table tbl1 has 11 cells.' 155 * 156 * @param {String} str The string you want to output. 157 * 158 * @param {Object} [vars] The variables you want to set in the language string. 159 * 160 * @returns {String} The string updated with the variables you provided. 161 */ 162 pwlib.strf = function (str, vars) { 163 if (!str) { 164 return str; 165 } 166 167 var re, i; 168 169 for (i in vars) { 170 re = new RegExp('{' + i + '}', 'g'); 171 str = str.replace(re, vars[i]); 172 } 173 174 return str; 175 }; 176 177 /** 178 * Parse a JSON string. This method uses the global JSON parser provided by 179 * the browser natively. The small difference is that this method allows 180 * normal JavaScript comments in the JSON string. 181 * 182 * @param {String} str The JSON string to parse. 183 * @returns The JavaScript object that was parsed. 184 */ 185 pwlib.jsonParse = function (str) { 186 str = str.replace(/\s*\/\*(\s|.)+?\*\//g, ''). 187 replace(/^\s*\/\/.*$/gm, ''); 188 189 return JSON.parse(str); 190 }; 191 192 /** 193 * Load a file from a given URL using XMLHttpRequest. 194 * 195 * @param {String} url The URL you want to load. 196 * 197 * @param {Function} handler The <code>onreadystatechange</code> event handler 198 * for the XMLHttpRequest object. Your event handler will always receive the 199 * XMLHttpRequest object as the first parameter. 200 * 201 * @param {String} [method="GET"] The HTTP method to use for loading the URL. 202 * 203 * @param {String} [send=null] The string you want to send in an HTTP POST 204 * request. 205 * 206 * @param {Object} [headers] An object holding the header names and values you 207 * want to set for the request. 208 * 209 * @returns {XMLHttpRequest} The XMLHttpRequest object created by this method. 210 * 211 * @throws {TypeError} If the <var>url</var> is not a string. 212 */ 213 pwlib.xhrLoad = function (url, handler, method, send, headers) { 214 if (typeof url !== 'string') { 215 throw new TypeError('The first argument must be a string!'); 216 } 217 218 if (!method) { 219 method = 'GET'; 220 } 221 222 if (!headers) { 223 headers = {}; 224 } 225 226 if (!send) { 227 send = null; 228 } 229 230 /** @ignore */ 231 var xhr = new XMLHttpRequest(); 232 /** @ignore */ 233 xhr.onreadystatechange = function () { handler(xhr); }; 234 xhr.open(method, url); 235 236 for (var header in headers) { 237 xhr.setRequestHeader(header, headers[header]); 238 } 239 240 xhr.send(send); 241 242 return xhr; 243 }; 244 245 /** 246 * Check if an URL points to a resource from the same host as the desired one. 247 * 248 * <p>Note that data URIs always return true. 249 * 250 * @param {String} url The URL you want to check. 251 * @param {String} host The host you want in the URL. The host name can include 252 * the port definition as well. 253 * 254 * @returns {Boolean} True if the <var>url</var> points to a resource from the 255 * <var>host</var> given, or false otherwise. 256 */ 257 pwlib.isSameHost = function (url, host) { 258 if (!url || !host) { 259 return false; 260 } 261 262 var pos = url.indexOf(':'), 263 proto = url.substr(0, pos + 1).toLowerCase(); 264 265 if (proto === 'data:') { 266 return true; 267 } 268 269 if (proto !== 'http:' && proto !== 'https:') { 270 return false; 271 } 272 273 var urlHost = url.replace(/^https?:\/\//i, ''); 274 pos = urlHost.indexOf('/'); 275 if (pos > -1) { 276 urlHost = urlHost.substr(0, pos); 277 } 278 279 // remove default port (80) 280 urlHost = urlHost.replace(/:80$/, ''); 281 host = host.replace(/:80$/, ''); 282 283 if (!urlHost || !host || urlHost !== host) { 284 return false; 285 } 286 287 return true; 288 }; 289 290 /** 291 * @class Custom application event. 292 * 293 * @param {String} type Event type. 294 * @param {Boolean} [cancelable=false] Tells if the event can be cancelled or 295 * not. 296 * 297 * @throws {TypeError} If the <var>type</var> parameter is not a string. 298 * @throws {TypeError} If the <var>cancelable</var> parameter is not a string. 299 * 300 * @see pwlib.appEvents for the application events interface which allows adding 301 * and removing event listeners. 302 */ 303 pwlib.appEvent = function (type, cancelable) { 304 if (typeof type !== 'string') { 305 throw new TypeError('The first argument must be a string'); 306 } else if (typeof cancelable === 'undefined') { 307 cancelable = false; 308 } else if (typeof cancelable !== 'boolean') { 309 throw new TypeError('The second argument must be a boolean'); 310 } 311 312 /** 313 * Event target object. 314 * @type Object 315 */ 316 this.target = null; 317 318 /** 319 * Tells if the event can be cancelled or not. 320 * @type Boolean 321 */ 322 this.cancelable = cancelable; 323 324 /** 325 * Tells if the event has the default action prevented or not. 326 * @type Boolean 327 */ 328 this.defaultPrevented = false; 329 330 /** 331 * Event type. 332 * @type String 333 */ 334 this.type = type; 335 336 /** 337 * Prevent the default action of the event. 338 */ 339 this.preventDefault = function () { 340 if (cancelable) { 341 this.defaultPrevented = true; 342 } 343 }; 344 345 /** 346 * Stop the event propagation to other event handlers. 347 */ 348 this.stopPropagation = function () { 349 this.propagationStopped_ = true; 350 }; 351 352 this.toString = function () { 353 return '[pwlib.appEvent.' + this.type + ']'; 354 }; 355 }; 356 357 /** 358 * @class Application initialization event. This event is not cancelable. 359 * 360 * @augments pwlib.appEvent 361 * 362 * @param {Number} state The initialization state. 363 * @param {String} [errorMessage] The error message, if any. 364 * 365 * @throws {TypeError} If the <var>state</var> is not a number. 366 */ 367 pwlib.appEvent.appInit = function (state, errorMessage) { 368 if (typeof state !== 'number') { 369 throw new TypeError('The first argument must be a number.'); 370 } 371 372 /** 373 * Application initialization not started. 374 * @constant 375 */ 376 this.INIT_NOT_STARTED = 0; 377 378 /** 379 * Application initialization started. 380 * @constant 381 */ 382 this.INIT_STARTED = 1; 383 384 /** 385 * Application initialization completed successfully. 386 * @constant 387 */ 388 this.INIT_DONE = 2; 389 390 /** 391 * Application initialization failed. 392 * @constant 393 */ 394 this.INIT_ERROR = -1; 395 396 /** 397 * Initialization state. 398 * @type Number 399 */ 400 this.state = state; 401 402 /** 403 * Initialization error message, if any. 404 * @type String|null 405 */ 406 this.errorMessage = errorMessage || null; 407 408 pwlib.appEvent.call(this, 'appInit'); 409 }; 410 411 /** 412 * @class Application destroy event. This event is not cancelable. 413 * 414 * @augments pwlib.appEvent 415 */ 416 pwlib.appEvent.appDestroy = function () { 417 pwlib.appEvent.call(this, 'appDestroy'); 418 }; 419 420 /** 421 * @class GUI show event. This event is not cancelable. 422 * 423 * @augments pwlib.appEvent 424 */ 425 pwlib.appEvent.guiShow = function () { 426 pwlib.appEvent.call(this, 'guiShow'); 427 }; 428 429 /** 430 * @class GUI hide event. This event is not cancelable. 431 * 432 * @augments pwlib.appEvent 433 */ 434 pwlib.appEvent.guiHide = function () { 435 pwlib.appEvent.call(this, 'guiHide'); 436 }; 437 438 /** 439 * @class Tool preactivation event. This event is cancelable. 440 * 441 * @augments pwlib.appEvent 442 * 443 * @param {String} id The ID of the new tool being activated. 444 * @param {String|null} prevId The ID of the previous tool. 445 * 446 * @throws {TypeError} If the <var>id</var> is not a string. 447 * @throws {TypeError} If the <var>prevId</var> is not a string or null. 448 */ 449 pwlib.appEvent.toolPreactivate = function (id, prevId) { 450 if (typeof id !== 'string') { 451 throw new TypeError('The first argument must be a string.'); 452 } else if (prevId !== null && typeof prevId !== 'string') { 453 throw new TypeError('The second argument must be a string or null.'); 454 } 455 456 /** 457 * Tool ID. 458 * @type String 459 */ 460 this.id = id; 461 462 /** 463 * Previous tool ID. 464 * @type String 465 */ 466 this.prevId = prevId; 467 468 pwlib.appEvent.call(this, 'toolPreactivate', true); 469 }; 470 471 /** 472 * @class Tool activation event. This event is not cancelable. 473 * 474 * @augments pwlib.appEvent 475 * 476 * @param {String} id The ID the tool which was activated. 477 * @param {String|null} prevId The ID of the previous tool. 478 * 479 * @throws {TypeError} If the <var>id</var> is not a string. 480 * @throws {TypeError} If the <var>prevId</var> is not a string or null. 481 */ 482 pwlib.appEvent.toolActivate = function (id, prevId) { 483 if (typeof id !== 'string') { 484 throw new TypeError('The first argument must be a string.'); 485 } else if (prevId !== null && typeof prevId !== 'string') { 486 throw new TypeError('The second argument must be a string or null.'); 487 } 488 489 /** 490 * Tool ID. 491 * @type String 492 */ 493 this.id = id; 494 495 /** 496 * Previous tool ID. 497 * @type String 498 */ 499 this.prevId = prevId; 500 501 pwlib.appEvent.call(this, 'toolActivate'); 502 }; 503 504 /** 505 * @class Tool registration event. This event is not cancelable. 506 * 507 * @augments pwlib.appEvent 508 * 509 * @param {String} id The ID of the tool being registered in an active PaintWeb 510 * instance. 511 * 512 * @throws {TypeError} If the <var>id</var> is not a string. 513 */ 514 pwlib.appEvent.toolRegister = function (id) { 515 if (typeof id !== 'string') { 516 throw new TypeError('The first argument must be a string.'); 517 } 518 519 /** 520 * Tool ID. 521 * @type String 522 */ 523 this.id = id; 524 525 pwlib.appEvent.call(this, 'toolRegister'); 526 }; 527 528 /** 529 * @class Tool removal event. This event is not cancelable. 530 * 531 * @augments pwlib.appEvent 532 * 533 * @param {String} id The ID of the tool being unregistered in an active 534 * PaintWeb instance. 535 * 536 * @throws {TypeError} If the <var>id</var> is not a string. 537 */ 538 pwlib.appEvent.toolUnregister = function (id) { 539 if (typeof id !== 'string') { 540 throw new TypeError('The first argument must be a string.'); 541 } 542 543 /** 544 * Tool ID. 545 * @type String 546 */ 547 this.id = id; 548 549 pwlib.appEvent.call(this, 'toolUnregister'); 550 }; 551 552 /** 553 * @class Extension registration event. This event is not cancelable. 554 * 555 * @augments pwlib.appEvent 556 * 557 * @param {String} id The ID of the extension being registered in an active 558 * PaintWeb instance. 559 * 560 * @throws {TypeError} If the <var>id</var> is not a string. 561 */ 562 pwlib.appEvent.extensionRegister = function (id) { 563 if (typeof id !== 'string') { 564 throw new TypeError('The first argument must be a string.'); 565 } 566 567 /** 568 * Extension ID. 569 * @type String 570 */ 571 this.id = id; 572 573 pwlib.appEvent.call(this, 'extensionRegister'); 574 }; 575 576 /** 577 * @class Extension removal event. This event is not cancelable. 578 * 579 * @augments pwlib.appEvent 580 * 581 * @param {String} id The ID of the extension being unregistered in an active 582 * PaintWeb instance. 583 * 584 * @throws {TypeError} If the <var>id</var> is not a string. 585 */ 586 pwlib.appEvent.extensionUnregister = function (id) { 587 if (typeof id !== 'string') { 588 throw new TypeError('The first argument must be a string.'); 589 } 590 591 /** 592 * Extension ID. 593 * @type String 594 */ 595 this.id = id; 596 597 pwlib.appEvent.call(this, 'extensionUnregister'); 598 }; 599 600 /** 601 * @class Command registration event. This event is not cancelable. 602 * 603 * @augments pwlib.appEvent 604 * 605 * @param {String} id The ID of the command being registered in an active 606 * PaintWeb instance. 607 * 608 * @throws {TypeError} If the <var>id</var> is not a string. 609 */ 610 pwlib.appEvent.commandRegister = function (id) { 611 if (typeof id !== 'string') { 612 throw new TypeError('The first argument must be a string.'); 613 } 614 615 /** 616 * Command ID. 617 * @type String 618 */ 619 this.id = id; 620 621 pwlib.appEvent.call(this, 'commandRegister'); 622 }; 623 624 /** 625 * @class Command removal event. This event is not cancelable. 626 * 627 * @augments pwlib.appEvent 628 * 629 * @param {String} id The ID of the command being unregistered in an active 630 * PaintWeb instance. 631 * 632 * @throws {TypeError} If the <var>id</var> is not a string. 633 */ 634 pwlib.appEvent.commandUnregister = function (id) { 635 if (typeof id !== 'string') { 636 throw new TypeError('The first argument must be a string.'); 637 } 638 639 /** 640 * Command ID. 641 * @type String 642 */ 643 this.id = id; 644 645 pwlib.appEvent.call(this, 'commandUnregister'); 646 }; 647 648 /** 649 * @class The image save event. This event is cancelable. 650 * 651 * @augments pwlib.appEvent 652 * 653 * @param {String} dataURL The data URL generated by the browser holding the 654 * pixels of the image being saved, in PNG format. 655 * @param {Number} width The image width. 656 * @param {Number} height The image height. 657 */ 658 pwlib.appEvent.imageSave = function (dataURL, width, height) { 659 /** 660 * The image saved by the browser, using the base64 encoding. 661 * @type String 662 */ 663 this.dataURL = dataURL; 664 665 /** 666 * Image width. 667 * @type Number 668 */ 669 this.width = width; 670 671 /** 672 * Image height. 673 * @type Number 674 */ 675 this.height = height; 676 677 pwlib.appEvent.call(this, 'imageSave', true); 678 }; 679 /** 680 * @class The image save result event. This event is not cancelable. 681 * 682 * @augments pwlib.appEvent 683 * 684 * @param {Boolean} successful Tells if the image save was successful or not. 685 * @param {String} [url] The image address. 686 * @param {String} [urlNew] The new image address. Provide this parameter, if, 687 * for example, you allow saving images from a remote server to a local server. 688 * In such cases the image address changes. 689 */ 690 pwlib.appEvent.imageSaveResult = function (successful, url, urlNew) { 691 /** 692 * Tells if the image save was successful or not. 693 * @type String 694 */ 695 this.successful = successful; 696 697 /** 698 * The image address. 699 * @type String|null 700 */ 701 this.url = url; 702 703 /** 704 * The new image address. 705 * @type String|null 706 */ 707 this.urlNew = urlNew; 708 709 pwlib.appEvent.call(this, 'imageSaveResult'); 710 }; 711 712 /** 713 * @class History navigation event. This event is not cancelable. 714 * 715 * @augments pwlib.appEvent 716 * 717 * @param {Number} currentPos The new history position. 718 * @param {Number} previousPos The previous history position. 719 * @param {Number} states The number of history states available. 720 * 721 * @throws {TypeError} If any of the arguments are not numbers. 722 */ 723 pwlib.appEvent.historyUpdate = function (currentPos, previousPos, states) { 724 if (typeof currentPos !== 'number' || typeof previousPos !== 'number' || 725 typeof states !== 'number') { 726 throw new TypeError('All arguments must be numbers.'); 727 } 728 729 /** 730 * Current history position. 731 * @type Number 732 */ 733 this.currentPos = currentPos; 734 735 /** 736 * Previous history position. 737 * @type Number 738 */ 739 this.previousPos = previousPos; 740 741 /** 742 * History states count. 743 * @type Number 744 */ 745 this.states = states; 746 747 pwlib.appEvent.call(this, 'historyUpdate'); 748 }; 749 750 /** 751 * @class Image size change event. This event is not cancelable. 752 * 753 * @augments pwlib.appEvent 754 * 755 * @param {Number} width The new image width. 756 * @param {Number} height The new image height. 757 * 758 * @throws {TypeError} If any of the arguments are not numbers. 759 */ 760 pwlib.appEvent.imageSizeChange = function (width, height) { 761 if (typeof width !== 'number' || typeof height !== 'number') { 762 throw new TypeError('Both arguments must be numbers.'); 763 } 764 765 /** 766 * New image width. 767 * @type Number 768 */ 769 this.width = width; 770 771 /** 772 * New image height. 773 * @type Number 774 */ 775 this.height = height; 776 777 pwlib.appEvent.call(this, 'imageSizeChange'); 778 }; 779 780 /** 781 * @class Canvas size change event. This event is not cancelable. 782 * 783 * <p>Note that the Canvas size is not the same as the image size. Canvas size 784 * refers to the scaling of the Canvas elements being applied (due to image 785 * zooming or due to browser zoom / DPI). 786 * 787 * @augments pwlib.appEvent 788 * 789 * @param {Number} width The new Canvas style width. 790 * @param {Number} height The new Canvas style height. 791 * @param {Number} scale The new Canvas scaling factor. 792 * 793 * @throws {TypeError} If any of the arguments are not numbers. 794 */ 795 pwlib.appEvent.canvasSizeChange = function (width, height, scale) { 796 if (typeof width !== 'number' || typeof height !== 'number' || typeof scale 797 !== 'number') { 798 throw new TypeError('All the arguments must be numbers.'); 799 } 800 801 /** 802 * New Canvas style width. 803 * @type Number 804 */ 805 this.width = width; 806 807 /** 808 * New Canvas style height. 809 * @type Number 810 */ 811 this.height = height; 812 813 /** 814 * The new Canvas scaling factor. 815 * @type Number 816 */ 817 this.scale = scale; 818 819 pwlib.appEvent.call(this, 'canvasSizeChange'); 820 }; 821 822 /** 823 * @class Image viewport size change event. This event is not cancelable. 824 * 825 * @augments pwlib.appEvent 826 * 827 * @param {String} width The new viewport width. This must be a CSS length 828 * value, like "100px", "100%" or "100em". 829 * 830 * @param {String} height The new viewport height. This must be a CSS length 831 * value, like "100px", "100%" or "100em". 832 */ 833 pwlib.appEvent.viewportSizeChange = function (width, height) { 834 /** 835 * New viewport width. 836 * @type String 837 */ 838 this.width = width; 839 840 /** 841 * New viewport height. 842 * @type String 843 */ 844 this.height = height; 845 846 pwlib.appEvent.call(this, 'viewportSizeChange'); 847 }; 848 849 850 /** 851 * @class Image zoom event. This event is cancelable. 852 * 853 * @augments pwlib.appEvent 854 * 855 * @param {Number} zoom The new image zoom level. 856 * 857 * @throws {TypeError} If the <var>zoom</var> argument is not a number. 858 */ 859 pwlib.appEvent.imageZoom = function (zoom) { 860 if (typeof zoom !== 'number') { 861 throw new TypeError('The first argument must be a number.'); 862 } 863 864 /** 865 * The new image zoom level. 866 * @type Number 867 */ 868 this.zoom = zoom; 869 870 pwlib.appEvent.call(this, 'imageZoom', true); 871 }; 872 873 /** 874 * @class Image crop event. This event is cancelable. 875 * 876 * @augments pwlib.appEvent 877 * 878 * @param {Number} x The crop start position on the x-axis. 879 * @param {Number} y The crop start position on the y-axis. 880 * @param {Number} width The cropped image width. 881 * @param {Number} height The cropped image height. 882 * 883 * @throws {TypeError} If any of the arguments are not numbers. 884 */ 885 pwlib.appEvent.imageCrop = function (x, y, width, height) { 886 if (typeof x !== 'number' || typeof y !== 'number' || typeof width !== 887 'number' || typeof height !== 'number') { 888 throw new TypeError('All arguments must be numbers.'); 889 } 890 891 /** 892 * The crop start position the x-axis. 893 * @type Number 894 */ 895 this.x = x; 896 897 /** 898 * The crop start position the y-axis. 899 * @type Number 900 */ 901 this.y = y; 902 903 /** 904 * The cropped image width. 905 * @type Number 906 */ 907 this.width = width; 908 909 /** 910 * The cropped image height. 911 * @type Number 912 */ 913 this.height = height; 914 915 pwlib.appEvent.call(this, 'imageCrop', true); 916 }; 917 918 /** 919 * @class Configuration change event. This event is not cancelable. 920 * 921 * @augments pwlib.appEvent 922 * 923 * @param {String|Number|Boolean} value The new value. 924 * @param {String|Number|Boolean} previousValue The previous value. 925 * @param {String} config The configuration property that just changed. 926 * @param {String} group The configuration group where the property is found. 927 * @param {Object} groupRef The configuration group object reference. 928 * 929 * @throws {TypeError} If the <var>prop</var> argument is not a string. 930 * @throws {TypeError} If the <var>group</var> argument is not a string. 931 * @throws {TypeError} If the <var>groupRef</var> argument is not an object. 932 */ 933 pwlib.appEvent.configChange = function (value, previousValue, config, group, 934 groupRef) { 935 if (typeof config !== 'string') { 936 throw new TypeError('The third argument must be a string.'); 937 } else if (typeof group !== 'string') { 938 throw new TypeError('The fourth argument must be a string.'); 939 } else if (typeof groupRef !== 'object') { 940 throw new TypeError('The fifth argument must be an object.'); 941 } 942 943 /** 944 * The new value. 945 */ 946 this.value = value; 947 948 /** 949 * The previous value. 950 */ 951 this.previousValue = previousValue; 952 953 /** 954 * Configuration property name. 955 * @type String 956 */ 957 this.config = config; 958 959 /** 960 * Configuration group name. 961 * @type String 962 */ 963 this.group = group; 964 965 /** 966 * Reference to the object holding the configuration property. 967 * @type Object 968 */ 969 this.groupRef = groupRef; 970 971 pwlib.appEvent.call(this, 'configChange'); 972 }; 973 974 /** 975 * @class Canvas shadows allowed change event. This event is not cancelable. 976 * 977 * @augments pwlib.appEvent 978 * 979 * @param {Boolean} allowed Tells the new allowance value. 980 * 981 * @throws {TypeError} If the argument is not a boolean value. 982 */ 983 pwlib.appEvent.shadowAllow = function (allowed) { 984 if (typeof allowed !== 'boolean') { 985 throw new TypeError('The first argument must be a boolean.'); 986 } 987 988 /** 989 * Tells if the Canvas shadows are allowed or not. 990 * @type Boolean 991 */ 992 this.allowed = allowed; 993 994 pwlib.appEvent.call(this, 'shadowAllow'); 995 }; 996 997 /** 998 * @class Clipboard update event. This event is not cancelable. 999 * 1000 * @augments pwlib.appEvent 1001 * 1002 * @param {ImageData} data Holds the clipboard ImageData. 1003 */ 1004 pwlib.appEvent.clipboardUpdate = function (data) { 1005 /** 1006 * The clipboard image data. 1007 * @type ImageData 1008 */ 1009 this.data = data; 1010 1011 pwlib.appEvent.call(this, 'clipboardUpdate'); 1012 }; 1013 1014 /** 1015 * @class An interface for adding, removing and dispatching of custom 1016 * application events. 1017 * 1018 * @param {Object} target_ The target for all the events. 1019 * 1020 * @see pwlib.appEvent to create application event objects. 1021 */ 1022 pwlib.appEvents = function (target_) { 1023 /** 1024 * Holds the list of event types and event handlers. 1025 * 1026 * @private 1027 * @type Object 1028 */ 1029 var events_ = {}; 1030 1031 var eventID_ = 1; 1032 1033 /** 1034 * Add an event listener. 1035 * 1036 * @param {String} type The event you want to listen for. 1037 * @param {Function} handler The event handler. 1038 * 1039 * @returns {Number} The event ID. 1040 * 1041 * @throws {TypeError} If the <var>type</var> argument is not a string. 1042 * @throws {TypeError} If the <var>handler</var> argument is not a function. 1043 * 1044 * @see pwlib.appEvents#remove to remove events. 1045 * @see pwlib.appEvents#dispatch to dispatch an event. 1046 */ 1047 this.add = function (type, handler) { 1048 if (typeof type !== 'string') { 1049 throw new TypeError('The first argument must be a string.'); 1050 } else if (typeof handler !== 'function') { 1051 throw new TypeError('The second argument must be a function.'); 1052 } 1053 1054 var id = eventID_++; 1055 1056 if (!(type in events_)) { 1057 events_[type] = {}; 1058 } 1059 1060 events_[type][id] = handler; 1061 1062 return id; 1063 }; 1064 1065 /** 1066 * Remove an event listener. 1067 * 1068 * @param {String} type The event type. 1069 * @param {Number} id The event ID. 1070 * 1071 * @throws {TypeError} If the <var>type</var> argument is not a string. 1072 * 1073 * @see pwlib.appEvents#add to add events. 1074 * @see pwlib.appEvents#dispatch to dispatch an event. 1075 */ 1076 this.remove = function (type, id) { 1077 if (typeof type !== 'string') { 1078 throw new TypeError('The first argument must be a string.'); 1079 } 1080 1081 if (!(type in events_) || !(id in events_[type])) { 1082 return; 1083 } 1084 1085 delete events_[type][id]; 1086 }; 1087 1088 /** 1089 * Dispatch an event. 1090 * 1091 * @param {String} type The event type. 1092 * @param {pwlib.appEvent} ev The event object. 1093 * 1094 * @returns {Boolean} True if the <code>event.preventDefault()</code> has been 1095 * invoked by one of the event handlers, or false if not. 1096 * 1097 * @throws {TypeError} If the <var>type</var> parameter is not a string. 1098 * @throws {TypeError} If the <var>ev</var> parameter is not an object. 1099 * 1100 * @see pwlib.appEvents#add to add events. 1101 * @see pwlib.appEvents#remove to remove events. 1102 * @see pwlib.appEvent the generic event object. 1103 */ 1104 this.dispatch = function (ev) { 1105 if (typeof ev !== 'object') { 1106 throw new TypeError('The second argument must be an object.'); 1107 } else if (typeof ev.type !== 'string') { 1108 throw new TypeError('The second argument must be an application event ' + 1109 'object.'); 1110 } 1111 1112 // No event handlers. 1113 if (!(ev.type in events_)) { 1114 return false; 1115 } 1116 1117 ev.target = target_; 1118 1119 var id, handlers = events_[ev.type]; 1120 for (id in handlers) { 1121 handlers[id].call(target_, ev); 1122 1123 if (ev.propagationStopped_) { 1124 break; 1125 } 1126 } 1127 1128 return ev.defaultPrevented; 1129 }; 1130 }; 1131 1132 1133 /** 1134 * @namespace Holds browser information. 1135 */ 1136 pwlib.browser = {}; 1137 1138 (function () { 1139 var ua = ''; 1140 1141 if (window.navigator && window.navigator.userAgent) { 1142 ua = window.navigator.userAgent.toLowerCase(); 1143 } 1144 1145 /** 1146 * @type Boolean 1147 */ 1148 pwlib.browser.opera = window.opera || /\bopera\b/.test(ua); 1149 1150 /** 1151 * Webkit is the render engine used primarily by Safari. It's also used by 1152 * Google Chrome and GNOME Epiphany. 1153 * 1154 * @type Boolean 1155 */ 1156 pwlib.browser.webkit = !pwlib.browser.opera && 1157 /\b(applewebkit|webkit)\b/.test(ua); 1158 1159 /** 1160 * Firefox uses the Gecko render engine. 1161 * 1162 * @type Boolean 1163 */ 1164 // In some variations of the User Agent strings provided by Opera, Firefox is 1165 // mentioned. 1166 pwlib.browser.firefox = /\bfirefox\b/.test(ua) && !pwlib.browser.opera; 1167 1168 /** 1169 * Gecko is the render engine used by Firefox and related products. 1170 * 1171 * @type Boolean 1172 */ 1173 // Typically, the user agent string of WebKit also mentions Gecko. Additionally, 1174 // Opera mentions Gecko for tricking some sites. 1175 pwlib.browser.gecko = /\bgecko\b/.test(ua) && !pwlib.browser.opera && 1176 !pwlib.browser.webkit; 1177 1178 /** 1179 * Microsoft Internet Explorer. The future of computing. 1180 * 1181 * @type Boolean 1182 */ 1183 // Again, Opera allows users to easily fake the UA. 1184 pwlib.browser.msie = /\bmsie\b/.test(ua) && !pwlib.browser.opera; 1185 1186 /** 1187 * Presto is the render engine used by Opera. 1188 * 1189 * @type Boolean 1190 */ 1191 // Older versions of Opera did not mention Presto in the UA string. 1192 pwlib.browser.presto = /\bpresto\b/.test(ua) || pwlib.browser.opera; 1193 1194 1195 /** 1196 * Browser operating system 1197 * 1198 * @type String 1199 */ 1200 pwlib.browser.os = (ua.match(/\b(windows|linux)\b/) || [])[1]; 1201 1202 /** 1203 * Tells if the browser is running on an OLPC XO. Typically, only the default 1204 * Gecko-based browser includes the OLPC XO tokens in the user agent string. 1205 * 1206 * @type Boolean 1207 */ 1208 pwlib.browser.olpcxo = /\bolpc\b/.test(ua) && /\bxo\b/.test(ua); 1209 1210 delete ua; 1211 })(); 1212 1213 1214 /** 1215 * @namespace Holds methods and properties necessary for DOM manipulation. 1216 */ 1217 pwlib.dom = {}; 1218 1219 /** 1220 * @namespace Holds the list of virtual key identifiers and a few characters, 1221 * each being associated to a key code commonly used by Web browsers. 1222 * 1223 * @private 1224 */ 1225 pwlib.dom.keyNames = { 1226 Help: 6, 1227 Backspace: 8, 1228 Tab: 9, 1229 Clear: 12, 1230 Enter: 13, 1231 Shift: 16, 1232 Control: 17, 1233 Alt: 18, 1234 Pause: 19, 1235 CapsLock: 20, 1236 Cancel: 24, 1237 'Escape': 27, 1238 Space: 32, 1239 PageUp: 33, 1240 PageDown: 34, 1241 End: 35, 1242 Home: 36, 1243 Left: 37, 1244 Up: 38, 1245 Right: 39, 1246 Down: 40, 1247 PrintScreen: 44, 1248 Insert: 45, 1249 'Delete': 46, 1250 Win: 91, 1251 ContextMenu: 93, 1252 '*': 106, 1253 '+': 107, 1254 F1: 112, 1255 F2: 113, 1256 F3: 114, 1257 F4: 115, 1258 F5: 116, 1259 F6: 117, 1260 F7: 118, 1261 F8: 119, 1262 F9: 120, 1263 F10: 121, 1264 F11: 122, 1265 F12: 123, 1266 NumLock: 144, 1267 ';': 186, 1268 '=': 187, 1269 ',': 188, 1270 '-': 189, 1271 '.': 190, 1272 '/': 191, 1273 '`': 192, 1274 '[': 219, 1275 '\\': 220, 1276 ']': 221, 1277 "'": 222 1278 }; 1279 1280 /** 1281 * @namespace Holds the list of codes, each being associated to a virtual key 1282 * identifier. 1283 * 1284 * @private 1285 */ 1286 pwlib.dom.keyCodes = { 1287 /* 1288 * For almost each key code, these comments give the key name, the 1289 * keyIdentifier from the DOM 3 Events spec and the Unicode character 1290 * information (if you would use the decimal code for direct conversion to 1291 * a character, e.g. String.fromCharCode()). Obviously, the Unicode character 1292 * information is not to be used, since these are only virtual key codes (not 1293 * really char codes) associated to key names. 1294 * 1295 * Each key name in here tries to follow the same style as the defined 1296 * keyIdentifiers from the DOM 3 Events. Thus for the Page Down button, 1297 * 'PageDown' is used (not other variations like 'pag-up'), and so on. 1298 * 1299 * Multiple key codes might be associated to the same key - it's not an error. 1300 * 1301 * Note that this list is not an exhaustive key codes list. This means that 1302 * for key A or for key 0, the script will do String.fromCharCode(keyCode), to 1303 * determine the key. For the case of alpha-numeric keys, this works fine. 1304 */ 1305 1306 /* 1307 * Key: Enter 1308 * Unicode: U+0003 [End of text] 1309 * 1310 * Note 1: This keyCode is only used in Safari 2 (older Webkit) for the Enter 1311 * key. 1312 * 1313 * Note 2: In Gecko this keyCode is used for the Cancel key (see 1314 * DOM_VK_CANCEL). 1315 */ 1316 3: 'Enter', 1317 1318 /* 1319 * Key: Help 1320 * Unicode: U+0006 [Acknowledge] 1321 * 1322 * Note: Taken from Gecko (DOM_VK_HELP). 1323 */ 1324 6: 'Help', 1325 1326 /* 1327 * Key: Backspace 1328 * Unicode: U+0008 [Backspace] 1329 * keyIdentifier: U+0008 1330 */ 1331 8: 'Backspace', 1332 1333 /* 1334 * Key: Tab 1335 * Unicode: U+0009 [Horizontal tab] 1336 * keyIdentifier: U+0009 1337 */ 1338 9: 'Tab', 1339 1340 /* 1341 * Key: Enter 1342 * Unicode: U+0010 [Line feed (LF) / New line (NL) / End of line (EOL)] 1343 * 1344 * Note: Taken from the Unicode characters list. If it ends up as a keyCode in 1345 * some event, it's simply considered as being the Enter key. 1346 */ 1347 10: 'Enter', 1348 1349 /* 1350 * Key: NumPad_Center 1351 * Unicode: U+000C [Form feed] 1352 * keyIdentifier: Clear 1353 * 1354 * Note 1: This keyCode is used when NumLock is off, and the user pressed the 1355 * 5 key on the numeric pad. 1356 * 1357 * Note 2: Safari 2 (older Webkit) assigns this keyCode to the NumLock key 1358 * itself. 1359 */ 1360 12: 'Clear', 1361 1362 /* 1363 * Key: Enter 1364 * Unicode: U+000D [Carriage return (CR)] 1365 * keyIdentifier: Enter 1366 * 1367 * Note 1: This is the keyCode used by most of the Web browsers when the Enter 1368 * key is pressed. 1369 * 1370 * Note 2: Gecko associates the DOM_VK_RETURN to this keyCode. 1371 */ 1372 13: 'Enter', 1373 1374 /* 1375 * Key: Enter 1376 * Unicode: U+000E [Shift out] 1377 * 1378 * Note: Taken from Gecko (DOM_VK_ENTER). 1379 */ 1380 14: 'Enter', 1381 1382 /* 1383 * Key: Shift 1384 * Unicode: U+0010 [Data link escape] 1385 * keyIdentifier: Shift 1386 * 1387 * Note: In older Safari (Webkit) versions Shift+Tab is assigned a different 1388 * keyCode: keyCode 25. 1389 */ 1390 16: 'Shift', 1391 1392 /* 1393 * Key: Control 1394 * Unicode: U+0011 [Device control one] 1395 * keyIdentifier: Control 1396 */ 1397 17: 'Control', 1398 1399 /* 1400 * Key: Alt 1401 * Unicode: U+0012 [Device control two] 1402 * keyIdentifier: Alt 1403 */ 1404 18: 'Alt', 1405 1406 /* 1407 * Key: Pause 1408 * Unicode: U+0013 [Device control three] 1409 * keyIdentifier: Pause 1410 */ 1411 19: 'Pause', 1412 1413 /* 1414 * Key: CapsLock 1415 * Unicode: U+0014 [Device control four] 1416 * keyIdentifier: CapsLock 1417 */ 1418 20: 'CapsLock', 1419 1420 /* 1421 * Key: Cancel 1422 * Unicode: U+0018 [Cancel] 1423 * keyIdentifier: U+0018 1424 */ 1425 24: 'Cancel', 1426 1427 /* 1428 * Key: Escape 1429 * Unicode: U+001B [Escape] 1430 * keyIdentifier: U+001B 1431 */ 1432 27: 'Escape', 1433 1434 /* 1435 * Key: Space 1436 * Unicode: U+0020 [Space] 1437 * keyIdentifier: U+0020 1438 */ 1439 32: 'Space', 1440 1441 /* 1442 * Key: PageUp or NumPad_North_East 1443 * Unicode: U+0021 ! [Exclamation mark] 1444 * keyIdentifier: PageUp 1445 */ 1446 33: 'PageUp', 1447 1448 /* 1449 * Key: PageDown or NumPad_South_East 1450 * Unicode: U+0022 " [Quotation mark] 1451 * keyIdentifier: PageDown 1452 */ 1453 34: 'PageDown', 1454 1455 /* 1456 * Key: End or NumPad_South_West 1457 * Unicode: U+0023 # [Number sign] 1458 * keyIdentifier: PageDown 1459 */ 1460 35: 'End', 1461 1462 /* 1463 * Key: Home or NumPad_North_West 1464 * Unicode: U+0024 $ [Dollar sign] 1465 * keyIdentifier: Home 1466 */ 1467 36: 'Home', 1468 1469 /* 1470 * Key: Left or NumPad_West 1471 * Unicode: U+0025 % [Percent sign] 1472 * keyIdentifier: Left 1473 */ 1474 37: 'Left', 1475 1476 /* 1477 * Key: Up or NumPad_North 1478 * Unicode: U+0026 & [Ampersand] 1479 * keyIdentifier: Up 1480 */ 1481 38: 'Up', 1482 1483 /* 1484 * Key: Right or NumPad_East 1485 * Unicode: U+0027 ' [Apostrophe] 1486 * keyIdentifier: Right 1487 */ 1488 39: 'Right', 1489 1490 /* 1491 * Key: Down or NumPad_South 1492 * Unicode: U+0028 ( [Left parenthesis] 1493 * keyIdentifier: Down 1494 */ 1495 40: 'Down', 1496 1497 /* 1498 * Key: PrintScreen 1499 * Unicode: U+002C , [Comma] 1500 * keyIdentifier: PrintScreen 1501 */ 1502 //44: 'PrintScreen', 1503 1504 /* 1505 * Key: Insert or NumPad_Insert 1506 * Unicode: U+002D - [Hyphen-Minus] 1507 * keyIdentifier: Insert 1508 */ 1509 45: 'Insert', 1510 1511 /* 1512 * Key: Delete or NumPad_Delete 1513 * Unicode: U+002E . [Full stop / period] 1514 * keyIdentifier: U+007F 1515 */ 1516 46: 'Delete', 1517 1518 /* 1519 * Key: WinLeft 1520 * Unicode: U+005B [ [Left square bracket] 1521 * keyIdentifier: Win 1522 * 1523 * Disabled: rarely needed. 1524 */ 1525 //91: 'Win', 1526 1527 /* 1528 * Key: WinRight 1529 * Unicode: U+005C \ [Reverse solidus / Backslash] 1530 * keyIdentifier: Win 1531 */ 1532 //92: 'Win', 1533 1534 /* 1535 * Key: Menu/ContextMenu 1536 * Unicode: U+005D ] [Right square bracket] 1537 * keyIdentifier: ... 1538 * 1539 * Disabled: Is it Meta? Is it Menu, ContextMenu, what? Too much mess. 1540 */ 1541 //93: 'ContextMenu', 1542 1543 /* 1544 * Key: NumPad_0 1545 * Unicode: U+0060 ` [Grave accent] 1546 * keyIdentifier: 0 1547 */ 1548 96: '0', 1549 1550 /* 1551 * Key: NumPad_1 1552 * Unicode: U+0061 a [Latin small letter a] 1553 * keyIdentifier: 1 1554 */ 1555 97: '1', 1556 1557 /* 1558 * Key: NumPad_2 1559 * Unicode: U+0062 b [Latin small letter b] 1560 * keyIdentifier: 2 1561 */ 1562 98: '2', 1563 1564 /* 1565 * Key: NumPad_3 1566 * Unicode: U+0063 c [Latin small letter c] 1567 * keyIdentifier: 3 1568 */ 1569 99: '3', 1570 1571 /* 1572 * Key: NumPad_4 1573 * Unicode: U+0064 d [Latin small letter d] 1574 * keyIdentifier: 4 1575 */ 1576 100: '4', 1577 1578 /* 1579 * Key: NumPad_5 1580 * Unicode: U+0065 e [Latin small letter e] 1581 * keyIdentifier: 5 1582 */ 1583 101: '5', 1584 1585 /* 1586 * Key: NumPad_6 1587 * Unicode: U+0066 f [Latin small letter f] 1588 * keyIdentifier: 6 1589 */ 1590 102: '6', 1591 1592 /* 1593 * Key: NumPad_7 1594 * Unicode: U+0067 g [Latin small letter g] 1595 * keyIdentifier: 7 1596 */ 1597 103: '7', 1598 1599 /* 1600 * Key: NumPad_8 1601 * Unicode: U+0068 h [Latin small letter h] 1602 * keyIdentifier: 8 1603 */ 1604 104: '8', 1605 1606 /* 1607 * Key: NumPad_9 1608 * Unicode: U+0069 i [Latin small letter i] 1609 * keyIdentifier: 9 1610 */ 1611 105: '9', 1612 1613 /* 1614 * Key: NumPad_Multiply 1615 * Unicode: U+0070 j [Latin small letter j] 1616 * keyIdentifier: U+002A * [Asterisk / Star] 1617 */ 1618 106: '*', 1619 1620 /* 1621 * Key: NumPad_Plus 1622 * Unicode: U+0071 k [Latin small letter k] 1623 * keyIdentifier: U+002B + [Plus] 1624 */ 1625 107: '+', 1626 1627 /* 1628 * Key: NumPad_Minus 1629 * Unicode: U+0073 m [Latin small letter m] 1630 * keyIdentifier: U+002D + [Hyphen / Minus] 1631 */ 1632 109: '-', 1633 1634 /* 1635 * Key: NumPad_Period 1636 * Unicode: U+0074 n [Latin small letter n] 1637 * keyIdentifier: U+002E . [Period] 1638 */ 1639 110: '.', 1640 1641 /* 1642 * Key: NumPad_Division 1643 * Unicode: U+0075 o [Latin small letter o] 1644 * keyIdentifier: U+002F / [Solidus / Slash] 1645 */ 1646 111: '/', 1647 1648 112: 'F1', // p 1649 113: 'F2', // q 1650 114: 'F3', // r 1651 115: 'F4', // s 1652 116: 'F5', // t 1653 117: 'F6', // u 1654 118: 'F7', // v 1655 119: 'F8', // w 1656 120: 'F9', // x 1657 121: 'F10', // y 1658 122: 'F11', // z 1659 123: 'F12', // { 1660 1661 /* 1662 * Key: Delete 1663 * Unicode: U+007F [Delete] 1664 * keyIdentifier: U+007F 1665 */ 1666 127: 'Delete', 1667 1668 /* 1669 * Key: NumLock 1670 * Unicode: U+0090 [Device control string] 1671 * keyIdentifier: NumLock 1672 */ 1673 144: 'NumLock', 1674 1675 186: ';', // º (Masculine ordinal indicator) 1676 187: '=', // » 1677 188: ',', // ¼ 1678 189: '-', // ½ 1679 190: '.', // ¾ 1680 191: '/', // ¿ 1681 192: '`', // À 1682 219: '[', // Û 1683 220: '\\', // Ü 1684 221: ']', // Ý 1685 222: "'" // Þ (Latin capital letter thorn) 1686 1687 //224: 'Win', // à 1688 //229: 'WinIME', // å or WinIME or something else in Webkit 1689 //255: 'NumLock', // ÿ, Gecko and Chrome, Windows XP in VirtualBox 1690 //376: 'NumLock' // Ÿ, Opera, Windows XP in VirtualBox 1691 }; 1692 1693 if (pwlib.browser.gecko) { 1694 pwlib.dom.keyCodes[3] = 'Cancel'; // DOM_VK_CANCEL 1695 } 1696 1697 /** 1698 * @namespace Holds a list of common wrong key codes in Web browsers. 1699 * 1700 * @private 1701 */ 1702 pwlib.dom.keyCodes_fixes = { 1703 42: pwlib.dom.keyNames['*'], // char * to key * 1704 47: pwlib.dom.keyNames['/'], // char / to key / 1705 59: pwlib.dom.keyNames[';'], // char ; to key ; 1706 61: pwlib.dom.keyNames['='], // char = to key = 1707 96: 48, // NumPad_0 to char 0 1708 97: 49, // NumPad_1 to char 1 1709 98: 50, // NumPad_2 to char 2 1710 99: 51, // NumPad_3 to char 3 1711 100: 52, // NumPad_4 to char 4 1712 101: 53, // NumPad_5 to char 5 1713 102: 54, // NumPad_6 to char 6 1714 103: 55, // NumPad_7 to char 7 1715 104: 56, // NumPad_8 to char 8 1716 105: 57, // NumPad_9 to char 9 1717 //106: 56, // NumPad_Multiply to char 8 1718 //107: 187, // NumPad_Plus to key = 1719 109: pwlib.dom.keyNames['-'], // NumPad_Minus to key - 1720 110: pwlib.dom.keyNames['.'], // NumPad_Period to key . 1721 111: pwlib.dom.keyNames['/'] // NumPad_Division to key / 1722 }; 1723 1724 /** 1725 * @namespace Holds the list of broken key codes generated by older Webkit 1726 * (Safari 2). 1727 * 1728 * @private 1729 */ 1730 pwlib.dom.keyCodes_Safari2 = { 1731 63232: pwlib.dom.keyNames.Up, // 38 1732 63233: pwlib.dom.keyNames.Down, // 40 1733 63234: pwlib.dom.keyNames.Left, // 37 1734 63235: pwlib.dom.keyNames.Right, // 39 1735 63236: pwlib.dom.keyNames.F1, // 112 1736 63237: pwlib.dom.keyNames.F2, // 113 1737 63238: pwlib.dom.keyNames.F3, // 114 1738 63239: pwlib.dom.keyNames.F4, // 115 1739 63240: pwlib.dom.keyNames.F5, // 116 1740 63241: pwlib.dom.keyNames.F6, // 117 1741 63242: pwlib.dom.keyNames.F7, // 118 1742 63243: pwlib.dom.keyNames.F8, // 119 1743 63244: pwlib.dom.keyNames.F9, // 120 1744 63245: pwlib.dom.keyNames.F10, // 121 1745 63246: pwlib.dom.keyNames.F11, // 122 1746 63247: pwlib.dom.keyNames.F12, // 123 1747 63248: pwlib.dom.keyNames.PrintScreen, // 44 1748 63272: pwlib.dom.keyNames['Delete'], // 46 1749 63273: pwlib.dom.keyNames.Home, // 36 1750 63275: pwlib.dom.keyNames.End, // 35 1751 63276: pwlib.dom.keyNames.PageUp, // 33 1752 63277: pwlib.dom.keyNames.PageDown, // 34 1753 63289: pwlib.dom.keyNames.NumLock, // 144 1754 63302: pwlib.dom.keyNames.Insert // 45 1755 }; 1756 1757 1758 /** 1759 * A complete keyboard events cross-browser compatibility layer. 1760 * 1761 * <p>Unfortunately, due to the important differences across Web browsers, 1762 * simply using the available properties in a single keyboard event is not 1763 * enough to accurately determine the key the user pressed. Thus, one needs to 1764 * have event handlers for all keyboard-related events <code>keydown</code>, 1765 * <code>keypress</code> and <code>keyup</code>. 1766 * 1767 * <p>This class provides a complete keyboard event compatibility layer. For any 1768 * new instance you provide the DOM element you want to listen events for, and 1769 * the event handlers for any of the three events <code>keydown</code> 1770 * / <code>keypress</code> / <code>keyup</code>. 1771 * 1772 * <p>Your event handlers will receive the original DOM Event object, with 1773 * several new properties defined: 1774 * 1775 * <ul> 1776 * <li><var>event.keyCode_</var> holds the correct code for event key. 1777 * 1778 * <li><var>event.key_</var> holds the key the user pressed. It can be either 1779 * a key name like "PageDown", "Delete", "Enter", or it is a character like 1780 * "A", "1", or "[". 1781 * 1782 * <li><var>event.charCode_</var> holds the Unicode character decimal code. 1783 * 1784 * <li><var>event.char_</var> holds the character generated by the event. 1785 * 1786 * <li><var>event.repeat_</var> is a boolean property telling if the 1787 * <code>keypress</code> event is repeated - the user is holding down the key 1788 * for a long-enough period of time to generate multiple events. 1789 * </ul> 1790 * 1791 * <p>The character-related properties, <var>charCode_</var> and 1792 * <var>char_</var> are only available in the <code>keypress</code> and 1793 * <code>keyup</code> event objects. 1794 * 1795 * <p>This class will ensure that the <code>keypress</code> event is always 1796 * fired in Webkit and MSIE for all keys, except modifiers. For modifier keys 1797 * like <kbd>Shift</kbd>, <kbd>Control</kbd>, and <kbd>Alt</kbd>, the 1798 * <code>keypress</code> event will not be fired, even if the Web browser does 1799 * it. 1800 * 1801 * <p>Some user agents like Webkit repeat the <code>keydown</code> event while 1802 * the user holds down a key. This class will ensure that only the 1803 * <code>keypress</code> event is repeated. 1804 * 1805 * <p>If you want to prevent the default action for an event, you should prevent 1806 * it on <code>keypress</code>. This class will prevent the default action for 1807 * <code>keydown</code> if need (in MSIE). 1808 * 1809 * @example 1810 * <code>var <var>klogger</var> = function (<var>ev</var>) { 1811 * console.log(<var>ev</var>.type + 1812 * ' keyCode_ ' + <var>ev</var>.keyCode_ + 1813 * ' key_ ' + <var>ev</var>.key_ + 1814 * ' charCode_ ' + <var>ev</var>.charCode_ + 1815 * ' char_ ' + <var>ev</var>.char_ + 1816 * ' repeat_ ' + <var>ev</var>.repeat_); 1817 * }; 1818 * 1819 * var <var>kbListener</var> = new pwlib.dom.KeyboardEventListener(window, 1820 * {keydown: <var>klogger</var>, 1821 * keypress: <var>klogger</var>, 1822 * keyup: <var>klogger</var>});</code> 1823 * 1824 * // later when you're done... 1825 * <code><var>kbListener</var>.detach();</code> 1826 * 1827 * @class A complete keyboard events cross-browser compatibility layer. 1828 * 1829 * @param {Element} elem_ The DOM Element you want to listen events for. 1830 * 1831 * @param {Object} handlers_ The object holding the list of event handlers 1832 * associated to the name of each keyboard event you want to listen. To listen 1833 * for all the three keyboard events use <code>{keydown: <var>fn1</var>, 1834 * keypress: <var>fn2</var>, keyup: <var>fn3</var>}</code>. 1835 * 1836 * @throws {TypeError} If the <var>handlers_</var> object does not contain any 1837 * event handler. 1838 */ 1839 pwlib.dom.KeyboardEventListener = function (elem_, handlers_) { 1840 /* 1841 Technical details: 1842 1843 For the keyup and keydown events the keyCode provided is that of the virtual 1844 key irrespective of other modifiers (e.g. Shift). Generally, during the 1845 keypress event, the keyCode holds the Unicode value of the character 1846 resulted from the key press, say an alphabetic upper/lower-case char, 1847 depending on the actual intent of the user and depending on the currently 1848 active keyboard layout. 1849 1850 Examples: 1851 * Pressing p you get keyCode 80 in keyup/keydown, and keyCode 112 in 1852 keypress. String.fromCharCode(80) = 'P' and String.fromCharCode(112) = 'p'. 1853 * Pressing P you get keyCode 80 in all events. 1854 * Pressing F1 you get keyCode 112 in keyup, keydown and keypress. 1855 * Pressing 9 you get keyCode 57 in all events. 1856 * Pressing Shift+9 you get keyCode 57 in keyup/keydown, and keyCode 40 in 1857 keypress. String.fromCharCode(57) = '9' and String.fromCharCode(40) = '('. 1858 1859 * Using the Greek layout when you press v on an US keyboard you get the 1860 output character ω. The keyup/keydown events hold keyCode 86 which is V. 1861 This does make sense, since it's the virtual key code we are dealing with 1862 - not the character code, not the result of pressing the key. The keypress 1863 event will hold keyCode 969 (ω). 1864 1865 * Pressing NumPad_Minus you get keyCode 109 in keyup/keydown and keyCode 45 1866 in keypress. Again, this happens because in keyup/keydown you don't get the 1867 character code, you get the key code, as indicated above. For 1868 your information: String.fromCharCode(109) = 'm' and String.fromCharCode(45) 1869 = '-'. 1870 1871 Therefore, we need to map all the codes of several keys, like F1-F12, 1872 Escape, Enter, Tab, etc. This map is held by pwlib.dom.keyCodes. It 1873 associates, for example, code 112 to F1, or 13 to Enter. This map is used to 1874 detect virtual keys in all events. 1875 1876 (This is only the general story, details about browser-specific differences 1877 follow below.) 1878 1879 If the code given by the browser doesn't appear in keyCode maps, it's used 1880 as is. The key_ returned is that of String.fromCharCode(keyCode). 1881 1882 In all browsers we consider all events having keyCode <= 32, as being events 1883 generated by a virtual key (not a character). As such, the keyCode value is 1884 always searched in pwlib.dom.keyCodes. 1885 1886 As you might notice from the above description, in the keypress event we 1887 cannot tell the difference from say F1 and p, because both give the code 1888 112. In Gecko and Webkit we can tell the difference because these UAs also 1889 set the charCode event property when the key generates a character. If F1 is 1890 pressed, or some other virtual key, charCode is never set. 1891 1892 In Opera the charCode property is never set. However, the 'which' event 1893 property is not set for several virtual keys. This means we can tell the 1894 difference between a character and a virtual key. However, there's a catch: 1895 not *all* virtual keys have the 'which' property unset. Known exceptions: 1896 Backspace (8), Tab (9), Enter (13), Shift (16), Control (17), Alt (18), 1897 Pause (19), Escape (27), End (35), Home (36), Insert (45), Delete (46) and 1898 NumLock (144). Given we already consider any keyCode <= 32 being one of some 1899 virtual key, fewer exceptions remain. We only have the End, Home, Insert, 1900 Delete and the NumLock keys which cannot be 100% properly detected in the 1901 keypress event, in Opera. To properly detect End/Home we can check if the 1902 Shift modifier is active or not. If the user wants # instead of End, then 1903 Shift must be active. The same goes for $ and Home. Thus we now only have 1904 the '-' (Insert) and the '.' (Delete) characters incorrectly detected as 1905 being Insert/Delete. 1906 1907 The above brings us to one of the main visible difference, when comparing 1908 the pwlib.dom.KeyboardEventListener class and the simple 1909 pwlib.dom.KeyboardEvent.getKey() function. In getKey(), for the keypress 1910 event we cannot accurately determine the exact key, because it requires 1911 checking the keyCode used for the keydown event. The KeyboardEventListener 1912 class monitors all the keyboard events, ensuring a more accurate key 1913 detection. 1914 1915 Different keyboard layouts and international characters are generally 1916 supported. Tested and known to work with the Cyrillic alphabet (Greek 1917 keyboard layout) and with the US Dvorak keyboard layouts. 1918 1919 Opera does not fire the keyup event for international characters when 1920 running on Linux. For example, this happens with the Greek keyboard layout, 1921 when trying Cyrillic characters. 1922 1923 Gecko gives no keyCode/charCode/which for international characters when 1924 running on Linux, in the keyup/keydown events. Thus, all such keys remain 1925 unidentified for these two events. For the keypress event there are no 1926 issues with such characters. 1927 1928 Webkit and Konqueror 4 also implement the keyIdentifier property from the 1929 DOM 3 Events specification. In theory, this should be great, but it's not 1930 without problems. Sometimes keyCode/charCode/which are all 0, but 1931 keyIdentifier is properly set. For several virtual keys the keyIdentifier 1932 value is simply 'U+0000'. Thus, the keyIdentifier is used only if the value 1933 is not 'Unidentified' / 'U+0000', and only when keyCode/charCode/which are 1934 not available. 1935 1936 Konqueror 4 does not use the 'U+XXXX' notation for Unicode characters. It 1937 simply gives the character, directly. 1938 1939 Additionally, Konqueror seems to have some problems with several keyCodes in 1940 keydown/keyup. For example, the key '[' gives keyCode 91 instead of 219. 1941 Thus, it effectively gives the Unicode for the character, not the key code. 1942 This issue is visible with other key as well. 1943 1944 NumPad_Clear is unidentified on Linux in all browsers, but it works on 1945 Windows. 1946 1947 In MSIE the keypress event is only fired for characters and for Escape, 1948 Space and Enter. Similarly, Webkit only fires the keypress event for 1949 characters. However, Webkit does not fire keypress for Escape. 1950 1951 International characters and different keyboard layouts seem to work fine in 1952 MSIE as well. 1953 1954 As of MSIE 4.0, the keypress event fires for the following keys: 1955 * Letters: A - Z (uppercase and lowercase) 1956 * Numerals: 0 - 9 1957 * Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~ 1958 * System: Escape (27), Space (32), Enter (13) 1959 1960 Documentation about the keypress event: 1961 http://msdn.microsoft.com/en-us/library/ms536939(VS.85).aspx 1962 1963 As of MSIE 4.0, the keydown event fires for the following keys: 1964 * Editing: Delete (46), Insert (45) 1965 * Function: F1 - F12 1966 * Letters: A - Z (uppercase and lowercase) 1967 * Navigation: Home, End, Left, Right, Up, Down 1968 * Numerals: 0 - 9 1969 * Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~ 1970 * System: Escape (27), Space (32), Shift (16), Tab (9) 1971 1972 As of MSIE 5, the event also fires for the following keys: 1973 * Editing: Backspace (8) 1974 * Navigation: PageUp (33), PageDown (34) 1975 * System: Shift+Tab (9) 1976 1977 Documentation about the keydown event: 1978 http://msdn.microsoft.com/en-us/library/ms536938(VS.85).aspx 1979 1980 As of MSIE 4.0, the keyup event fires for the following keys: 1981 * Editing: Delete, Insert 1982 * Function: F1 - F12 1983 * Letters: A - Z (uppercase and lowercase) 1984 * Navigation: Home (36), End (35), Left (37), Right (39), Up (38), Down (40) 1985 * Numerals: 0 - 9 1986 * Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~ 1987 * System: Escape (27), Space (32), Shift (16), Tab (9) 1988 1989 As of MSIE 5, the event also fires for the following keys: 1990 * Editing: Backspace (8) 1991 * Navigation: PageUp (33), PageDown (34) 1992 * System: Shift+Tab (9) 1993 1994 Documentation about the keyup event: 1995 http://msdn.microsoft.com/en-us/library/ms536940(VS.85).aspx 1996 1997 For further gory details and a different implementation see: 1998 http://code.google.com/p/doctype/source/browse/trunk/goog/events/keycodes.js 1999 http://code.google.com/p/doctype/source/browse/trunk/goog/events/keyhandler.js 2000 2001 Opera keydown/keyup: 2002 These events fire for all keys, including for modifiers. 2003 keyCode is always set. 2004 charCode is never set. 2005 which is always set. 2006 keyIdentifier is always undefined. 2007 2008 Opera keypress: 2009 This event fires for all keys, except for modifiers themselves. 2010 keyCode is always set. 2011 charCode is never set. 2012 which is set for all characters. which = 0 for several virtual keys. 2013 which is known to be set for: Backspace (8), Tab (9), Enter (13), Shift 2014 (16), Control (17), Alt (18), Pause (19), Escape (27), End (35), Home 2015 (36), Insert (45), Delete (46), NumLock (144). 2016 which is known to be unset for: F1 - F12, PageUp (33), PageDown (34), Left 2017 (37), Up (38), Right (39), Down (40). 2018 keyIdentifier is always undefined. 2019 2020 MSIE keyup/keypress/keydown: 2021 Event firing conditions are described above. 2022 keyCode is always set. 2023 charCode is never set. 2024 which is never set. 2025 keyIdentifier is always undefined. 2026 2027 Webkit keydown/keyup: 2028 These events fires for all keys, including for modifiers. 2029 keyCode is always set. 2030 charCode is never set. 2031 which is always set. 2032 keyIdentifier is always set. 2033 2034 Webkit keypress: 2035 This event fires for characters keys, similarly to MSIE (see above info). 2036 keyCode is always set. 2037 charCode is always set for all characters. 2038 which is always set. 2039 keyIdentifier is null. 2040 2041 Gecko keydown/keyup: 2042 These events fire for all keys, including for modifiers. 2043 keyCode is always set. 2044 charCode is never set. 2045 which is always set. 2046 keyIdentifier is always undefined. 2047 2048 Gecko keypress: 2049 This event fires for all keys, except for modifiers themselves. 2050 keyCode is only set for virtual keys, not for characters. 2051 charCode is always set for all characters. 2052 which is always set for all characters and for the Enter virtual key. 2053 keyIdentifier is always undefined. 2054 2055 Another important difference between the KeyboardEventListener class and the 2056 getKey() function is that the class tries to ensure that the keypress event 2057 is fired for the handler, even if the Web browser does not do it natively. 2058 Also, the class tries to provide a consistent approach to keyboard event 2059 repetition when the user holds down a key for longer periods of time, by 2060 repeating only the keypress event. 2061 2062 On Linux, Opera, Firefox and Konqueror do not repeat the keydown event, only 2063 keypress. On Windows, Opera, Firefox and MSIE do repeat the keydown and 2064 keypress events while the user holds down the key. Webkit repeats the 2065 keydown and the keypress (when it fires) events on both systems. 2066 2067 The default action can be prevented for during keydown in MSIE, and during 2068 keypress for the other browsers. In Webkit when keypress doesn't fire, 2069 keydown needs to be prevented. 2070 2071 The KeyboardEventListener class tries to bring consistency. The keydown 2072 event never repeats, only the keypress event repeats and it always fires for 2073 all keys. The keypress event never fires for modifiers. Events should always 2074 be prevented during keypress - the class deals with preventing the event 2075 during keydown or keypress as needed in Webkit and MSIE. 2076 2077 If no code/keyIdentifier is given by the browser, the getKey() function 2078 returns null. In the case of the KeyboardEventListener class, keyCode_ 2079 / key_ / charCode_ / char_ will be null or undefined. 2080 */ 2081 2082 /** 2083 * During a keyboard event flow, this holds the current key code, starting 2084 * from the <code>keydown</code> event. 2085 * 2086 * @private 2087 * @type Number 2088 */ 2089 var keyCode_ = null; 2090 2091 /** 2092 * During a keyboard event flow, this holds the current key, starting from the 2093 * <code>keydown</code> event. 2094 * 2095 * @private 2096 * @type String 2097 */ 2098 var key_ = null; 2099 2100 /** 2101 * During a keyboard event flow, this holds the current character code, 2102 * starting from the <code>keypress</code> event. 2103 * 2104 * @private 2105 * @type Number 2106 */ 2107 var charCode_ = null; 2108 2109 /** 2110 * During a keyboard event flow, this holds the current character, starting 2111 * from the <code>keypress</code> event. 2112 * 2113 * @private 2114 * @type String 2115 */ 2116 var char_ = null; 2117 2118 /** 2119 * True if the current keyboard event is repeating. This happens when the user 2120 * holds down a key for longer periods of time. 2121 * 2122 * @private 2123 * @type Boolean 2124 */ 2125 var repeat_ = false; 2126 2127 2128 if (!handlers_) { 2129 throw new TypeError('The first argument must be of type an object.'); 2130 } 2131 2132 if (!handlers_.keydown && !handlers_.keypress && !handlers_.keyup) { 2133 throw new TypeError('The provided handlers object has no keyboard event' + 2134 'handler.'); 2135 } 2136 2137 if (handlers_.keydown && typeof handlers_.keydown !== 'function') { 2138 throw new TypeError('The keydown event handler is not a function!'); 2139 } 2140 if (handlers_.keypress && typeof handlers_.keypress !== 'function') { 2141 throw new TypeError('The keypress event handler is not a function!'); 2142 } 2143 if (handlers_.keyup && typeof handlers_.keyup !== 'function') { 2144 throw new TypeError('The keyup event handler is not a function!'); 2145 } 2146 2147 /** 2148 * Attach the keyboard event listeners to the current DOM element. 2149 */ 2150 this.attach = function () { 2151 keyCode_ = null; 2152 key_ = null; 2153 charCode_ = null; 2154 char_ = null; 2155 repeat_ = false; 2156 2157 // FIXME: I have some ideas for a solution to the problem of having multiple 2158 // event handlers like these attached to the same element. Somehow, only one 2159 // should do all the needed work. 2160 2161 elem_.addEventListener('keydown', keydown, false); 2162 elem_.addEventListener('keypress', keypress, false); 2163 elem_.addEventListener('keyup', keyup, false); 2164 }; 2165 2166 /** 2167 * Detach the keyboard event listeners from the current DOM element. 2168 */ 2169 this.detach = function () { 2170 elem_.removeEventListener('keydown', keydown, false); 2171 elem_.removeEventListener('keypress', keypress, false); 2172 elem_.removeEventListener('keyup', keyup, false); 2173 2174 keyCode_ = null; 2175 key_ = null; 2176 charCode_ = null; 2177 char_ = null; 2178 repeat_ = false; 2179 }; 2180 2181 /** 2182 * Dispatch an event. 2183 * 2184 * <p>This function simply invokes the handler for the event of the given 2185 * <var>type</var>. The handler receives the <var>ev</var> event. 2186 * 2187 * @private 2188 * @param {String} type The event type to dispatch. 2189 * @param {Event} ev The DOM Event object to dispatch to the handler. 2190 */ 2191 function dispatch (type, ev) { 2192 if (!handlers_[type]) { 2193 return; 2194 } 2195 2196 var handler = handlers_[type]; 2197 2198 if (type === ev.type) { 2199 handler.call(elem_, ev); 2200 2201 } else { 2202 // This happens when the keydown event tries to dispatch a keypress event. 2203 2204 // FIXME: I could use createEvent() ... food for thought for later. 2205 2206 /** @ignore */ 2207 var ev_new = {}; 2208 pwlib.extend(ev_new, ev); 2209 ev_new.type = type; 2210 2211 // Make sure preventDefault() is not borked... 2212 /** @ignore */ 2213 ev_new.preventDefault = function () { 2214 ev.preventDefault(); 2215 }; 2216 2217 handler.call(elem_, ev_new); 2218 } 2219 }; 2220 2221 /** 2222 * The <code>keydown</code> event handler. This function determines the key 2223 * pressed by the user, and checks if the <code>keypress</code> event will 2224 * fire in the current Web browser, or not. If it does not, a synthetic 2225 * <code>keypress</code> event will be fired. 2226 * 2227 * @private 2228 * @param {Event} ev The DOM Event object. 2229 */ 2230 function keydown (ev) { 2231 var prevKey = key_; 2232 2233 charCode_ = null; 2234 char_ = null; 2235 2236 findKeyCode(ev); 2237 2238 ev.keyCode_ = keyCode_; 2239 ev.key_ = key_; 2240 ev.repeat_ = key_ && prevKey === key_ ? true : false; 2241 2242 repeat_ = ev.repeat_; 2243 2244 // When the user holds down a key for a longer period of time, the keypress 2245 // event is generally repeated. However, in Webkit keydown is repeated (and 2246 // keypress if it fires keypress for the key). As such, we do not dispatch 2247 // the keydown event when a key event starts to be repeated. 2248 if (!repeat_) { 2249 dispatch('keydown', ev); 2250 } 2251 2252 // MSIE and Webkit only fire the keypress event for characters 2253 // (alpha-numeric and symbols). 2254 if (!isModifierKey(key_) && !firesKeyPress(ev)) { 2255 ev.type_ = 'keydown'; 2256 keypress(ev); 2257 } 2258 }; 2259 2260 /** 2261 * The <code>keypress</code> event handler. This function determines the 2262 * character generated by the keyboard event. 2263 * 2264 * @private 2265 * @param {Event} ev The DOM Event object. 2266 */ 2267 function keypress (ev) { 2268 // We reuse the keyCode_/key_ from the keydown event, because ev.keyCode 2269 // generally holds the character code during the keypress event. 2270 // However, if keyCode_ is not available, try to determine the key for this 2271 // event as well. 2272 if (!keyCode_) { 2273 findKeyCode(ev); 2274 repeat_ = false; 2275 } 2276 2277 ev.keyCode_ = keyCode_; 2278 ev.key_ = key_; 2279 2280 findCharCode(ev); 2281 2282 ev.charCode_ = charCode_; 2283 ev.char_ = char_; 2284 2285 // Any subsequent keypress event is considered a repeated keypress (the user 2286 // is holding down the key). 2287 ev.repeat_ = repeat_; 2288 if (!repeat_) { 2289 repeat_ = true; 2290 } 2291 2292 if (!isModifierKey(key_)) { 2293 dispatch('keypress', ev); 2294 } 2295 }; 2296 2297 /** 2298 * The <code>keyup</code> event handler. 2299 * 2300 * @private 2301 * @param {Event} ev The DOM Event object. 2302 */ 2303 function keyup (ev) { 2304 /* 2305 * Try to determine the keyCode_ for keyup again, even if we might already 2306 * have it from keydown. This is needed because the user might press some 2307 * key which only generates the keydown and keypress events, after which 2308 * a sudden keyup event is fired for a completely different key. 2309 * 2310 * Example: in Opera press F2 then Escape. It will first generate two 2311 * events, keydown and keypress, for the F2 key. When you press Escape to 2312 * close the dialog box, the script receives keyup for Escape. 2313 */ 2314 findKeyCode(ev); 2315 2316 ev.keyCode_ = keyCode_; 2317 ev.key_ = key_; 2318 2319 // Provide the character info from the keypress event in keyup as well. 2320 ev.charCode_ = charCode_; 2321 ev.char_ = char_; 2322 2323 dispatch('keyup', ev); 2324 2325 keyCode_ = null; 2326 key_ = null; 2327 charCode_ = null; 2328 char_ = null; 2329 repeat_ = false; 2330 }; 2331 2332 /** 2333 * Tells if the <var>key</var> is a modifier or not. 2334 * 2335 * @private 2336 * @param {String} key The key name. 2337 * @returns {Boolean} True if the <var>key</var> is a modifier, or false if 2338 * not. 2339 */ 2340 function isModifierKey (key) { 2341 switch (key) { 2342 case 'Shift': 2343 case 'Control': 2344 case 'Alt': 2345 case 'Meta': 2346 case 'Win': 2347 return true; 2348 default: 2349 return false; 2350 } 2351 }; 2352 2353 /** 2354 * Tells if the current Web browser will fire the <code>keypress</code> event 2355 * for the current <code>keydown</code> event object. 2356 * 2357 * @private 2358 * @param {Event} ev The DOM Event object. 2359 * @returns {Boolean} True if the Web browser will fire 2360 * a <code>keypress</code> event, or false if not. 2361 */ 2362 function firesKeyPress (ev) { 2363 // Gecko does not fire keypress for the Up/Down arrows when the target is an 2364 // input element. 2365 if ((key_ === 'Up' || key_ === 'Down') && pwlib.browser.gecko && ev.target 2366 && ev.target.tagName.toLowerCase() === 'input') { 2367 return false; 2368 } 2369 2370 if (!pwlib.browser.msie && !pwlib.browser.webkit) { 2371 return true; 2372 } 2373 2374 // Check if the key is a character key, or not. 2375 // If it's not a character, then keypress will not fire. 2376 // Known exceptions: keypress fires for Space, Enter and Escape in MSIE. 2377 if (key_ && key_ !== 'Space' && key_ !== 'Enter' && key_ !== 'Escape' && 2378 key_.length !== 1) { 2379 return false; 2380 } 2381 2382 // Webkit doesn't fire keypress for Escape as well ... 2383 if (pwlib.browser.webkit && key_ === 'Escape') { 2384 return false; 2385 } 2386 2387 // MSIE does not fire keypress if you hold Control / Alt down, while Shift 2388 // is off. Albeit, based on testing I am not completely sure if Shift needs 2389 // to be down or not. Sometimes MSIE won't fire keypress even if I hold 2390 // Shift down, and sometimes it does. Eh. 2391 if (pwlib.browser.msie && !ev.shiftKey && (ev.ctrlKey || ev.altKey)) { 2392 return false; 2393 } 2394 2395 return true; 2396 }; 2397 2398 /** 2399 * Determine the key and the key code for the current DOM Event object. This 2400 * function updates the <var>keyCode_</var> and the <var>key_</var> variables 2401 * to hold the result. 2402 * 2403 * @private 2404 * @param {Event} ev The DOM Event object. 2405 */ 2406 function findKeyCode (ev) { 2407 /* 2408 * If the event has no keyCode/which/keyIdentifier values, then simply do 2409 * not overwrite any existing keyCode_/key_. 2410 */ 2411 if (ev.type === 'keyup' && !ev.keyCode && !ev.which && (!ev.keyIdentifier || 2412 ev.keyIdentifier === 'Unidentified' || ev.keyIdentifier === 'U+0000')) { 2413 return; 2414 } 2415 2416 keyCode_ = null; 2417 key_ = null; 2418 2419 // Try to use keyCode/which. 2420 if (ev.keyCode || ev.which) { 2421 keyCode_ = ev.keyCode || ev.which; 2422 2423 // Fix Webkit quirks 2424 if (pwlib.browser.webkit) { 2425 // Old Webkit gives keyCode 25 when Shift+Tab is used. 2426 if (keyCode_ == 25 && this.shiftKey) { 2427 keyCode_ = pwlib.dom.keyNames.Tab; 2428 } else if (keyCode_ >= 63232 && keyCode_ in pwlib.dom.keyCodes_Safari2) { 2429 // Old Webkit gives wrong values for several keys. 2430 keyCode_ = pwlib.dom.keyCodes_Safari2[keyCode_]; 2431 } 2432 } 2433 2434 // Fix keyCode quirks in all browsers. 2435 if (keyCode_ in pwlib.dom.keyCodes_fixes) { 2436 keyCode_ = pwlib.dom.keyCodes_fixes[keyCode_]; 2437 } 2438 2439 key_ = pwlib.dom.keyCodes[keyCode_] || String.fromCharCode(keyCode_); 2440 2441 return; 2442 } 2443 2444 // Try to use ev.keyIdentifier. This is only available in Webkit and 2445 // Konqueror 4, each having some quirks. Sometimes the property is needed, 2446 // because keyCode/which are not always available. 2447 2448 var key = null, 2449 keyCode = null, 2450 id = ev.keyIdentifier; 2451 2452 if (!id || id === 'Unidentified' || id === 'U+0000') { 2453 return; 2454 } 2455 2456 if (id.substr(0, 2) === 'U+') { 2457 // Webkit gives character codes using the 'U+XXXX' notation, as per spec. 2458 keyCode = parseInt(id.substr(2), 16); 2459 2460 } else if (id.length === 1) { 2461 // Konqueror 4 implements keyIdentifier, and they provide the Unicode 2462 // character directly, instead of using the 'U+XXXX' notation. 2463 keyCode = id.charCodeAt(0); 2464 key = id; 2465 2466 } else { 2467 /* 2468 * Common keyIdentifiers like 'PageDown' are used as they are. 2469 * We determine the common keyCode used by Web browsers, from the 2470 * pwlib.dom.keyNames object. 2471 */ 2472 keyCode_ = pwlib.dom.keyNames[id] || null; 2473 key_ = id; 2474 2475 return; 2476 } 2477 2478 // Some keyIdentifiers like 'U+007F' (127: Delete) need to become key names. 2479 if (keyCode in pwlib.dom.keyCodes && (keyCode <= 32 || keyCode == 127 || 2480 keyCode == 144)) { 2481 key_ = pwlib.dom.keyCodes[keyCode]; 2482 } else { 2483 if (!key) { 2484 key = String.fromCharCode(keyCode); 2485 } 2486 2487 // Konqueror gives lower-case chars 2488 key_ = key.toUpperCase(); 2489 if (key !== key_) { 2490 keyCode = key_.charCodeAt(0); 2491 } 2492 } 2493 2494 // Correct the keyCode, make sure it's a common keyCode, not the Unicode 2495 // decimal representation of the character. 2496 if (key_ === 'Delete' || key_.length === 1 && key_ in pwlib.dom.keyNames) { 2497 keyCode = pwlib.dom.keyNames[key_]; 2498 } 2499 2500 keyCode_ = keyCode; 2501 }; 2502 2503 /** 2504 * Determine the character and the character code for the current DOM Event 2505 * object. This function updates the <var>charCode_</var> and the 2506 * <var>char_</var> variables to hold the result. 2507 * 2508 * @private 2509 * @param {Event} ev The DOM Event object. 2510 */ 2511 function findCharCode (ev) { 2512 charCode_ = null; 2513 char_ = null; 2514 2515 // Webkit and Gecko implement ev.charCode. 2516 if (ev.charCode) { 2517 charCode_ = ev.charCode; 2518 char_ = String.fromCharCode(ev.charCode); 2519 2520 return; 2521 } 2522 2523 // Try the keyCode mess. 2524 if (ev.keyCode || ev.which) { 2525 var keyCode = ev.keyCode || ev.which; 2526 2527 var force = false; 2528 2529 // We accept some keyCodes. 2530 switch (keyCode) { 2531 case pwlib.dom.keyNames.Tab: 2532 case pwlib.dom.keyNames.Enter: 2533 case pwlib.dom.keyNames.Space: 2534 force = true; 2535 } 2536 2537 // Do not consider the keyCode a character code, if during the keydown 2538 // event it was determined the key does not generate a character, unless 2539 // it's Tab, Enter or Space. 2540 if (!force && key_ && key_.length !== 1) { 2541 return; 2542 } 2543 2544 // If the keypress event at hand is synthetically dispatched by keydown, 2545 // then special treatment is needed. This happens only in Webkit and MSIE. 2546 if (ev.type_ === 'keydown') { 2547 var key = pwlib.dom.keyCodes[keyCode]; 2548 // Check if the keyCode points to a single character. 2549 // If it does, use it. 2550 if (key && key.length === 1) { 2551 charCode_ = key.charCodeAt(0); // keyCodes != charCodes 2552 char_ = key; 2553 } 2554 } else if (keyCode >= 32 || force) { 2555 // For normal keypress events, we are done. 2556 charCode_ = keyCode; 2557 char_ = String.fromCharCode(keyCode); 2558 } 2559 2560 if (charCode_) { 2561 return; 2562 } 2563 } 2564 2565 /* 2566 * Webkit and Konqueror do not provide a keyIdentifier in the keypress 2567 * event, as per spec. However, in the unlikely case when the keyCode is 2568 * missing, and the keyIdentifier is available, we use it. 2569 * 2570 * This property might be used when a synthetic keypress event is generated 2571 * by the keydown event, and keyCode/charCode/which are all not available. 2572 */ 2573 2574 var c = null, 2575 charCode = null, 2576 id = ev.keyIdentifier; 2577 2578 if (id && id !== 'Unidentified' && id !== 'U+0000' && 2579 (id.substr(0, 2) === 'U+' || id.length === 1)) { 2580 2581 // Characters in Konqueror... 2582 if (id.length === 1) { 2583 charCode = id.charCodeAt(0); 2584 c = id; 2585 2586 } else { 2587 // Webkit uses the 'U+XXXX' notation as per spec. 2588 charCode = parseInt(id.substr(2), 16); 2589 } 2590 2591 if (charCode == pwlib.dom.keyNames.Tab || 2592 charCode == pwlib.dom.keyNames.Enter || 2593 charCode >= 32 && charCode != 127 && 2594 charCode != pwlib.dom.keyNames.NumLock) { 2595 2596 charCode_ = charCode; 2597 char_ = c || String.fromCharCode(charCode); 2598 2599 return; 2600 } 2601 } 2602 2603 // Try to use the key determined from the previous keydown event, if it 2604 // holds a character. 2605 if (key_ && key_.length === 1) { 2606 charCode_ = key_.charCodeAt(0); 2607 char_ = key_; 2608 } 2609 }; 2610 2611 this.attach(); 2612 }; 2613 2614 // Check out the libmacrame project: http://code.google.com/p/libmacrame. 2615 2616 // vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix: 2617 2618