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