1 /*
  2  * Copyright (C) 2008, 2009 Mihai Şucan
  3  *
  4  * This file is part of PaintWeb.
  5  *
  6  * PaintWeb is free software: you can redistribute it and/or modify
  7  * it under the terms of the GNU General Public License as published by
  8  * the Free Software Foundation, either version 3 of the License, or
  9  * (at your option) any later version.
 10  *
 11  * PaintWeb is distributed in the hope that it will be useful,
 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  * GNU General Public License for more details.
 15  *
 16  * You should have received a copy of the GNU General Public License
 17  * along with PaintWeb.  If not, see <http://www.gnu.org/licenses/>.
 18  *
 19  * $URL: http://code.google.com/p/paintweb $
 20  * $Date: 2009-06-16 21:56:40 +0300 $
 21  */
 22 
 23 /**
 24  * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a>
 25  * @fileOverview Allows users to draw in PaintWeb using the keyboard, without 
 26  * any pointing device.
 27  */
 28 
 29 /**
 30  * @class The MouseKeys extension.
 31  *
 32  * @param {PaintWeb} app Reference to the main paint application object.
 33  */
 34 pwlib.extensions.mousekeys = function (app) {
 35   var _self     = this,
 36       canvas    = app.buffer.canvas,
 37       config    = app.config,
 38       container = app.gui.elems.canvasContainer,
 39       doc       = app.doc,
 40       gui       = app.gui,
 41       image     = app.image,
 42       MathCeil  = Math.ceil,
 43       mouse     = app.mouse,
 44       tool      = app.tool || {};
 45 
 46   /**
 47    * Holds the current mouse movement speed in pixels.
 48    *
 49    * @private
 50    * @type Number
 51    */
 52   var speed = 1;
 53 
 54   /**
 55    * Holds the current mouse movement acceleration, taken from the 
 56    * configuration.
 57    *
 58    * @private
 59    * @type Number
 60    * @see PaintWeb.config.mousekeys.accel The mouse keys acceleration setting.
 61    */
 62   var accel = 0.1;
 63 
 64   /**
 65    * Holds a reference to the DOM element representing the pointer on top of the 
 66    * canvas element.
 67    *
 68    * @private
 69    * @type Element
 70    */
 71   var pointer = null;
 72   var pointerStyle = null;
 73 
 74   /**
 75    * The <code>extensionRegister</code> event handler. This initializes the 
 76    * extension by adding the pointer DOM element and by setting up the keyboard 
 77    * shortcuts.
 78    *
 79    * @returns {Boolean} True if the extension initialized successfully, or false 
 80    * if not.
 81    */
 82   this.extensionRegister = function () {
 83     accel = config.mousekeys.accel;
 84 
 85     pointer = doc.createElement('div');
 86     if (!pointer) {
 87       return false;
 88     }
 89     pointerStyle = pointer.style;
 90 
 91     pointer.className = gui.classPrefix + 'mousekeysPointer';
 92     pointerStyle.display = 'none';
 93     container.appendChild(pointer);
 94 
 95     canvas.addEventListener('mousemove', pointerMousemove, false);
 96 
 97     var action, keys, i, n, result = {};
 98 
 99     for (action in config.mousekeys.actions) {
100       keys = config.mousekeys.actions[action];
101 
102       for (i = 0, n = keys.length; i < n; i++) {
103         result[keys[i]] = {'extension': _self._id, 'action': action};
104       }
105     };
106 
107     pwlib.extend(config.keys, result);
108 
109     return true;
110   };
111 
112   /**
113    * The <code>extensionUnregister</code> event handler. This will remove the 
114    * pointer DOM element and the canvas event listener.
115    */
116   this.extensionUnregister = function () {
117     container.removeChild(pointer);
118     canvas.removeEventListener('mousemove', pointerMousemove, false);
119 
120     var key, kobj;
121     for (key in config.keys) {
122       kobj = config.keys[key];
123       if (kobj.extension === _self._id) {
124         delete config.keys[key];
125       }
126     }
127   };
128 
129   /**
130    * Track the virtual pointer coordinates, by updating the position of the 
131    * <var>pointer</var> element. This allows the keyboard users to see where 
132    * they moved the virtual pointer.
133    *
134    * @param {Event} ev The DOM Event object.
135    */
136   function pointerMousemove (ev) {
137     if (!('kobj_' in ev) || !('extension' in ev.kobj_) ||
138         ev.kobj_.extension !== _self._id) {
139       if (pointerStyle.display === 'block') {
140         pointerStyle.display = 'none';
141       }
142     }
143   };
144 
145   /**
146    * The <code>keydown</code> event handler.
147    *
148    * <p>This method requires a DOM Event object which has the 
149    * <var>ev.kobj_</var> object reference from the keyboard shortcuts 
150    * configuration. The <var>kobj_</var> object must have the <var>action</var> 
151    * property. Support for the "ButtonToggle" and the "ButtonClick" actions is 
152    * implemented.
153    * 
154    * <p>The "ButtonToggle" action essentially means that a mouse event will be 
155    * generated, either <code>mousedown</code> or <code>mouseup</code>. By 
156    * alternating these two events, this method allows the user to start and stop 
157    * the drawing operation at any moment using the keyboard shortcut they have 
158    * configured.
159    *
160    * <p>Under typical usage, the "ButtonClick" action translates the 
161    * <code>keydown</code> event to <code>mousedown</code>. The 
162    * <code>keyup</code> event handler will also fire the <code>mouseup</code> 
163    * event. This allows the user to simulate holding down the mouse button, 
164    * while he/she holds down a key.
165    *
166    * <p>A <code>click</code> event is always fired after the firing of 
167    * a <code>mouseup</code> event.
168    *
169    * <p>Irrespective of the key the user pressed, this method does always reset 
170    * the speed and acceleration of the pointer movement.
171    *
172    * @param {Event} ev The DOM Event object.
173    *
174    * @returns {Boolean} True if the keyboard shortcut was recognized, or false 
175    * if not.
176    *
177    * @see PaintWeb.config.mousekeys.actions The keyboard shortcuts configuration 
178    * object.
179    */
180   this.keydown = function (ev) {
181     speed = 1;
182     accel = config.mousekeys.accel;
183 
184     if (pointerStyle.display === 'none') {
185       pointerStyle.display = 'block';
186       pointerStyle.top  = (mouse.y * image.canvasScale) + 'px';
187       pointerStyle.left = (mouse.x * image.canvasScale) + 'px';
188 
189       if (mouse.buttonDown) {
190         pointer.className += ' ' + gui.classPrefix + 'mouseDown';
191       } else {
192         pointer.className = pointer.className.replace(' ' + gui.classPrefix 
193             + 'mouseDown', '');
194       }
195     }
196 
197     tool = app.tool || {};
198 
199     switch (ev.kobj_.action) {
200       case 'ButtonToggle':
201         if (mouse.buttonDown) {
202           mouse.buttonDown = false;
203           if ('mouseup' in tool) {
204             tool.mouseup(ev);
205           }
206           if ('click' in tool) {
207             tool.click(ev);
208           }
209 
210         } else {
211           mouse.buttonDown = true;
212 
213           if ('mousedown' in tool) {
214             tool.mousedown(ev);
215           }
216         }
217         break;
218 
219       case 'ButtonClick':
220         if (!mouse.buttonDown) {
221           mouse.buttonDown = true;
222 
223           if ('mousedown' in tool) {
224             tool.mousedown(ev);
225           }
226         }
227 
228         break;
229 
230       default:
231         return false;
232     }
233 
234     if (mouse.buttonDown) {
235       pointer.className += ' ' + gui.classPrefix + 'mouseDown';
236     } else {
237       pointer.className = pointer.className.replace(' ' + gui.classPrefix 
238           + 'mouseDown', '');
239     }
240 
241     return true;
242   };
243 
244   /**
245    * The <code>keypress</code> event handler.
246    *
247    * <p>This method requires a DOM Event object with a <var>ev.kobj_</var> 
248    * object reference to the keyboard shortcut configuration. The keyboard 
249    * shortcut configuration object must have the <var>action</var> property.
250    *
251    * <p>This event handler implements support for the following <var>param</var>  
252    * values: "SouthWest", "South", "SouthEast", "West", "East", "NorthWest", 
253    * "North" and "NorthEast", All of these values indicate the movement 
254    * direction. This method generates synthetic <var>movemove</var> events based 
255    * on the direction desired, effectively emulating the use of a real pointing 
256    * device.
257    *
258    * @param {Event} ev The DOM Event object.
259    *
260    * @returns {Boolean} True if the keyboard shortcut was recognized, or false 
261    * if not.
262    *
263    * @see PaintWeb.config.mousekeys.actions The keyboard shortcuts configuration 
264    * object.
265    */
266   this.keypress = function (ev) {
267     if (ev.shiftKey) {
268       speed += speed * accel * 3;
269     } else {
270       speed += speed * accel;
271     }
272 
273     var step = MathCeil(speed);
274 
275     switch (ev.kobj_.action) {
276       case 'SouthWest':
277         mouse.x -= step;
278         mouse.y += step;
279         break;
280       case 'South':
281         mouse.y += step;
282         break;
283       case 'SouthEast':
284         mouse.x += step;
285         mouse.y += step;
286         break;
287       case 'West':
288         mouse.x -= step;
289         break;
290       case 'East':
291         mouse.x += step;
292         break;
293       case 'NorthWest':
294         mouse.x -= step;
295         mouse.y -= step;
296         break;
297       case 'North':
298         mouse.y -= step;
299         break;
300       case 'NorthEast':
301         mouse.x += step;
302         mouse.y -= step;
303         break;
304       default:
305         return false;
306     }
307 
308     if (mouse.x < 0) {
309       mouse.x = 0;
310     } else if (mouse.x > image.width) {
311       mouse.x = image.width;
312     }
313 
314     if (mouse.y < 0) {
315       mouse.y = 0;
316     } else if (mouse.y > image.height) {
317       mouse.y = image.height;
318     }
319 
320     pointerStyle.top  = (mouse.y * image.canvasScale) + 'px';
321     pointerStyle.left = (mouse.x * image.canvasScale) + 'px';
322 
323     if ('mousemove' in tool) {
324       tool.mousemove(ev);
325     }
326 
327     return true;
328   };
329 
330   /**
331    * The <code>keyup</code> event handler.
332    *
333    * <p>This method requires a DOM Event object which has the 
334    * <var>ev.kobj_</var> object reference from the keyboard shortcuts 
335    * configuration. The <var>kobj_</var> object must have the <var>action</var> 
336    * property. Support for the "ButtonClick" action is implemented.
337    * 
338    * <p>Under typical usage, the "ButtonClick" action translates the 
339    * <code>keydown</code> event to <code>mousedown</code>. This event handler 
340    * fires the <code>mouseup</code> event. This allows the user to simulate 
341    * holding down the mouse button, while he/she holds down a key.
342    *
343    * <p>A <code>click</code> event is always fired after the firing of the 
344    * <code>mouseup</code> event.
345    *
346    * @param {Event} ev The DOM Event object.
347    *
348    * @returns {Boolean} True if the keyboard shortcut was recognized, or false 
349    * if not.
350    *
351    * @see PaintWeb.config.mousekeys.actions The keyboard shortcuts configuration 
352    * object.
353    */
354   this.keyup = function (ev) {
355     if (ev.kobj_.action == 'ButtonClick' && mouse.buttonDown) {
356       mouse.buttonDown = false;
357 
358       if ('mouseup' in tool) {
359         tool.mouseup(ev);
360       }
361       if ('click' in tool) {
362         tool.click(ev);
363       }
364 
365       pointer.className = pointer.className.replace(' ' + gui.classPrefix 
366           + 'mouseDown', '');
367       return true;
368     }
369 
370     return false;
371   };
372 };
373 
374 // vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix:
375 
376