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