1   /****************************************************************************
2    * This demo file is part of yFiles for Java 2.14.
3    * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4    * 72070 Tuebingen, Germany. All rights reserved.
5    * 
6    * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7    * of demo files in source code or binary form, with or without
8    * modification, is not permitted.
9    * 
10   * Owners of a valid software license for a yFiles for Java version that this
11   * demo is shipped with are allowed to use the demo source code as basis
12   * for their own yFiles for Java powered applications. Use of such programs is
13   * governed by the rights and conditions as set out in the yFiles for Java
14   * license agreement.
15   * 
16   * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19   * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21   * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   *
27   ***************************************************************************/
28  package demo.view.realizer;
29  
30  
31  import java.awt.Color;
32  import java.awt.Graphics2D;
33  import java.awt.Stroke;
34  import java.awt.geom.GeneralPath;
35  import java.io.IOException;
36  import java.io.ObjectInputStream;
37  import java.io.ObjectOutputStream;
38  
39  import y.util.YVersion;
40  import y.view.LineType;
41  import y.view.NodeRealizer;
42  import y.view.ShapeNodeRealizer;
43  import y.io.graphml.graph2d.ShapeNodeRealizerSerializer;
44  import y.io.graphml.input.GraphMLParseContext;
45  import y.io.graphml.input.GraphMLParseException;
46  import y.io.graphml.output.XmlWriter;
47  import y.io.graphml.output.GraphMLWriteContext;
48  import org.w3c.dom.Node;
49  import org.w3c.dom.Element;
50  
51  /**
52   * This class represents a custom NodeRealizer with its own paint,
53   * copy and serialisation routines.
54   * <br>
55   * This realizer will be used in the demo
56   * {@link demo.view.realizer.StateNodeRealizerDemo}.
57   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/realizers#cls_ShapeNodeRealizer" target="_blank">Section Bringing Graph Elements to Life: The Realizer Concept</a> in the yFiles for Java Developer's Guide
58   * @see <a href="http://docs.yworks.com/yfiles/doc/api/index.html#/dguide/mvc_controller#custom_edit_mode" target="_blank">Section User Interaction</a> in the yFiles for Java Developer's Guide
59   */
60  public class StateNodeRealizer extends ShapeNodeRealizer
61  {
62    /**
63     * State specifier constant. A node with this state will be drawn
64     * like an ordinary ShapeNodeRealizer.
65     */
66    public static final byte INITIAL_STATE     = 0;
67    
68    /**
69     * State specifier constant. A node with this state will be drawn
70     * with an additional dashed line.
71     */
72    public static final byte TRANSITION_STATE  = 1;
73  
74    /**
75     * State specifier constant. A node with this state will be drawn
76     * with an additional solid line.
77     */
78    public static final byte FINAL_STATE       = 2;
79    
80    
81    private byte state;
82    
83    /**
84     * Instantiates a new StateNodeRealizer with the given state.
85     * @see #setState(byte)
86     */
87    public StateNodeRealizer(byte state)
88    {
89      super();
90      this.state = state;
91      this.setShapeType(ELLIPSE);
92    }
93    
94    /**
95     * Instantiates a new StateNodeRealizer.
96     */
97    public StateNodeRealizer()
98    {
99      this(INITIAL_STATE);
100   }
101   
102   /**
103    * Instantiates a new StateNodeRealizer as a copy of 
104    * the given NodeRealizer.
105    */
106   public StateNodeRealizer(NodeRealizer r)
107   {
108     super(r);
109     if(r instanceof StateNodeRealizer)
110     {
111       StateNodeRealizer sr = (StateNodeRealizer)r;
112       state = sr.state;
113     }
114     else
115     {
116       state = INITIAL_STATE;
117     }
118   }
119   
120   /**
121    * Sets the state of this realizer.
122    * @param state on of {@link #INITIAL_STATE}, {@link #TRANSITION_STATE}
123    * or {@link #FINAL_STATE}.
124    */
125   public void setState(byte state)
126   {
127     this.state = state;
128   }
129   
130   /**
131    * Returns the state of this realizer.
132    * By default {@link #INITIAL_STATE} will be returned.
133    * @see #setState(byte)
134    */
135   public byte getState()
136   {
137     return this.state;
138   }
139   
140   
141   /**
142    * Paints the node on the given graphics context.
143    * Depending on the state of the realizer the node will be
144    * drawn differently
145    */
146   public void paintNode(Graphics2D gfx)
147   {
148     //first paint the shape node realizer
149     super.paintNode(gfx);
150     
151     if(getState() != INITIAL_STATE)
152     {
153       //then draw an additional state marker
154       //on top of the node.
155       Stroke oldStroke = gfx.getStroke();
156       Color oldColor   = gfx.getColor();
157       if(state == TRANSITION_STATE) {
158         gfx.setStroke(LineType.DASHED_1);
159       } else if(getState() == FINAL_STATE) {
160         gfx.setStroke(LineType.LINE_1);
161       }
162       double oldWidth  = getWidth();
163       double oldHeight = getHeight();
164       if(oldWidth > 10.0 && oldHeight > 10.0)
165       {
166         setSize(oldWidth- 10.0,oldHeight- 10.0);
167         gfx.draw(shape);
168         setSize(oldWidth,oldHeight);
169       }
170       
171       gfx.setColor(oldColor);
172       gfx.setStroke(oldStroke);
173     }
174   }
175   
176   /**
177    * Creates a copy of the given realizer that has type
178    * StateNodeRealizer. It is important to implement this method properly
179    * if the realizer should be able to act as a template for 
180    * other realizers, e.g. if it is used as default node realizer
181    * in a Graph2D.
182    * The canonical way to implement this method is to return the result
183    * of a copy constructor.
184    * @see y.view.Graph2D#setDefaultNodeRealizer(y.view.NodeRealizer)
185    * @see #StateNodeRealizer(NodeRealizer) 
186    */
187   public NodeRealizer createCopy(NodeRealizer r)
188   {
189     return new StateNodeRealizer(r);
190   }
191 
192   /**
193    * A custom shape type specifier.
194    */
195   public static final byte CUSTOM_SHAPE = -1;
196 
197   /**
198    * Demonstrates how to define custom shapes.
199    */
200   public void setShapeType(byte type)
201   {
202     if(type == CUSTOM_SHAPE)
203     {
204       updateCustomShape();
205     }
206     super.setShapeType(type);
207   }
208   
209   void updateCustomShape()
210   {
211     GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD,5);
212     float x = (float)getX();
213     float y = (float)getY();
214     float w = (float)getWidth();
215     float h = (float)getHeight();
216     path.moveTo(x, y + h);
217     path.lineTo(x, y);
218     float dx = Math.min(h*0.2f,w);
219     path.lineTo(x+w-dx,y);
220     path.lineTo(x+w,y+0.5f*h);
221     path.lineTo(x+w-dx,y+h);
222     path.closePath();
223     shape = path;      
224   }
225   
226   /**
227    * adjust custom shape when size changes. Overrides default scaling operation.
228    */
229   public void setSize(double x, double y)
230   {
231     if(getShapeType() == CUSTOM_SHAPE)
232     {
233       shape = null;
234       super.setSize(x,y);
235       updateCustomShape();
236     }
237     else
238     {
239       super.setSize(x,y);
240     }
241   }
242       
243   
244   /**
245    * Writes out this realizer in a serialized form. This method 
246    * will be used by YGFIOHandler to serialize this NodeRealizer.
247    * @deprecated Use {@link demo.view.realizer.StateNodeRealizer.StateNodeRealizerSerializer}
248    * for serialization to the {@link y.io.GraphMLIOHandler GraphML format}
249    * instead.
250    */
251   public void write(ObjectOutputStream out) throws IOException 
252   {
253     //write out a version tag. version tags help to provide future
254     //serialization compatibility when node realizer features
255     //change.    
256     out.writeByte(YVersion.VERSION_1);
257     //write out the shape node realizer features
258     super.write(out);
259     //write out the state variable
260     out.writeByte(state);
261   }
262   
263   /**
264    * Reads in the serialized form of this realizer. The realizer must have been
265    * written out before by it's {@link #write(ObjectOutputStream)} method.
266    * This method will be used by YGFIOHandler to deserialize this NodeRealizer.
267    * @deprecated Use {@link demo.view.realizer.StateNodeRealizer.StateNodeRealizerSerializer}
268    * for serialization to the {@link y.io.GraphMLIOHandler GraphML format} 
269    * instead.
270    */
271   public void read(ObjectInputStream in) throws IOException, 
272     ClassNotFoundException 
273   {
274     switch(in.readByte()) {
275     case YVersion.VERSION_1:
276       super.read(in);
277       state = in.readByte();
278       break;
279     default:
280       //trouble
281     }
282   }
283 
284   /**
285    * RealizerSerializer that can be used to serialize instances of StateNodeRealizer to and from
286    * {@link y.io.GraphMLIOHandler GraphML format}.
287    */
288   static class StateNodeRealizerSerializer extends ShapeNodeRealizerSerializer {
289     public String getName() {
290       return "StateNode";
291     }
292 
293     public String getNamespaceURI() {
294       return "demo.view.realizer";
295     }
296 
297     public Class getRealizerClass() {
298       return StateNodeRealizer.class;
299     }
300 
301     public void parse(NodeRealizer realizer, Node domNode, GraphMLParseContext context) throws GraphMLParseException {
302       super.parse(realizer, domNode, context);
303       StateNodeRealizer snr = (StateNodeRealizer) realizer;
304       String state = ((Element) domNode).getAttribute("state");
305 
306       if("initial".equals(state)) {
307         snr.setState(INITIAL_STATE);
308       }
309       else if("transition".equals(state)) {
310         snr.setState(TRANSITION_STATE);
311       }
312       else if("final".equals(state)) {
313         snr.setState(FINAL_STATE);
314       }
315     }
316 
317 
318     protected byte decodeShapeType(String s, GraphMLParseContext context) {
319       if("custom".equals(s)) {
320         return CUSTOM_SHAPE;
321       }
322       return super.decodeShapeType(s, context);
323     }
324 
325     protected String encodeShapeType(ShapeNodeRealizer snr, GraphMLWriteContext context) {
326       if(snr.getShapeType() == CUSTOM_SHAPE) {
327         return "custom";
328       }
329       return super.encodeShapeType(snr, context);
330     }
331 
332     public void writeAttributes(NodeRealizer nr, XmlWriter writer, GraphMLWriteContext context) {
333       super.writeAttributes(nr, writer, context);
334       StateNodeRealizer snr = (StateNodeRealizer) nr;
335       switch(snr.getState()) {
336         case INITIAL_STATE:
337           writer.writeAttribute("state", "initial");
338           break;
339         case TRANSITION_STATE:
340           writer.writeAttribute("state", "transition");
341           break;
342         case FINAL_STATE:
343           writer.writeAttribute("state", "final");
344           break;
345       }
346     }
347   }
348 }
349 
350