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-01 18:44:56 +0300 $
 21  */
 22 
 23 /**
 24  * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a>
 25  * @fileOverview Holds the ellipse tool implementation.
 26  */
 27 
 28 /**
 29  * @class The ellipse tool.
 30  *
 31  * @param {PaintWeb} app Reference to the main paint application object.
 32  */
 33 pwlib.tools.ellipse = function (app) {
 34   var _self         = this,
 35       clearInterval = app.win.clearInterval,
 36       config        = app.config,
 37       context       = app.buffer.context,
 38       gui           = app.gui,
 39       image         = app.image,
 40       MathMax       = Math.max,
 41       MathMin       = Math.min,
 42       mouse         = app.mouse,
 43       setInterval   = app.win.setInterval;
 44 
 45   /**
 46    * The interval ID used for invoking the drawing operation every few 
 47    * milliseconds.
 48    *
 49    * @private
 50    * @see PaintWeb.config.toolDrawDelay
 51    */
 52   var timer = null;
 53 
 54   /**
 55    * Tells if the <kbd>Shift</kbd> key is down or not. This is used by the 
 56    * drawing function.
 57    *
 58    * @private
 59    * @type Boolean
 60    * @default false
 61    */
 62   var shiftKey = false;
 63 
 64   /**
 65    * Tells if the drawing canvas needs to be updated or not.
 66    *
 67    * @private
 68    * @type Boolean
 69    * @default false
 70    */
 71   var needsRedraw = false;
 72 
 73   var K = 4*((Math.SQRT2-1)/3);
 74 
 75   /**
 76    * Holds the starting point on the <var>x</var> axis of the image, for the 
 77    * current drawing operation.
 78    *
 79    * @private
 80    * @type Number
 81    */
 82   var x0 = 0;
 83 
 84   /**
 85    * Holds the starting point on the <var>y</var> axis of the image, for the 
 86    * current drawing operation.
 87    *
 88    * @private
 89    * @type Number
 90    */
 91   var y0 = 0;
 92 
 93   /**
 94    * Tool deactivation event handler.
 95    */
 96   this.deactivate = function () {
 97     if (timer) {
 98       clearInterval(timer);
 99       timer = null;
100     }
101 
102     if (mouse.buttonDown) {
103       context.clearRect(0, 0, image.width, image.height);
104     }
105 
106     needsRedraw = false;
107 
108     return true;
109   };
110 
111   /**
112    * Initialize the drawing operation.
113    *
114    * @param {Event} ev The DOM Event object.
115    */
116   this.mousedown = function (ev) {
117     // The mouse start position
118     x0 = mouse.x;
119     y0 = mouse.y;
120 
121     if (!timer) {
122       timer = setInterval(_self.draw, config.toolDrawDelay);
123     }
124     shiftKey = ev.shiftKey;
125     needsRedraw = false;
126 
127     gui.statusShow('ellipseMousedown');
128 
129     return true;
130   };
131 
132   /**
133    * Store the <kbd>Shift</kbd> key state which is used by the drawing function.
134    *
135    * @param {Event} ev The DOM Event object.
136    */
137   this.mousemove = function (ev) {
138     shiftKey = ev.shiftKey;
139     needsRedraw = true;
140   };
141 
142   /**
143    * Perform the drawing operation. This function is called every few 
144    * milliseconds.
145    *
146    * <p>Hold down the <kbd>Shift</kbd> key to draw a circle.
147    * <p>Press <kbd>Escape</kbd> to cancel the drawing operation.
148    *
149    * @see PaintWeb.config.toolDrawDelay
150    */
151   this.draw = function () {
152     if (!needsRedraw) {
153       return;
154     }
155 
156     context.clearRect(0, 0, image.width, image.height);
157 
158     var rectx0 = MathMin(mouse.x, x0),
159         rectx1 = MathMax(mouse.x, x0),
160         recty0 = MathMin(mouse.y, y0),
161         recty1 = MathMax(mouse.y, y0);
162 
163     /*
164       ABCD - rectangle
165       A(rectx0, recty0), B(rectx1, recty0), C(rectx1, recty1), D(rectx0, recty1)
166     */
167 
168     var w = rectx1-rectx0,
169         h = recty1-recty0;
170 
171     if (!w || !h) {
172       needsRedraw = false;
173       return;
174     }
175 
176     // Constrain the ellipse to be a circle
177     if (shiftKey) {
178       if (w > h) {
179         recty1 = recty0+w;
180         if (recty0 == mouse.y) {
181           recty0 -= w-h;
182           recty1 -= w-h;
183         }
184         h = w;
185       } else {
186         rectx1 = rectx0+h;
187         if (rectx0 == mouse.x) {
188           rectx0 -= h-w;
189           rectx1 -= h-w;
190         }
191         w = h;
192       }
193     }
194 
195     // Ellipse radius
196     var rx = w/2,
197         ry = h/2; 
198 
199     // Ellipse center
200     var cx = rectx0+rx,
201         cy = recty0+ry;
202 
203     // Ellipse radius*Kappa, for the Bézier curve control points
204     rx *= K;
205     ry *= K;
206 
207     context.beginPath();
208 
209     // startX, startY
210     context.moveTo(cx, recty0);
211 
212     // Control points: cp1x, cp1y, cp2x, cp2y, destx, desty
213     // go clockwise: top-middle, right-middle, bottom-middle, then left-middle
214     context.bezierCurveTo(cx + rx, recty0, rectx1, cy - ry, rectx1, cy);
215     context.bezierCurveTo(rectx1, cy + ry, cx + rx, recty1, cx, recty1);
216     context.bezierCurveTo(cx - rx, recty1, rectx0, cy + ry, rectx0, cy);
217     context.bezierCurveTo(rectx0, cy - ry, cx - rx, recty0, cx, recty0);
218 
219     if (config.shapeType != 'stroke') {
220       context.fill();
221     }
222     if (config.shapeType != 'fill') {
223       context.stroke();
224     }
225 
226     context.closePath();
227 
228     needsRedraw = false;
229   };
230 
231   /**
232    * End the drawing operation, once the user releases the mouse button.
233    *
234    * @param {Event} ev The DOM Event object.
235    */
236   this.mouseup = function (ev) {
237     // Allow click+mousemove, not only mousedown+move+up
238     if (mouse.x == x0 && mouse.y == y0) {
239       mouse.buttonDown = true;
240       return true;
241     }
242 
243     if (timer) {
244       clearInterval(timer);
245       timer = null;
246     }
247 
248     shiftKey = ev.shiftKey;
249     _self.draw();
250     app.layerUpdate();
251     gui.statusShow('ellipseActive');
252 
253     return true;
254   };
255 
256   /**
257    * Allows the user to press <kbd>Escape</kbd> to cancel the drawing operation.
258    *
259    * @param {Event} ev The DOM Event object.
260    *
261    * @returns {Boolean} True if the drawing operation was cancelled, or false if 
262    * not.
263    */
264   this.keydown = function (ev) {
265     if (!mouse.buttonDown || ev.kid_ != 'Escape') {
266       return false;
267     }
268 
269     if (timer) {
270       clearInterval(timer);
271       timer = null;
272     }
273 
274     context.clearRect(0, 0, image.width, image.height);
275     mouse.buttonDown = false;
276     needsRedraw = false;
277 
278     gui.statusShow('ellipseActive');
279 
280     return true;
281   };
282 };
283 
284 // vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix:
285 
286