1 /*
2
3 dsh-piccolo-identify Piccolo2D nodes for identifiable beans.
4 Copyright (c) 2007-2011 held jointly by the individual authors.
5
6 This library is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 3 of the License, or (at
9 your option) any later version.
10
11 This library is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with this library; if not, write to the Free Software Foundation,
18 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
19
20 > http://www.fsf.org/licensing/licenses/lgpl.html
21 > http://www.opensource.org/licenses/lgpl-license.php
22
23 */
24 package org.dishevelled.piccolo.identify;
25
26 import java.awt.Component;
27 import java.awt.Image;
28 import java.awt.Font;
29
30 import java.awt.geom.Dimension2D;
31
32 import java.beans.PropertyChangeEvent;
33 import java.beans.PropertyChangeListener;
34
35 import java.net.URL;
36
37 import org.piccolo2d.PNode;
38
39 import org.piccolo2d.event.PInputEvent;
40 import org.piccolo2d.event.PDragSequenceEventHandler;
41
42 import org.piccolo2d.nodes.PImage;
43 import org.piccolo2d.nodes.PText;
44
45 import org.apache.commons.scxml.env.SimpleErrorHandler;
46
47 import org.apache.commons.scxml.model.SCXML;
48
49 import org.apache.commons.scxml.io.SCXMLParser;
50
51 import org.dishevelled.iconbundle.IconBundle;
52 import org.dishevelled.iconbundle.IconSize;
53 import org.dishevelled.iconbundle.IconState;
54 import org.dishevelled.iconbundle.IconTextDirection;
55
56 import org.dishevelled.identify.IdentifyUtils;
57
58 /**
59 * Abstract id node.
60 *
61 * <p>An icon bundle image node and name text node are
62 * provided by this class but are not added as child nodes. That
63 * is left up to the subclass.</p>
64 *
65 * <p>This node is backed by a state machine which is responsible
66 * for managing all state transitions. Mouse events are translated
67 * into state transitions by a default mouse event handler. Clients
68 * may fire additional state transitions using methods on this class (see e.g.
69 * <a href="#enable"><code>enable()</code>.</a> Invalid state transitions
70 * are ignored.</p>
71 *
72 * <p>Subclasses may associate visual properties and behavior with states
73 * by providing private no-arg state methods which will be called via reflection
74 * on entry by the state machine engine.</p>
75 *
76 * <p>For example:
77 * <pre>
78 * class MyIdNode extends AbstractIdNode {
79 * // ...
80 *
81 * private void normal() {
82 * setBackground(Color.WHITE);
83 * setForeground(Color.BLACK);
84 * }
85 * private void selected() {
86 * setBackground(Color.BLACK);
87 * setForeground(Color.WHITE);
88 * }
89 * }
90 * </pre></p>
91 *
92 * @author Michael Heuer
93 * @version $Revision$ $Date$
94 */
95 public abstract class AbstractIdNode
96 extends PNode
97 {
98 /** Value for this id node. */
99 private Object value;
100
101 /** Icon size for this id node. */
102 private IconSize iconSize;
103
104 /** Icon state for this id node. */
105 private IconState iconState;
106
107 /** Icon text direction for this id node. */
108 private IconTextDirection iconTextDirection;
109
110 /** Icon bundle image node. */
111 private final IconBundleImageNode iconBundleImageNode;
112
113 /** Name text node. */
114 private final NameTextNode nameTextNode;
115
116 /** State machine. */
117 private static SCXML stateMachine;
118
119 /** State machine support. */
120 private final StateMachineSupport stateMachineSupport;
121
122 /** Default icon size, 32x32. */
123 public static final IconSize DEFAULT_ICON_SIZE = IconSize.DEFAULT_32X32;
124
125 /** Default icon state, normal. */
126 public static final IconState DEFAULT_ICON_STATE = IconState.NORMAL;
127
128 /** Default icon text direction, left to right. */
129 public static final IconTextDirection DEFAULT_ICON_TEXT_DIRECTION = IconTextDirection.LEFT_TO_RIGHT;
130
131
132 /**
133 * Create a new abstract id node with the specified value.
134 *
135 * @param value value for this id node
136 */
137 protected AbstractIdNode(final Object value)
138 {
139 this.value = value;
140 iconSize = DEFAULT_ICON_SIZE;
141 iconState = DEFAULT_ICON_STATE;
142 iconTextDirection = DEFAULT_ICON_TEXT_DIRECTION;
143 iconBundleImageNode = new IconBundleImageNode();
144 nameTextNode = new NameTextNode();
145
146 // TODO: split into drag, mouseover, selection handlers
147 PDragSequenceEventHandler dragSequenceEventHandler = new PDragSequenceEventHandler()
148 {
149 /** {@inheritDoc} */
150 protected void drag(final PInputEvent event)
151 {
152 Dimension2D delta = event.getDeltaRelativeTo(AbstractIdNode.this);
153 AbstractIdNode.this.translate(delta.getWidth(), delta.getHeight());
154 event.setHandled(true);
155 }
156
157 /** {@inheritDoc} */
158 protected void startDrag(final PInputEvent event)
159 {
160 AbstractIdNode.this.moveToFront();
161 AbstractIdNode.this.startDrag();
162 super.startDrag(event);
163 }
164
165 /** {@inheritDoc} */
166 protected void endDrag(final PInputEvent event)
167 {
168 AbstractIdNode.this.endDrag();
169 super.endDrag(event);
170 }
171
172 /** {@inheritDoc} */
173 public void mouseEntered(final PInputEvent event)
174 {
175 AbstractIdNode.this.mouseEntered();
176 super.mouseEntered(event);
177 }
178
179 /** {@inheritDoc} */
180 public void mouseExited(final PInputEvent event)
181 {
182 AbstractIdNode.this.mouseExited();
183 super.mouseExited(event);
184 }
185
186 /** {@inheritDoc} */
187 public void mousePressed(final PInputEvent event)
188 {
189 AbstractIdNode.this.mousePressed();
190 super.mousePressed(event);
191 }
192
193 /** {@inheritDoc} */
194 public void mouseReleased(final PInputEvent event)
195 {
196 AbstractIdNode.this.mouseReleased();
197 super.mouseReleased(event);
198 }
199 };
200 dragSequenceEventHandler.setMinDragStartDistance(4.0d);
201 addInputEventListener(dragSequenceEventHandler);
202
203 if (stateMachine == null)
204 {
205 try
206 {
207 URL stateChart = getClass().getResource("stateChart.xml");
208 stateMachine = SCXMLParser.parse(stateChart, new SimpleErrorHandler());
209 }
210 catch (Exception e)
211 {
212 // ignore
213 }
214 }
215 stateMachineSupport = new StateMachineSupport(this, stateMachine);
216 }
217
218
219 /**
220 * Return the value for this id node.
221 *
222 * @return the value for this id node
223 */
224 public final Object getValue()
225 {
226 return value;
227 }
228
229 /**
230 * Set the value for this id node to <code>value</code>.
231 *
232 * <p>This is a bound property.</p>
233 *
234 * @param value value for this id node
235 */
236 public final void setValue(final Object value)
237 {
238 Object oldValue = this.value;
239 this.value = value;
240 firePropertyChange(-1, "value", oldValue, this.value);
241 }
242
243 /**
244 * Return the icon size for this id node.
245 * The icon size will not be null.
246 *
247 * @return the icon size for this id node
248 */
249 public final IconSize getIconSize()
250 {
251 return iconSize;
252 }
253
254 /**
255 * Set the icon size for this id node to <code>iconSize</code>.
256 *
257 * <p>This is a bound property.</p>
258 *
259 * @param iconSize icon size for this id node, must not be null
260 */
261 public final void setIconSize(final IconSize iconSize)
262 {
263 if (iconSize == null)
264 {
265 throw new IllegalArgumentException("iconSize must not be null");
266 }
267 IconSize oldIconSize = this.iconSize;
268 this.iconSize = iconSize;
269 firePropertyChange(-1, "iconSize", oldIconSize, this.iconSize);
270 }
271
272 /**
273 * Return the icon state for this id node.
274 * The icon state will not be null.
275 *
276 * @return the icon state for this id node
277 */
278 public final IconState getIconState()
279 {
280 return iconState;
281 }
282
283 /**
284 * Set the icon state for this id node to <code>iconState</code>.
285 *
286 * <p>This is a bound property.</p>
287 *
288 * @param iconState icon state for this id node, must not be null
289 */
290 public final void setIconState(final IconState iconState)
291 {
292 if (iconState == null)
293 {
294 throw new IllegalArgumentException("iconState must not be null");
295 }
296 IconState oldIconState = this.iconState;
297 this.iconState = iconState;
298 firePropertyChange(-1, "iconState", oldIconState, this.iconState);
299 }
300
301 /**
302 * Return the icon text direction for this id node.
303 * The icon text direction will not be null.
304 *
305 * @return the icon text direction for this id node
306 */
307 public final IconTextDirection getIconTextDirection()
308 {
309 return iconTextDirection;
310 }
311
312 /**
313 * Set the icon text direction for this id node to <code>iconTextDirection</code>.
314 *
315 * <p>This is a bound property.</p>
316 *
317 * @param iconTextDirection icon text direction for this id node, must not be null
318 */
319 public final void setIconTextDirection(final IconTextDirection iconTextDirection)
320 {
321 if (iconTextDirection == null)
322 {
323 throw new IllegalArgumentException("iconTextDirection must not be null");
324 }
325 IconTextDirection oldIconTextDirection = this.iconTextDirection;
326 this.iconTextDirection = iconTextDirection;
327 firePropertyChange(-1, "iconTextDirection", oldIconTextDirection, this.iconTextDirection);
328 }
329
330 /**
331 * Set the font for the name text node to <code>font</code>.
332 *
333 * @param font font for the name text node
334 */
335 public final void setFont(final Font font)
336 {
337 // TODO: manage property changes here?
338 nameTextNode.setFont(font);
339 }
340
341 /**
342 * Return the icon bundle image node for this id node.
343 *
344 * @return the icon bundle image node for this id node
345 */
346 protected final IconBundleImageNode getIconBundleImageNode()
347 {
348 return iconBundleImageNode;
349 }
350
351 /**
352 * Return the name text node for this id node.
353 *
354 * @return the name text node for this id node
355 */
356 protected final NameTextNode getNameTextNode()
357 {
358 return nameTextNode;
359 }
360
361 /**
362 * Reset the state machine to its "initial" configuration.
363 */
364 protected final void resetStateMachine()
365 {
366 stateMachineSupport.resetStateMachine();
367 }
368
369 /**
370 * Fire a state machine event with the specified event name.
371 *
372 * @param eventName event name, must not be null
373 */
374 private void fireStateMachineEvent(final String eventName)
375 {
376 stateMachineSupport.fireStateMachineEvent(eventName);
377 }
378
379 /**
380 * Fire a <code>"reverseVideo"</code> state transition event.
381 */
382 public final void reverseVideo()
383 {
384 fireStateMachineEvent("reverseVideo");
385 }
386
387 /**
388 * Fire an <code>"enable"</code> state transition event.
389 */
390 public final void enable()
391 {
392 fireStateMachineEvent("enable");
393 }
394
395 /**
396 * Fire a <code>"disable"</code> state transition event.
397 */
398 public final void disable()
399 {
400 fireStateMachineEvent("disable");
401 }
402
403 /**
404 * Fire a <code>"mouseEntered"</code> state transition event.
405 */
406 public final void mouseEntered()
407 {
408 fireStateMachineEvent("mouseEntered");
409 }
410
411 /**
412 * Fire a <code>"mouseExited"</code> state transition event.
413 */
414 public final void mouseExited()
415 {
416 fireStateMachineEvent("mouseExited");
417 }
418
419 /**
420 * Fire a <code>"mouseReleased"</code> state transition event.
421 */
422 public final void mouseReleased()
423 {
424 fireStateMachineEvent("mouseReleased");
425 }
426
427 /**
428 * Fire a <code>"mousePressed"</code> state transition event.
429 */
430 public final void mousePressed()
431 {
432 fireStateMachineEvent("mousePressed");
433 }
434
435 /**
436 * Fire a <code>"select"</code> state transition event.
437 */
438 public final void select()
439 {
440 fireStateMachineEvent("select");
441 }
442
443 /**
444 * Fire a <code>"deselect"</code> state transition event.
445 */
446 public final void deselect()
447 {
448 fireStateMachineEvent("deselect");
449 }
450
451 /**
452 * Fire a <code>"startDrag"</code> state transition event.
453 */
454 public final void startDrag()
455 {
456 fireStateMachineEvent("startDrag");
457 }
458
459 /**
460 * Fire a <code>"endDrag"</code> state transition event.
461 */
462 public final void endDrag()
463 {
464 fireStateMachineEvent("endDrag");
465 }
466
467 /**
468 * Icon bundle image node.
469 */
470 protected final class IconBundleImageNode
471 extends PImage
472 implements PropertyChangeListener
473 {
474
475 /**
476 * Create a new icon bundle image node.
477 */
478 IconBundleImageNode()
479 {
480 super();
481 update();
482 AbstractIdNode.this.addPropertyChangeListener("value", this);
483 AbstractIdNode.this.addPropertyChangeListener("iconSize", this);
484 AbstractIdNode.this.addPropertyChangeListener("iconState", this);
485 AbstractIdNode.this.addPropertyChangeListener("iconTextDirection", this);
486 }
487
488
489 /**
490 * Update this icon bundle image node.
491 */
492 private void update()
493 {
494 IconBundle iconBundle = IdentifyUtils.getIconBundleFor(value);
495 if (iconBundle == null)
496 {
497 setWidth(0.0d);
498 setHeight(0.0d);
499 }
500 else
501 {
502 Image image = iconBundle.getImage(null, iconTextDirection, iconState, iconSize);
503 setImage(image);
504 setWidth(iconSize.getWidth());
505 setHeight(iconSize.getHeight());
506 }
507 }
508
509 /** {@inheritDoc} */
510 public void propertyChange(final PropertyChangeEvent event)
511 {
512 update();
513 }
514 }
515
516 /**
517 * Name text node.
518 */
519 protected final class NameTextNode
520 extends PText
521 implements PropertyChangeListener
522 {
523
524 /**
525 * Create a new name text node.
526 */
527 NameTextNode()
528 {
529 super();
530 setHorizontalAlignment(Component.CENTER_ALIGNMENT);
531 update();
532 AbstractIdNode.this.addPropertyChangeListener("value", this);
533 //AbstractIdNode.this.addPropertyChangeListener("iconTextDirection", this);
534 }
535
536
537 /**
538 * Update this name text node.
539 */
540 private void update()
541 {
542 String name = IdentifyUtils.getNameFor(value);
543 setText(name);
544 }
545
546 /** {@inheritDoc} */
547 public void propertyChange(final PropertyChangeEvent event)
548 {
549 update();
550 }
551 }
552 }