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-08-24 13:18:05 +0300 $
 21  */
 22 
 23 /**
 24  * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a>
 25  * @fileOverview Holds the Bézier curve tool implementation.
 26  */
 27 
 28 /**
 29  * @class The Bézier curve tool.
 30  *
 31  * @param {PaintWeb} app Reference to the main paint application object.
 32  */
 33 pwlib.tools.bcurve = 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       mouse         = app.mouse,
 41       setInterval   = app.win.setInterval,
 42       snapXY        = app.toolSnapXY;
 43 
 44   /**
 45    * Holds the points in the Bézier curve being drawn.
 46    *
 47    * @private
 48    * @type Array
 49    */
 50   var points = [];
 51 
 52   /**
 53    * The interval ID used for invoking the drawing operation every few 
 54    * milliseconds.
 55    *
 56    * @private
 57    * @see PaintWeb.config.toolDrawDelay
 58    */
 59   var timer = null;
 60 
 61   /**
 62    * Tells if the <kbd>Shift</kbd> key is down or not. This is used by the 
 63    * drawing function.
 64    *
 65    * @private
 66    * @type Boolean
 67    * @default false
 68    */
 69   var shiftKey = false;
 70 
 71   /**
 72    * Tells if the drawing canvas needs to be updated or not.
 73    *
 74    * @private
 75    * @type Boolean
 76    * @default false
 77    */
 78   var needsRedraw = false;
 79 
 80   /**
 81    * The tool deactivation method, used for clearing the buffer.
 82    */
 83   this.deactivate = function () {
 84     if (timer) {
 85       clearInterval(timer);
 86       timer = null;
 87     }
 88 
 89     if (points.length > 0) {
 90       context.clearRect(0, 0, image.width, image.height);
 91     }
 92 
 93     needsRedraw = false;
 94     points = [];
 95 
 96     return true;
 97   };
 98 
 99   /**
100    * The <code>mousedown</code> event handler.
101    *
102    * @param {Event} ev The DOM Event object.
103    */
104   this.mousedown = function (ev) {
105     if (points.length === 0) {
106       gui.statusShow('bcurveSnapping');
107       points.push([mouse.x, mouse.y]);
108     }
109 
110     if (!timer) {
111       timer = setInterval(_self.draw, config.toolDrawDelay);
112     }
113 
114     shiftKey = ev.shiftKey;
115     needsRedraw = false;
116 
117     return true;
118   };
119 
120   /**
121    * Store the <kbd>Shift</kbd> key state which is used by the drawing function.
122    *
123    * @param {Event} ev The DOM Event object.
124    */
125   this.mousemove = function (ev) {
126     shiftKey = ev.shiftKey;
127     needsRedraw = true;
128   };
129 
130   /**
131    * Draw the Bézier curve, using the available points.
132    *
133    * @see PaintWeb.config.toolDrawDelay
134    */
135   this.draw = function () {
136     if (!needsRedraw) {
137       return;
138     }
139 
140     var n = points.length;
141 
142     // Add the temporary point while the mouse button is down.
143     if (mouse.buttonDown) {
144       if (shiftKey && n === 1) {
145         snapXY(points[0][0], points[0][1]);
146       }
147       points.push([mouse.x, mouse.y]);
148       n++;
149     }
150 
151     var p0 = points[0],
152         p1 = points[1],
153         p2 = points[2],
154         p3 = points[3] || points[2];
155 
156     if (mouse.buttonDown) {
157       points.pop();
158     }
159 
160     context.clearRect(0, 0, image.width, image.height);
161 
162     if (!n) {
163       needsRedraw = false;
164       return;
165     }
166 
167     // Draw the main line
168     if (n === 2) {
169       context.beginPath();
170       context.moveTo(p0[0], p0[1]+2);
171       context.lineTo(p1[0], p1[1]+2);
172 
173       if (config.shapeType === 'fill') {
174         var lineWidth   = context.lineWidth,
175             strokeStyle = context.strokeStyle;
176 
177         context.lineWidth   = 1;
178         context.strokeStyle = context.fillStyle;
179       }
180 
181       context.stroke();
182       context.closePath();
183 
184       if (config.shapeType === 'fill') {
185         context.lineWidth   = lineWidth;
186         context.strokeStyle = strokeStyle;
187       }
188 
189       needsRedraw = false;
190       return;
191     }
192 
193     // Draw the Bézier curve
194 
195     context.beginPath();
196     context.moveTo(p0[0], p0[1]);
197     context.bezierCurveTo(
198       p2[0], p2[1],
199       p3[0], p3[1],
200       p1[0], p1[1]);
201 
202     if (config.shapeType !== 'stroke') {
203       context.fill();
204     }
205 
206     if (config.shapeType !== 'fill') {
207       context.stroke();
208     }
209 
210     context.closePath();
211 
212     needsRedraw = false;
213   };
214 
215   /**
216    * The <code>mouseup</code> event handler. This method stores the current 
217    * mouse coordinates as a point to be used for drawing the Bézier curve.
218    *
219    * @param {Event} ev The DOM Event object.
220    */
221   this.mouseup = function (ev) {
222     var n = points.length;
223 
224     // Allow click+mousemove+click, not only mousedown+mousemove+mouseup.
225     // Do this only for the start point.
226     if (n === 1 && mouse.x === points[0][0] && mouse.y === points[0][1]) {
227       mouse.buttonDown = true;
228       return true;
229     }
230 
231     if (timer) {
232       clearInterval(timer);
233       timer = null;
234     }
235 
236     if (n === 1 && ev.shiftKey) {
237       snapXY(points[0][0], points[0][1]);
238     }
239 
240     // We need 4 points to draw the Bézier curve: start, end, and two control 
241     // points.
242     if (n < 4) {
243       points.push([mouse.x, mouse.y]);
244       needsRedraw = true;
245       n++;
246     }
247 
248     // Make sure the canvas is up-to-date.
249     shiftKey = ev.shiftKey;
250     _self.draw();
251 
252     if (n === 2 || n === 3) {
253       gui.statusShow('bcurveControlPoint' + (n-1));
254     } else if (n === 4) {
255       gui.statusShow('bcurveActive');
256       app.layerUpdate();
257       points = [];
258     }
259 
260     return true;
261   };
262 
263   /**
264    * The <code>keydown</code> event handler. This method allows the user to 
265    * press the <kbd>Escape</kbd> key to cancel the current drawing operation.
266    *
267    * @param {Event} ev The DOM Event object.
268    *
269    * @returns {Boolean} True if the keyboard shortcut was recognized, or false 
270    * if not.
271    */
272   this.keydown = function (ev) {
273     if (!points.length || ev.kid_ !== 'Escape') {
274       return false;
275     }
276 
277     if (timer) {
278       clearInterval(timer);
279       timer = null;
280     }
281 
282     context.clearRect(0, 0, image.width, image.height);
283 
284     points = [];
285     needsRedraw = false;
286     mouse.buttonDown = false;
287 
288     gui.statusShow('bcurveActive');
289 
290     return true;
291   };
292 };
293 
294 // vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix:
295 
296