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