001 
002 /*
003  *  JScripter Standard 1.0 - To Script In Java
004  *  Copyright (C) 2008-2011  J.J.Liu<jianjunliu@126.com> <http://www.jscripter.org>
005  *  
006  *  This program is free software: you can redistribute it and/or modify
007  *  it under the terms of the GNU Affero General Public License as published by
008  *  the Free Software Foundation, either version 3 of the License, or
009  *  (at your option) any later version.
010  *  
011  *  This program is distributed in the hope that it will be useful,
012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014  *  GNU Affero General Public License for more details.
015  *  
016  *  You should have received a copy of the GNU Affero General Public License
017  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
018  */
019 
020 package jsx.ui.vect.canvas;
021 
022 import js.Id;
023 import js.Js;
024 import jsx.Configurable;
025 import jsx.core.ObjectLikes;
026 import jsx.ui.Component;
027 import jsx.ui.Widget;
028 import jsx.ui.dd.Mouse;
029 import jsx.ui.dd.event.DragMove;
030 import jsx.ui.dd.event.DragStart;
031 import jsx.ui.dd.event.DragStop;
032 import jsx.ui.dd.event.OnDragMove;
033 import jsx.ui.dd.event.OnDragStart;
034 import jsx.ui.dd.event.OnDragStop;
035 import jsx.ui.event.Position;
036 import jsx.ui.vect.Canvas;
037 
038 /**
039  * <p>An abstract base class for drawing tools that can interactively draw on a wrapped canvas.</p>
040  * <p>A drawing tool of a subclass of this one wraps a {@link Canvas} component and 
041  * draws a specific type of shapes in accordance with the {@link Mouse} handle to which 
042  * it listens mouse events.</p>
043  * <p>A {@link CanvasDraw} widget is {@link Configurable} and is also an event source 
044  * which may fire high level events.</p>
045  * 
046  * @author <a href="mailto:jianjunliu@126.com">J.J.Liu (Jianjun Liu)</a> at <a href="http://www.jscripter.org" target="_blank">http://www.jscripter.org</a>
047  */
048 public abstract class CanvasDraw extends Widget implements OnDragStart, OnDragMove, OnDragStop
049 {
050     /**
051      * <p>A global identifier for a configurable property of a {@link CanvasDraw} object.</p>
052      * <p>The identified configurable property of a {@link CanvasDraw} widget refers to 
053      * a {@link Canvas} widget that wraps the same component as the {@link CanvasDraw} 
054      * widget does.</p>
055      * @since 1.0
056      */
057     protected final static Id<Canvas> CANVAS = new Id<Canvas>();
058     /**
059      * <p>A global identifier for a configurable property of a {@link CanvasDraw} object.</p>
060      * <p>The identified configurable property of a {@link CanvasDraw} widget refers to 
061      * its {@link Mouse} handle to which the widget listens mouse events.</p>
062      * @since 1.0
063      */
064     protected final static Id<Mouse> MOUSE = new Id<Mouse>();
065     /**
066      * <p>A global identifier for a configurable property of a {@link CanvasDraw} object.</p>
067      * <p>The identified configurable property of a {@link CanvasDraw} widget refers to 
068      * a boolean value specifying whether the canvas drawing widget is in the drawing 
069      * mode.</p>
070      * @since 1.0
071      */
072     protected final static Id<Boolean> START = new Id<Boolean>();
073     /**
074      * <p>A global identifier for a configurable property of a {@link CanvasDraw} object.</p>
075      * <p>The identified configurable property of a {@link CanvasDraw} widget caches the 
076      * X coordinate of the left-corner of the canvas.</p>
077      * @since 1.0
078      */
079     protected final static Id<Double> X = new Id<Double>();
080     /**
081      * <p>A global identifier for a configurable property of a {@link CanvasDraw} object.</p>
082      * <p>The identified configurable property of a {@link CanvasDraw} widget caches the 
083      * Y coordinate of the left-corner of the canvas.</p>
084      * @since 1.0
085      */
086     protected final static Id<Double> Y = new Id<Double>();
087     /**
088      * <p>A global identifier for a configurable property of a {@link CanvasDraw} object.</p>
089      * <p>The identified configurable property of a {@link CanvasDraw} widget caches the 
090      * X dimension of the canvas.</p>
091      * @since 1.0
092      */
093     protected final static Id<Double> W = new Id<Double>();
094     /**
095      * <p>A global identifier for a configurable property of a {@link CanvasDraw} object.</p>
096      * <p>The identified configurable property of a {@link CanvasDraw} widget caches the 
097      * Y dimension of the canvas.</p>
098      * @since 1.0
099      */
100     protected final static Id<Double> H = new Id<Double>();
101 
102     /**
103      * <p>A typical constructor that constructs a wrapper widget of this type and forces 
104      * constructors of subclasses to pass a {@link Canvas} widget.</p>
105      * <p>This constructor invokes the typical constructor of the superclass passing 
106      * the component wrapped by the specified {@link Canvas} widget that is set to the 
107      * configurable property {@link #CANVAS} of the widget to be constructed.</p>
108      * @param c A canvas widget.
109      * @since 1.0
110      */
111     protected CanvasDraw(Canvas c) {
112         super(c.unwrap());
113         ini(this).var(CANVAS, c);
114     }
115 
116     /**
117      * <p>Gets the canvas widget of this canvas drawing wrapper.</p>
118      * <p>This method simply returns the configurable property {@link #CANVAS} of this 
119      * wrapper widget.</p>
120      * @return The canvas widget of this canvas drawing tool.
121      * @since 1.0
122      */
123     public final Canvas canvas() {
124         return ini(this).var(CANVAS);
125     }
126 
127     /**
128      * <p>Gets the mouse handle currently attached to this canvas drawing wrapper.</p>
129      * <p>This method simply returns the configurable property {@link #MOUSE} of this 
130      * wrapper widget.</p>
131      * @return The mouse widget attached to this canvas drawing tool.
132      * @since 1.0
133      */
134     public final Mouse getMouse() {
135         return ini(this).var(MOUSE);
136     }
137 
138     /**
139      * <p>Attaches a specified mouse handle to the canvas drawing widget.</p>
140      * <p>This method detaches the old mouse handle if there is one and attaches the 
141      * specified one to the current drawing widget enabling it to listen the necessary 
142      * types of mouse events from the mouse handle. Call this method from event listeners 
143      * with caution.</p>
144      * @param h The new mouse widget to be attached to the canvas drawing tool.
145      * @since 1.0
146      * @see #detach()
147      */
148     public final void attach(Mouse h) {
149         if (Js.be(getMouse())) {
150             detach();
151         }
152         ini(this).var(MOUSE, h);
153         h.addListener(DragStart.class, this);
154         h.addListener(DragMove .class, this);
155         h.addListener(DragStop .class, this);
156     }
157 
158     /**
159      * <p>Detaches the current mouse handle from the canvas drawing widget.</p>
160      * <p>This method removes the mouse handle currently attached to the drawing 
161      * widget and unregisters it as an event listener from the mouse widget with all 
162      * event types. Call the method from event listeners with caution.</p>
163      * @since 1.0
164      * @see #attach(Mouse)
165      */
166     public final void detach() {
167         Mouse h = getMouse();
168         if (Js.be(h)) {
169             h.removeListener(DragStart.class, this);
170             h.removeListener(DragMove .class, this);
171             h.removeListener(DragStop .class, this);
172             ObjectLikes.delete(ini(this), MOUSE);
173         }
174     }
175 
176     /**
177      * <p>Limits a numerical value in a range exclusive.</p>
178      * <p>A subclass may also call this method to meet its particular needs.</p>
179      * @param x A value to be limited.
180      * @param min The start value of a range.
181      * @param max The end value of the range.
182      * @return The value limited with the specified range.
183      * @since 1.0
184      */
185     protected static final double limit(double x, double min, double max) {
186         return x < min ? min : x > max ? max : x;
187     }
188 
189     /**
190      * <p>Limits an X coordinate in a {@link Canvas} widget with a padding.</p>
191      * <p>A subclass may also call this method to meet its particular needs.</p>
192      * @param c A canvas widget limiting coordinates.
193      * @param w A padding the specified canvas has to leave when it limits coordinates.
194      * @param x An X coordinate to be limited.
195      * @return The X coordinate limited with the specified canvas and padding.
196      * @since 1.0
197      */
198     protected static final double x(Canvas c, double w, double x) {
199         return limit(x, w, Canvas.width(c) - w);
200     }
201 
202     /**
203      * <p>Limits an YX coordinate in a {@link Canvas} widget with a padding.</p>
204      * <p>A subclass may also call this method to meet its particular needs.</p>
205      * @param c A canvas widget limiting coordinates.
206      * @param w A padding the specified canvas has to leave when it limits coordinates.
207      * @param y A Y coordinate to be limited.
208      * @return The Y coordinate limited with the specified canvas and padding.
209      * @since 1.0
210      */
211     protected static final double y(Canvas c, double w, double y) {
212         return limit(y, w, Canvas.height(c) - w);
213     }
214 
215     /**
216      * <p>Moves the current position of a canvas.</p>
217      * <p>This method moves the current position of a specified {@link Canvas} widget 
218      * in accordance with the specified mouse {@link Position} data and the position data 
219      * cached by the specified canvas drawing tool.</p>
220      * <p>A subclass may also call this method to meet its particular needs.</p>
221      * @param d A canvas drawing wrapper.
222      * @param c The canvas widget wrapped by the specified canvas drawing tool.
223      * @param evt A mouse position event.
224      * @since 1.0
225      */
226     protected static final void moveTo(CanvasDraw d, Canvas c, Position<?> evt) {
227         double w = Canvas.lineWidth(c);
228         Canvas.moveTo(
229                 c,
230                 x(c, w, ini(evt).var(Position.X).doubleValue() - ini(d).var(X)),
231                 y(c, w, ini(evt).var(Position.Y).doubleValue() - ini(d).var(Y))
232         );
233     }
234 
235     /**
236      * <p>Draws a line from the current position of a canvas.</p>
237      * <p>This method draws a line from the current position of a specified {@link Canvas} 
238      * widget to a new position computed with the specified mouse {@link Position} data 
239      * and the position data cached by the specified canvas drawing tool.</p>
240      * <p>A subclass may also call this method to meet its particular needs.</p>
241      * @param d A canvas drawing wrapper.
242      * @param c The canvas widget wrapped by the specified canvas drawing tool.
243      * @param evt A mouse position event.
244      * @since 1.0
245      */
246     protected static final void lineTo(CanvasDraw d, Canvas c, Position<?> evt) {
247         double w = Canvas.lineWidth(c);
248         Canvas.lineTo(
249                 c,
250                 x(c, w, ini(evt).var(Position.X).doubleValue() - ini(d).var(X)),
251                 y(c, w, ini(evt).var(Position.Y).doubleValue() - ini(d).var(Y))
252         );
253         Canvas.stroke(c);
254     }
255 
256     /**
257      * <p>Performs an action on the dispatched event.</p>
258      * <p>This method enters the drawing mode and initializes the canvas and the 
259      * internal caches.</p>
260      * @param evt The event dispatched to this listener.
261      * @since 1.0
262      */
263     public void onEvent(DragStart evt) {
264         if (Js.not(ini(this).var(START))) {
265             ini(this).var(START, true);
266             Component e = unwrap();
267             ini(this).var(X, e.pageLeft());
268             ini(this).var(Y, e.pageTop ());
269             Canvas c = canvas();
270             Canvas.beginPath(c);
271             moveTo(this, c, evt);
272         }
273     }
274 }