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