View Javadoc

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 &quot;initial&quot; 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 }