1 /* 2 * © 2009 ROBO Design 3 * http://www.robodesign.ro 4 * 5 * $Date: 2009-04-21 14:31:38 +0300 $ 6 */ 7 8 /** 9 * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a> 10 * @fileOverview The paint application core code. 11 */ 12 13 /** 14 * @class The paint tool application object. 15 */ 16 function Painter () { 17 var _self = this; 18 19 /** 20 * Holds the buffer canvas and context references. 21 * @type Object 22 */ 23 this.buffer = {canvas: null, context: null}; 24 25 /** 26 * Holds the current layer ID, canvas and context references. 27 * @type Object 28 */ 29 this.layer = {id: null, canvas: null, context: null}; 30 31 /** 32 * The instance of the active tool object. 33 * 34 * @type Object 35 * @see PainterConfig.tool_default holds the ID of the tool which is activated 36 * when the application loads. 37 */ 38 this.tool = null; 39 40 /** 41 * Holds references to important DOM elements. 42 * 43 * @private 44 * @type Object 45 */ 46 this.elems = {}; 47 48 /** 49 * Holds the keyboard event listener object. 50 * 51 * @private 52 * @type lib.dom.KeyboardEventListener 53 * @see lib.dom.KeyboardEventListener The class dealing with the cross-browser 54 * differences in the DOM keyboard events. 55 */ 56 var kbListener_; 57 58 /** 59 * Initialize the paint application. 60 * @private 61 */ 62 function init () { 63 if (!window.lang) { 64 alert('Error: The language object is not available!'); 65 return; 66 } 67 68 if (!window.PaintTools) { 69 alert(lang.PaintToolsNotFound); 70 return; 71 } 72 73 if (!window.PainterConfig) { 74 alert(lang.PainterConfigNotFound); 75 return; 76 } 77 78 // This application does not yet implement layers support. 79 // However, there's only little additional work to be done for layers 80 // support. 81 _self.layer.id = 1; 82 83 // Find the canvas element. 84 _self.layer.canvas = document.getElementById('imageLayer'); 85 if (!_self.layer.canvas) { 86 alert(lang.errorCanvasNotFound); 87 return; 88 } 89 90 if (!_self.layer.canvas.getContext) { 91 alert(lang.errorGetContext); 92 return; 93 } 94 95 // Get the 2D canvas context. 96 _self.layer.context = _self.layer.canvas.getContext('2d'); 97 if (!_self.layer.context) { 98 alert(lang.errorGetContext); 99 return; 100 } 101 102 // Add the buffer canvas. 103 var container = _self.layer.canvas.parentNode; 104 _self.buffer.canvas = document.createElement('canvas'); 105 if (!_self.buffer.canvas) { 106 alert(lang.errorCanvasCreate); 107 return; 108 } 109 110 _self.buffer.canvas.id = 'imageBuffer'; 111 _self.buffer.canvas.width = _self.layer.canvas.width; 112 _self.buffer.canvas.height = _self.layer.canvas.height; 113 container.appendChild(_self.buffer.canvas); 114 115 _self.buffer.context = _self.buffer.canvas.getContext('2d'); 116 117 // Get the tools drop-down. 118 _self.elems.tool_select = document.getElementById('tool'); 119 if (!_self.elems.tool_select) { 120 alert(lang.errorToolSelect); 121 return; 122 } 123 _self.elems.tool_select.addEventListener('change', ev_tool_change, false); 124 125 // Activate the default tool. 126 _self.toolActivate(PainterConfig.tool_default); 127 128 // Attach the mousedown, mousemove and mouseup event listeners. 129 _self.buffer.canvas.addEventListener('mousedown', ev_canvas, false); 130 _self.buffer.canvas.addEventListener('mousemove', ev_canvas, false); 131 _self.buffer.canvas.addEventListener('mouseup', ev_canvas, false); 132 133 // Add the keyboard events handler. 134 kbListener_ = new lib.dom.KeyboardEventListener(window, 135 {keydown: ev_keyhandler, keypress: ev_keyhandler, keyup: ev_keyhandler}); 136 }; 137 138 /** 139 * The Canvas event handler. 140 * 141 * <p>This method determines the mouse position relative to the canvas 142 * element, after which it invokes the method of the currently active tool 143 * with the same name as the current event type. For example, for the 144 * <code>mousedown</code> event the <code><var>tool</var>.mousedown()</code> 145 * method is invoked. 146 * 147 * <p>The mouse coordinates are added to the <var>ev</var> DOM Event object: 148 * <var>ev.x_</var> and <var>ev.y_</var>. 149 * 150 * @private 151 * 152 * @param {Event} ev The DOM Event object. 153 * 154 * @returns The result returned by the event handler of the current tool. If 155 * no event handler was executed, false is returned. 156 */ 157 function ev_canvas (ev) { 158 if (!ev.type || !_self.tool) { 159 return false; 160 } 161 162 if (typeof ev.layerX != 'undefined') { // Firefox 163 ev.x_ = ev.layerX; 164 ev.y_ = ev.layerY; 165 } else if (typeof ev.offsetX != 'undefined') { // Opera 166 ev.x_ = ev.offsetX; 167 ev.y_ = ev.offsetY; 168 } 169 170 // Call the event handler of the active tool. 171 var func = _self.tool[ev.type]; 172 if (typeof func != 'function') { 173 return false; 174 } 175 176 res = func(ev); 177 178 /* 179 * If the event handler from the current tool does return false, it means it 180 * did not execute for some reason. For example, in a keydown event handler 181 * the keyboard shortcut does not match some criteria, thus the handler 182 * returns false, leaving the event continue its normal flow. 183 */ 184 if (res !== false && ev.preventDefault) { 185 ev.preventDefault(); 186 } 187 188 return res; 189 }; 190 191 /** 192 * The event handler for any changes made to the tool selector. 193 * 194 * @private 195 * @see Painter#toolActivate The method which does the actual drawing tool 196 * activation. 197 */ 198 function ev_tool_change () { 199 _self.toolActivate(this.value); 200 }; 201 202 /** 203 * Activate a drawing tool by ID. 204 * 205 * <p>The <var>id</var> provided must be available in the global {@link 206 * PaintTools} object. 207 * 208 * @param {String} id The ID of the drawing tool to be activated. 209 * 210 * @returns {Boolean} True if the tool has been activated, or false if not. 211 * 212 * @see PaintTools The object holding all the drawing tools. 213 */ 214 this.toolActivate = function (id) { 215 if (!id) { 216 return false; 217 } 218 219 // Find the tool object. 220 var tool = PaintTools[id]; 221 if (!tool) { 222 return false; 223 } 224 225 // Check if the current tool is the same as the desired one. 226 if (_self.tool && _self.tool instanceof tool) { 227 return true; 228 } 229 230 // Construct the new tool object. 231 var tool_obj = new tool(_self); 232 if (!tool_obj) { 233 alert(lang.errorToolActivate); 234 return false; 235 } 236 237 _self.tool = tool_obj; 238 239 // Update the tool drop-down. 240 _self.elems.tool_select.value = id; 241 242 return true; 243 }; 244 245 /** 246 * The global keyboard events handler. This makes all the keyboard shortcuts 247 * work in the web application. 248 * 249 * <p>This method determines the key the user pressed, based on the 250 * <var>ev</var> DOM Event object, taking into consideration any browser 251 * differences. One new property is added to the <var>ev</var> object: 252 * 253 * <ul> 254 * <li><var>ev.kid_</var> is a string holding the key and the modifiers list 255 * (<kbd>Control</kbd>, <kbd>Alt</kbd> and/or <kbd>Shift</kbd>). For 256 * example, if the user would press the key <kbd>A</kbd> while holding down 257 * <kbd>Control</kbd>, then <var>ev.kid_</var> would be "Control A". If the 258 * user would press "9" while holding down <kbd>Shift</kbd>, then 259 * <var>ev.kid_</var> would be "Shift 9". 260 * </ul> 261 * 262 * <p>In {@link PainterConfig.keys} one can setup the keyboard shortcuts. If 263 * the keyboard combination is found in that list, then the associated tool is 264 * activated. 265 * 266 * @private 267 * 268 * @param {Event} ev The DOM Event object. 269 * 270 * @see PainterConfig.keys The keyboard shortcuts configuration. 271 * @see lib.dom.KeyboardEventListener The class dealing with the cross-browser 272 * differences in the DOM keyboard events. 273 */ 274 function ev_keyhandler (ev) { 275 if (!ev || !ev.key_) { 276 return; 277 } 278 279 // Do not continue if the event target is some form input. 280 if (ev.target && ev.target.nodeName) { 281 switch (ev.target.nodeName.toLowerCase()) { 282 case 'input': 283 case 'select': 284 return; 285 } 286 } 287 288 // Determine the key ID. 289 ev.kid_ = ''; 290 var i, kmods = {altKey: 'Alt', ctrlKey: 'Control', shiftKey: 'Shift'}; 291 for (i in kmods) { 292 if (ev[i] && ev.key_ != kmods[i]) { 293 ev.kid_ += kmods[i] + ' '; 294 } 295 } 296 ev.kid_ += ev.key_; 297 298 /* 299 * Send the event to the canvas, and eventually to the keydown event handler 300 * of the currently active tool (if any). 301 * The effect of calling ev_canvas() is that the event object *might* have 302 * the x_ and y_ coordinate properties added. Additionally, if ev_canvas() 303 * returns some result, we can use it to cancel any global keyboard 304 * shortcut. 305 */ 306 var canvas_result = ev_canvas(ev); 307 if (canvas_result) { 308 return; 309 } 310 311 // If there's no event handler within active tool, or if the event handler 312 // does otherwise return false, then continue with the global keyboard 313 // shortcuts. 314 315 var gkey = PainterConfig.keys[ev.kid_]; 316 if (!gkey) { 317 return; 318 } 319 320 // Activate the tool associated with the current keyboard shortcut. 321 // Do this only once, for the keydown event. 322 if (ev.type == 'keydown' && gkey.tool) { 323 _self.toolActivate(gkey.tool); 324 } 325 326 if (ev.type == 'keypress' && ev.preventDefault) { 327 ev.preventDefault(); 328 } 329 }; 330 331 /** 332 * This method draws the buffer canvas on top of the current image layer, 333 * after which the buffer is cleared. This function is called each time when 334 * the user completes a drawing operation. 335 */ 336 this.layerUpdate = function () { 337 _self.layer.context.drawImage(_self.buffer.canvas, 0, 0); 338 _self.buffer.context.clearRect(0, 0, _self.buffer.canvas.width, _self.buffer.canvas.height); 339 }; 340 341 init(); 342 }; 343 344 if(window.addEventListener) { 345 window.addEventListener('load', function () { 346 if (window.Painter) { 347 // Create a Painter object instance. 348 window.PainterInstance = new Painter(); 349 } 350 }, false); } 351 352 // vim:set spell spl=en fo=wan1croql tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix: 353