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-07-06 16:20:38 +0300 $ 21 */ 22 23 /** 24 * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a> 25 * @fileOverview Holds the "Insert image" tool implementation. 26 */ 27 28 // TODO: allow inserting images from a different host, using server-side magic. 29 30 /** 31 * @class The "Insert image" tool. 32 * 33 * @param {PaintWeb} app Reference to the main paint application object. 34 */ 35 pwlib.tools.insertimg = function (app) { 36 var _self = this, 37 canvasImage = app.image, 38 clearInterval = app.win.clearInterval, 39 config = app.config, 40 context = app.buffer.context, 41 gui = app.gui, 42 lang = app.lang, 43 MathAbs = Math.abs, 44 MathMin = Math.min, 45 MathRound = Math.round, 46 mouse = app.mouse, 47 setInterval = app.win.setInterval; 48 49 /** 50 * Holds the previous tool ID. 51 * 52 * @private 53 * @type String 54 */ 55 var prevTool = app.tool ? app.tool._id : null; 56 57 /** 58 * The interval ID used for invoking the drawing operation every few 59 * milliseconds. 60 * 61 * @private 62 * @see PaintWeb.config.toolDrawDelay 63 */ 64 var timer = null; 65 66 /** 67 * Tells if the <kbd>Shift</kbd> key is down or not. This is used by the 68 * drawing function. 69 * 70 * @private 71 * @type Boolean 72 * @default false 73 */ 74 var shiftKey = false; 75 76 /** 77 * Tells if the drawing canvas needs to be updated or not. 78 * 79 * @private 80 * @type Boolean 81 * @default false 82 */ 83 var needsRedraw = false; 84 85 /** 86 * Holds the starting point on the <var>x</var> axis of the image, for the 87 * current drawing operation. 88 * 89 * @private 90 * @type Number 91 */ 92 var x0 = 0; 93 94 /** 95 * Holds the starting point on the <var>y</var> axis of the image, for the 96 * current drawing operation. 97 * 98 * @private 99 * @type Number 100 */ 101 var y0 = 0; 102 103 /** 104 * Tells if the image element loaded or not. 105 * 106 * @private 107 * @type Boolean 108 */ 109 var imageLoaded = false; 110 111 /** 112 * Holds the image aspect ratio, used by the resize method. 113 * 114 * @private 115 * @type Number 116 */ 117 var imageRatio = 1; 118 119 /** 120 * Holds the DOM image element. 121 * 122 * @private 123 * @type Element 124 */ 125 var imageElement = null; 126 127 /** 128 * Holds the image address. 129 * @type String 130 */ 131 if (!this.url) { 132 this.url = 'http://'; 133 } 134 135 /** 136 * The tool preactivation code. This function asks the user to provide an URL 137 * to the image which is desired to be inserted into the canvas. 138 * 139 * @returns {Boolean} True if the URL provided is correct. False is returned 140 * if the URL is not provided or if it's incorrect. When false is returned the 141 * tool activation is cancelled. 142 */ 143 this.preActivate = function () { 144 if (!gui.elems.viewport) { 145 return false; 146 } 147 148 _self.url = prompt(lang.promptInsertimg, _self.url); 149 150 if (!_self.url || _self.url.toLowerCase() === 'http://') { 151 return false; 152 } 153 154 // Remember the URL. 155 pwlib.extend(true, _self.constructor.prototype, {url: _self.url}); 156 157 if (!pwlib.isSameHost(_self.url, app.win.location.host)) { 158 alert(lang.errorInsertimgHost); 159 return false; 160 } 161 162 return true; 163 }; 164 165 /** 166 * The tool activation event handler. This function is called once the 167 * previous tool has been deactivated. 168 */ 169 this.activate = function () { 170 imageElement = new Image(); 171 imageElement.addEventListener('load', ev_imageLoaded, false); 172 imageElement.src = _self.url; 173 174 return true; 175 }; 176 177 /** 178 * The tool deactivation event handler. 179 */ 180 this.deactivate = function () { 181 if (imageElement) { 182 imageElement = null; 183 } 184 185 if (timer) { 186 clearInterval(timer); 187 timer = null; 188 } 189 needsRedraw = false; 190 191 context.clearRect(0, 0, canvasImage.width, canvasImage.height); 192 193 return true; 194 }; 195 196 /** 197 * The <code>load</code> event handler for the image element. This method 198 * makes sure the image dimensions are synchronized with the zoom level, and 199 * draws the image on the canvas. 200 * 201 * @private 202 */ 203 function ev_imageLoaded () { 204 // Did the image already load? 205 if (imageLoaded) { 206 return; 207 } 208 209 // The default position for the inserted image is the top left corner of the visible area, taking into consideration the zoom level. 210 var x = MathRound(gui.elems.viewport.scrollLeft / canvasImage.canvasScale), 211 y = MathRound(gui.elems.viewport.scrollTop / canvasImage.canvasScale); 212 213 context.clearRect(0, 0, canvasImage.width, canvasImage.height); 214 215 try { 216 context.drawImage(imageElement, x, y); 217 } catch (err) { 218 alert(lang.errorInsertimg); 219 return; 220 } 221 222 imageLoaded = true; 223 needsRedraw = false; 224 225 if (!timer) { 226 timer = setInterval(_self.draw, config.toolDrawDelay); 227 } 228 229 gui.statusShow('insertimgLoaded'); 230 }; 231 232 /** 233 * The <code>mousedown</code> event handler. This method stores the current 234 * mouse location and the image aspect ratio for later reuse by the 235 * <code>draw()</code> method. 236 * 237 * @param {Event} ev The DOM Event object. 238 */ 239 this.mousedown = function (ev) { 240 if (!imageLoaded) { 241 alert(lang.errorInsertimgNotLoaded); 242 return false; 243 } 244 245 x0 = mouse.x; 246 y0 = mouse.y; 247 248 // The image aspect ratio - used by the draw() method when the user holds 249 // the Shift key down. 250 imageRatio = imageElement.width / imageElement.height; 251 shiftKey = ev.shiftKey; 252 253 gui.statusShow('insertimgResize'); 254 255 if (ev.stopPropagation) { 256 ev.stopPropagation(); 257 } 258 }; 259 260 /** 261 * The <code>mousemove</code> event handler. 262 * 263 * @param {Event} ev The DOM Event object. 264 */ 265 this.mousemove = function (ev) { 266 shiftKey = ev.shiftKey; 267 needsRedraw = true; 268 }; 269 270 /** 271 * Perform the drawing operation. When the mouse button is not down, the user 272 * is allowed to pick where he/she wants to insert the image element, inside 273 * the canvas. Once the <code>mousedown</code> event is fired, this method 274 * allows the user to resize the image inside the canvas. 275 * 276 * @see PaintWeb.config.toolDrawDelay 277 */ 278 this.draw = function () { 279 if (!imageLoaded || !needsRedraw) { 280 return; 281 } 282 283 context.clearRect(0, 0, canvasImage.width, canvasImage.height); 284 285 // If the user is holding down the mouse button, then allow him/her to 286 // resize the image. 287 if (mouse.buttonDown) { 288 var w = MathAbs(mouse.x - x0), 289 h = MathAbs(mouse.y - y0), 290 x = MathMin(mouse.x, x0), 291 y = MathMin(mouse.y, y0); 292 293 if (!w || !h) { 294 needsRedraw = false; 295 return; 296 } 297 298 // If the Shift key is down, constrain the image to have the same aspect 299 // ratio as the original image element. 300 if (shiftKey) { 301 if (w > h) { 302 if (y == mouse.y) { 303 y -= w-h; 304 } 305 h = MathRound(w/imageRatio); 306 } else { 307 if (x == mouse.x) { 308 x -= h-w; 309 } 310 w = MathRound(h*imageRatio); 311 } 312 } 313 314 context.drawImage(imageElement, x, y, w, h); 315 } else { 316 // If the mouse button is not down, simply allow the user to pick where 317 // he/she wants to insert the image element. 318 context.drawImage(imageElement, mouse.x, mouse.y); 319 } 320 321 needsRedraw = false; 322 }; 323 324 /** 325 * The <code>mouseup</code> event handler. This method completes the drawing 326 * operation by inserting the image in the layer canvas, and by activating the 327 * previous tool. 328 * 329 * @param {Event} ev The DOM Event object. 330 */ 331 this.mouseup = function (ev) { 332 if (!imageLoaded) { 333 return false; 334 } 335 336 if (timer) { 337 clearInterval(timer); 338 timer = null; 339 } 340 341 app.layerUpdate(); 342 343 if (prevTool) { 344 app.toolActivate(prevTool, ev); 345 } 346 347 if (ev.stopPropagation) { 348 ev.stopPropagation(); 349 } 350 }; 351 352 /** 353 * The <code>keydown</code> event handler allows users to press the 354 * <kbd>Escape</kbd> key to cancel the drawing operation and return to the 355 * previous tool. 356 * 357 * @param {Event} ev The DOM Event object. 358 * @returns {Boolean} True if the key was recognized, or false if not. 359 */ 360 this.keydown = function (ev) { 361 if (!prevTool || ev.kid_ != 'Escape') { 362 return false; 363 } 364 365 if (timer) { 366 clearInterval(timer); 367 timer = null; 368 } 369 370 mouse.buttonDown = false; 371 app.toolActivate(prevTool, ev); 372 373 return true; 374 }; 375 }; 376 377 // vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix: 378 379