View Javadoc

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