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