1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.9. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2011 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.view.advanced.ports;
15  
16  import demo.view.DemoBase;
17  import demo.view.DemoDefaults;
18  import demo.view.application.DragAndDropDemo;
19  import y.base.Edge;
20  import y.base.Node;
21  import y.base.YList;
22  import y.geom.YPoint;
23  import y.geom.YRectangle;
24  import y.layout.LayoutOrientation;                            
25  import y.layout.hierarchic.IncrementalHierarchicLayouter;     
26  import y.view.Arrow;
27  import y.view.CreateEdgeMode;
28  import y.view.EdgeRealizer;
29  import y.view.EditMode;
30  import y.view.GenericNodeRealizer;
31  import y.view.Graph2D;
32  import y.view.HotSpotMode;
33  import y.view.LineType;
34  import y.view.MovePortMode;
35  import y.view.NodeLabel;
36  import y.view.NodePort;
37  import y.view.NodeScaledPortLocationModel;
38  import y.view.NodeRealizer;
39  import y.view.HitInfo;
40  import y.view.Drawable;
41  import y.view.AbstractCustomNodePainter;
42  import y.view.SmartNodeLabelModel;
43  import y.view.YRenderingHints;
44  import y.view.Graph2DTraversal;
45  import y.view.Graph2DLayoutExecutor;                          
46  
47  import javax.swing.AbstractAction;
48  import javax.swing.Action;
49  import javax.swing.JList;
50  import javax.swing.JScrollPane;
51  import javax.swing.JToolBar;
52  import javax.swing.event.ListSelectionEvent;
53  import javax.swing.event.ListSelectionListener;
54  import java.awt.BorderLayout;
55  import java.awt.Color;
56  import java.awt.EventQueue;
57  import java.awt.Insets;
58  import java.awt.Graphics2D;
59  import java.awt.Rectangle;
60  import java.awt.GradientPaint;
61  import java.awt.Shape;
62  import java.awt.geom.Ellipse2D;
63  import java.awt.geom.Rectangle2D;
64  import java.awt.geom.GeneralPath;
65  import java.awt.geom.Arc2D;
66  import java.awt.geom.AffineTransform;
67  import java.awt.geom.Line2D;
68  import java.awt.event.ActionEvent;
69  import java.awt.event.MouseEvent;
70  import java.util.ArrayList;
71  import java.util.List;
72  import java.util.Locale;
73  import java.util.Map;
74  import java.util.HashMap;
75  import java.util.Iterator;
76  
77  /**
78   * Demonstrates how to create an application that makes use of nodes that have a fixed set of ports.
79   * The nodes represent logic gates. Edges can only start at the ports located at the right side of nodes.
80   * These are the outputs of the represented gates. Edges can only end at ports located at the left side
81   * of the nodes. These are the inputs of the represented gates. Valid ports will be highlighted when
82   * hovering over a node or when creating an edge. The nodes are realized using GenericNodeRealizers and the
83   * ports are realized using NodePorts.
84   *
85   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/mvc_controller.html">Section User Interaction</a> in the yFiles for Java Developer's Guide
86   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/realizer_related.html">Section Realizer-Related Features</a> in the yFiles for Java Developer's Guide
87   */
88  public class LogicGatesDemo extends DemoBase {
89    private static final String AND_GATE_NODE_CONFIGURATION = "AndGateNodeConfiguration";
90    private static final String NAND_GATE_NODE_CONFIGURATION = "NandGateNodeConfiguration";
91    private static final String NOT_GATE_NODE_CONFIGURATION = "NotGateNodeConfiguration";
92    private static final String PORT_CONFIG_INPUT = "INPUT_PORT";
93    private static final String PORT_CONFIG_OUTPUT = "OUTPUT_PORT";
94    private static final Color PORT_HIGHLIGHT_COLOR = Color.GREEN;
95    private static final Color LINE_COLOR = Color.BLACK;
96    private static final LineType LINE_TYPE = LineType.LINE_2;
97  
98    public LogicGatesDemo() {
99      this(null);
100   }
101 
102   public LogicGatesDemo( final String helpFilePath ) {
103     //load the default graph of the demo
104     loadGraph("resource/LogicGatesDemo.graphml");
105     addHelpPane(helpFilePath);
106   }
107 
108   // For running a layout the node ports have to be translated to strong port      
109   // constraints for the edges connecting to the node ports since node ports are a 
110   // view-only feature. The node port configurator used by the layout executor     
111   // provides a setting for doing this translation.                                
112   protected JToolBar createToolBar() {                                             
113     final IncrementalHierarchicLayouter ihl = new IncrementalHierarchicLayouter(); 
114     ihl.setLayoutOrientation(LayoutOrientation.LEFT_TO_RIGHT);                     
115     ihl.setOrthogonallyRouted(true);                                               
116     final Graph2DLayoutExecutor executor = new Graph2DLayoutExecutor();            
117     // Set up port constraints for the node ports before performing the layout.    
118     executor.getNodePortConfigurator().setAutomaticPortConstraintsEnabled(true);   
119 
120     final Action layoutAction = new AbstractAction(                                
121             "Layout", SHARED_LAYOUT_ICON) {                                        
122       public void actionPerformed(ActionEvent e) {                                 
123         executor.doLayout(view, ihl);                                              
124       }                                                                            
125     };                                                                             
126 
127     final JToolBar jtb = super.createToolBar();                                    
128     jtb.addSeparator();                                                            
129     jtb.add(createActionControl(layoutAction));                                    
130     return jtb;                                                                    
131   }                                                                                
132 
133   protected void initialize() {
134     initializePortConfiguration();
135 
136     // Configuration for Logic Gate Symbols
137     List gatesNRList = new ArrayList();
138     GenericNodeRealizer.Factory gnrFactory = GenericNodeRealizer.getFactory();
139 
140     // AND Gate Configuration
141     Map andSymbolConfMap = gnrFactory.createDefaultConfigurationMap();
142     AndGateConfiguration andGateHandler = new AndGateConfiguration(true);
143     andSymbolConfMap.put(GenericNodeRealizer.Painter.class, andGateHandler);
144     andSymbolConfMap.put(GenericNodeRealizer.ContainsTest.class, andGateHandler);
145     gnrFactory.addConfiguration(AND_GATE_NODE_CONFIGURATION, andSymbolConfMap);
146     GenericNodeRealizer andRealizer = new GenericNodeRealizer(AND_GATE_NODE_CONFIGURATION);
147     andRealizer.setSize(100, 50);
148     andRealizer.setLineColor(Color.BLACK);
149     andRealizer.setLineType(LINE_TYPE);
150     andRealizer.setFillColor2(Color.WHITE);
151     andRealizer.setFillColor(DemoDefaults.DEFAULT_CONTRAST_COLOR);
152     configureLabelModel(andRealizer.getLabel());
153     addPort(andRealizer, new YPoint(-0.5, -0.25), PORT_CONFIG_INPUT);
154     addPort(andRealizer, new YPoint(-0.5,  0.25), PORT_CONFIG_INPUT);
155     addPort(andRealizer, new YPoint( 0.5,  0.0),  PORT_CONFIG_OUTPUT);
156     gatesNRList.add(andRealizer);
157 
158     // NAND Gate Configuration
159     Map nandSymbolConfMap = gnrFactory.createDefaultConfigurationMap();
160     AndGateConfiguration nandGateHandler = new AndGateConfiguration(false);
161     nandSymbolConfMap.put(GenericNodeRealizer.Painter.class, nandGateHandler);
162     nandSymbolConfMap.put(GenericNodeRealizer.ContainsTest.class, nandGateHandler);
163     gnrFactory.addConfiguration(NAND_GATE_NODE_CONFIGURATION, nandSymbolConfMap);
164     GenericNodeRealizer nandRealizer = new GenericNodeRealizer(NAND_GATE_NODE_CONFIGURATION);
165     nandRealizer.setSize(100, 50);
166     nandRealizer.setLineColor(Color.BLACK);
167     nandRealizer.setLineType(LINE_TYPE);
168     nandRealizer.setFillColor2(Color.WHITE);
169     nandRealizer.setFillColor(DemoDefaults.DEFAULT_CONTRAST_COLOR);
170     configureLabelModel(nandRealizer.getLabel());
171     addPort(nandRealizer, new YPoint(-0.5, -0.25), PORT_CONFIG_INPUT);
172     addPort(nandRealizer, new YPoint(-0.5,  0.25), PORT_CONFIG_INPUT);
173     addPort(nandRealizer, new YPoint( 0.5,  0.0),  PORT_CONFIG_OUTPUT);
174     gatesNRList.add(nandRealizer);
175 
176     // NOT Gate Configuration
177     Map notSymbolConfMap = gnrFactory.createDefaultConfigurationMap();
178     NotGateConfiguration notGateHandler = new NotGateConfiguration();
179     notSymbolConfMap.put(GenericNodeRealizer.Painter.class, notGateHandler);
180     notSymbolConfMap.put(GenericNodeRealizer.ContainsTest.class, notGateHandler);
181     gnrFactory.addConfiguration(NOT_GATE_NODE_CONFIGURATION, notSymbolConfMap);
182     GenericNodeRealizer notRealizer = new GenericNodeRealizer(NOT_GATE_NODE_CONFIGURATION);
183     notRealizer.getLabel().setInsets(new Insets(0, 0, 0, 15));
184     notRealizer.setSize(100, 50);
185     notRealizer.setLineColor(Color.BLACK);
186     notRealizer.setLineType(LINE_TYPE);
187     notRealizer.setFillColor2(Color.WHITE);
188     notRealizer.setFillColor(DemoDefaults.DEFAULT_CONTRAST_COLOR);
189     configureLabelModel(notRealizer.getLabel());
190     addPort(notRealizer, new YPoint(-0.5, 0.0), PORT_CONFIG_INPUT);
191     addPort(notRealizer, new YPoint( 0.5, 0.0), PORT_CONFIG_OUTPUT);
192     gatesNRList.add(notRealizer);
193 
194     final Graph2D graph = this.view.getGraph2D();
195 
196     final GenericNodeRealizer[] logicSymbols = new GenericNodeRealizer[gatesNRList.size()];
197     gatesNRList.toArray(logicSymbols);
198 
199     // Set default edge realizer configuration.
200     EdgeRealizer er = graph.getDefaultEdgeRealizer();
201     er.setLineType(LINE_TYPE);
202     er.setLineColor(LINE_COLOR);
203     er.setArrow(Arrow.NONE);
204 
205     // Create drag and drop support.
206     DragAndDropDemo.DragAndDropSupport dragAndDropSupport = new DragAndDropDemo.DragAndDropSupport(logicSymbols, view);
207     dragAndDropSupport.configureSnapping(true, 30, 20, true);
208 
209     // The default NodeRealizer depends on list selection in the drag and drop list.
210     final JList list = dragAndDropSupport.getList();
211     list.addListSelectionListener(new ListSelectionListener() {
212       public void valueChanged(ListSelectionEvent e) {
213         graph.setDefaultNodeRealizer(logicSymbols[list.getSelectedIndex()]);
214       }
215     });
216 
217     // Select the first element in the list.
218     list.setSelectedIndex(0);
219     JScrollPane scrollPane = new JScrollPane(list);
220 
221     contentPane.add(scrollPane, BorderLayout.WEST);
222   }
223 
224   private static void configureLabelModel(NodeLabel label) {
225     SmartNodeLabelModel model = new SmartNodeLabelModel();
226     label.setLabelModel(model);
227     label.setModelParameter(model.getDefaultParameter());
228   }
229 
230   private static void addPort(GenericNodeRealizer owner, YPoint relativeOffsets, String portConfiguration) {
231     NodePort port = new NodePort();
232     owner.addPort(port);
233     port.setConfiguration(portConfiguration);
234     port.setModelParameter(new NodeScaledPortLocationModel().createScaledParameter(relativeOffsets));
235   }
236 
237   private void initializePortConfiguration() {
238     final HashMap portImpls = new HashMap();
239 
240     // Since the bounds of a node port depend very much on its owning node realizer, the handler for
241     // the realizer also implements the NodePort.BoundsProvider interface.
242     portImpls.put(NodePort.BoundsProvider.class, new NodePort.BoundsProvider() {
243       public YRectangle getBounds(NodePort port) {
244         return getLogicGateHandler(port).getBounds(port);
245       }
246 
247       private LogicGateConfiguration getLogicGateHandler(NodePort port) {
248         String configuration = ((GenericNodeRealizer) port.getRealizer()).getConfiguration();
249         GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
250         return (LogicGateConfiguration) factory.getImplementation(configuration, GenericNodeRealizer.ContainsTest.class);
251       }
252     });
253     // The node ports are drawn like edges.
254     portImpls.put(NodePort.Painter.class, new NodePort.Painter() {
255       public void paint(NodePort port, Graphics2D gfx) {
256         YRectangle bounds = port.getBounds();
257         // Use the same stroke and color as for edges.
258         gfx.setStroke(LINE_TYPE);
259         gfx.setColor(LINE_COLOR);
260         gfx.draw(new Line2D.Double(bounds.getX(), bounds.getY(), bounds.getX() + bounds.getWidth(), bounds.getY()));
261       }
262     });
263     NodePort.getFactory().addConfiguration(PORT_CONFIG_INPUT, portImpls);
264     NodePort.getFactory().addConfiguration(PORT_CONFIG_OUTPUT, portImpls);
265   }
266 
267   protected EditMode createEditMode() {
268     EditMode editMode = new HighlightOutputPortsEditMode();
269     editMode.setMoveNodePortMode(null);
270     editMode.allowMovePorts(true);
271     editMode.setOrthogonalEdgeRouting(true);
272     editMode.assignNodeLabel(false);
273 
274     // Start new edges only at output ports and end them only at input ports.
275     CreateEdgeMode createEdgeMode = new CreateEdgeMode() {
276       protected boolean acceptSourceNodePort(Node node, NodePort port, double x, double y) {
277         // Accept a port for starting an edge, if it is an output of its gate.
278         return isOutput(port);
279       }
280       protected boolean acceptTargetNodePort(Node node, NodePort port, double x, double y) {
281         // Accept a port for ending an edge, if it is an input of its gate with no adjacent edges so far
282         return isInput(port);
283       }
284       protected void drawTargetPortIndicator(Graphics2D gfx, final NodePort port) {
285         // Do not use the standard visual for drawing target (input) ports. Use the highlightPort
286         // method instead which is also used by the HighlightOutputPortsEditMode and MovePortMode.
287         if (isInput(port)) {
288           highlightPort(gfx, port);
289         }
290       }
291     };
292     createEdgeMode.setIndicatingTargetNode(true);
293     createEdgeMode.setOrthogonalEdgeCreation(true);
294     editMode.setCreateEdgeMode(createEdgeMode);
295 
296     // Ensure that the aspect ratio of a gate is kept on resizing.
297     HotSpotMode hotSpotMode = new HotSpotMode() {
298       protected boolean isModifierPressed(MouseEvent me) {
299         return true;
300       }
301     };
302     editMode.setHotSpotMode(hotSpotMode);
303 
304     // Ensure that the restriction on edges is kept while reassigning their endpoints.
305     final MovePortMode mpm = new MovePortMode() {
306       protected YList getPortCandidates(Node v, Edge e, double gridSpacing) {
307         YList result = new YList();
308         NodeRealizer nr = getGraph2D().getRealizer(v);
309         // Do we move the target port of the edge?
310         boolean newTarget = port == port.getOwner().getTargetPort();
311         for (int i = 0; i < nr.portCount(); i++) {
312           final NodePort nodePort = nr.getPort(i);
313           if ((newTarget && isInput(nodePort)) || (!newTarget && isOutput(nodePort))) {
314             result.add(nodePort.getLocation());
315           }
316         }
317         return result;
318       }
319 
320       protected void drawPortCandidate(Graphics2D gfx, YPoint p, Node v, Edge e, boolean isSnapActive,
321                                        boolean isSnapCandidate) {
322         // Draw port candidates like the EditMode/CreateEdgeMode.
323         if (isSnapCandidate) {
324           HitInfo info = view.getHitInfoFactory().createHitInfo(p.x, p.y, Graph2DTraversal.NODE_PORTS, true);
325           NodePort hitNodePort = info.getHitNodePort();
326           if (hitNodePort != null && isInput(hitNodePort)) {
327             highlightPort(gfx, hitNodePort);
328           }
329         }
330       }
331     };
332     mpm.setChangeEdgeEnabled(true);
333     mpm.setUsingRealizerPortCandidates(true);
334     mpm.setUsingNodePortCandidates(true);
335     mpm.setSegmentSnappingEnabled(true);
336     editMode.setMovePortMode(mpm);
337 
338     return editMode;
339   }
340 
341   private boolean isInput(NodePort port) {
342     return port.getConfiguration().equals(PORT_CONFIG_INPUT);
343   }
344 
345   private boolean isOutput(NodePort port) {
346     return port.getConfiguration().equals(PORT_CONFIG_OUTPUT);
347   }
348 
349   // This method is used to highlight candidate ports by the custom view modes.
350   public static void highlightPort(Graphics2D gfx, NodePort port1) {
351     final YRectangle bounds = port1.getBounds();
352     Rectangle2D.Double box = new Rectangle2D.Double(
353         bounds.getX(), bounds.getY() - 2, bounds.getWidth(), bounds.getHeight() + 4);
354     Color oldColor = gfx.getColor();
355     gfx.setColor(PORT_HIGHLIGHT_COLOR);
356     gfx.fill(box);
357     gfx.setColor(oldColor);
358   }
359 
360   public static void main(String[] args) {
361     EventQueue.invokeLater(new Runnable() {
362       public void run() {
363         Locale.setDefault(Locale.ENGLISH);
364         initLnF();
365         (new LogicGatesDemo("resource/logicgateshelp.html")).start();
366       }
367     });
368   }
369 
370   /** This EditMode highlights output ports when hovering over them. */
371   private class HighlightOutputPortsEditMode extends EditMode {
372     private Node previousNode;
373     private ArrayList portHighlightDrawables;
374 
375     public HighlightOutputPortsEditMode() {
376       super();
377       portHighlightDrawables = new ArrayList();
378     }
379 
380     public void mousePressed(MouseEvent e) {
381       unhighlightPorts();
382       super.mousePressed(e);
383     }
384 
385     public void mouseMoved(double x, double y) {
386       super.mouseMoved(x, y);
387       final HitInfo hi = getHitInfo(x, y);
388       if (hi.hasHitNodes()) {
389         // The mouse is over a node.
390         final Node node = hi.getHitNode();
391         final Graph2D graph = view.getGraph2D();
392         final NodeRealizer nr = graph.getRealizer(node);
393         if ((node != previousNode) && !nr.isSelected()) {
394           // Highlight output ports of the hit node.
395           unhighlightPorts();
396           for (int i = 0; i < nr.portCount(); i++) {
397             final NodePort port = nr.getPort(i);
398             if (isOutput(port)) {
399               highlight(port);
400             }
401           }
402           view.updateView();
403           previousNode = node;
404         }
405       } else {
406         previousNode = null;
407         unhighlightPorts();
408       }
409     }
410 
411     private void highlight(NodePort port) {
412       PortHighlightDrawable portHighlightDrawable = new PortHighlightDrawable(port);
413       portHighlightDrawables.add(portHighlightDrawable);
414       view.addDrawable(portHighlightDrawable);
415     }
416 
417     private void unhighlightPorts() {
418       if (!portHighlightDrawables.isEmpty()) {
419         for (Iterator ports = portHighlightDrawables.iterator(); ports.hasNext();) {
420           PortHighlightDrawable currPortHighlightDrawable = (PortHighlightDrawable) ports.next();
421           view.removeDrawable(currPortHighlightDrawable);
422         }
423         portHighlightDrawables.clear();
424         view.updateView();
425       }
426     }
427   }
428 
429   /** This drawable is used by the HighlightOutputPortsEditMode. */
430   private static final class PortHighlightDrawable implements Drawable {
431     private NodePort port;
432 
433     public PortHighlightDrawable(NodePort port) {
434       this.port = port;
435     }
436 
437     public void paint(Graphics2D gfx) {
438       highlightPort(gfx, port);
439     }
440 
441     public Rectangle getBounds() {
442       YRectangle rect = port.getBounds();
443       return new Rectangle((int) rect.getX(), (int) rect.getY(), (int) rect.getWidth(), (int) rect.getHeight());
444     }
445   }
446 
447   /**
448    * This is an abstract base class for handling visual aspects of logic gates. It is responsible for
449    * painting the gate symbol (it extends AbstractCustomNodePainter) and provides a contains test (by
450    * implementing GenericNodeRealizer.ContainsTest). Moreover it also provides the bounds for the input
451    * and output node ports.
452    */
453   private abstract static class LogicGateConfiguration
454       extends AbstractCustomNodePainter implements GenericNodeRealizer.ContainsTest {
455 
456     // This is the height of the bounds of a node port (see getBounds(NodePort)). If the bounds
457     // have height zero, node ports cannot be found by a hit test.
458     private static final double REALLY_SMALL_HEIGHT = 0.001;
459 
460     // The raw bounds are assumed to encompass the gate symbol and its input and output ports.
461     private static final Rectangle2D.Double rawBounds = new Rectangle2D.Double(0, 0, 120, 60);
462 
463     // This is the gate symbol. It is initialized in subclasses.
464     protected GeneralPath symbol;
465 
466     // This is true for an inverted gate, i.e. a gate which inverts its output like a NAND gate.
467     private boolean inverted;
468 
469     protected LogicGateConfiguration(boolean inverted) {
470       super();
471       this.inverted = inverted;
472     }
473 
474     public boolean isInverted() {
475       return inverted;
476     }
477 
478     protected GeneralPath getSymbol() {
479       return symbol;
480     }
481 
482     protected Rectangle2D.Double getRawBounds() {
483       return rawBounds;
484     }
485 
486     // AbstractCustomNodePainter implementation
487     protected void paintNode(NodeRealizer context, Graphics2D graphics, boolean sloppy) {
488       Shape shape = getTransformedSymbol(context);
489       final boolean useSelectionStyle = useSelectionStyle(context, graphics);
490       Color fillColor1 = getFillColor(context, useSelectionStyle);
491       Color fillColor2 = getFillColor2(context, useSelectionStyle);
492       if (fillColor2 != null && useGradientStyle(graphics)) {
493         double x = context.getX();
494         double y = context.getY();
495         double width = context.getWidth();
496         double height = context.getHeight();
497         GradientPaint gp = new GradientPaint(
498                 (float) x, (float) y, fillColor2,
499                 (float) (x + width * 0.5), (float) (y + height * 0.5), fillColor1);
500         graphics.setPaint(gp);
501       } else {
502         graphics.setColor(fillColor1);
503       }
504       graphics.fill(shape);
505       graphics.setStroke(getLineStroke(context, useSelectionStyle));
506       graphics.setColor(getLineColor(context, useSelectionStyle));
507       graphics.draw(shape);
508     }
509 
510     // GenericNodeRealizer.ContainsTest implementation
511     // This method returns true if the given coordinate lies within the symbol or the ports.
512     public boolean contains(NodeRealizer context, double x, double y) {
513       if (!context.getBoundingBox().contains(x, y)) {
514         // This is an optional optimization. If the coordinate is outside of the bounds
515         // of the context we neither have to look at the symbol nor the ports.
516         return false;
517       }
518       if (getTransformedSymbol(context).contains(x, y)) {
519         return true;
520       }
521       // Consider node ports.
522       for (int i = 0; i < context.portCount(); i++) {
523         NodePort port = context.getPort(i);
524         YRectangle bounds = port.getBounds();
525         double tolerance = 4;
526         if (bounds.x - tolerance <= x && x <= bounds.x + bounds.width + tolerance &&
527             bounds.y - tolerance <= y && y <= bounds.y + bounds.height + tolerance) {
528           return true;
529         }
530       }
531       return false;
532     }
533 
534     // The bounds for a port are a horizontal line starting at the port location at the left or right
535     // border of the realizer and ending at the opposite border of the gate symbol in the middle of the
536     // realizer.
537     public YRectangle getBounds(NodePort port) {
538       YPoint location = port.getLocation();
539       if (port.getConfiguration().equals(PORT_CONFIG_INPUT)) {
540         double width = getXScale(port.getRealizer()) * getLeftOffset();
541         return new YRectangle(location.getX(), location.getY(), width, REALLY_SMALL_HEIGHT);
542       } else {
543         double width = getXScale(port.getRealizer()) * getRightOffset();
544         return new YRectangle(location.getX() - width, location.getY(), width, REALLY_SMALL_HEIGHT);
545       }
546     }
547 
548     static boolean useGradientStyle( final Graphics2D graphics ) {
549       return YRenderingHints.isGradientPaintingEnabled(graphics);
550     }
551 
552     static boolean useSelectionStyle(
553             final NodeRealizer context,
554             final Graphics2D gfx
555     ) {
556       return context.isSelected() &&
557              YRenderingHints.isSelectionPaintingEnabled(gfx);
558     }
559 
560     // Returns a copy of the symbol which is translated and scaled according to the location and dimension
561     // of the given node realizer.
562     private Shape getTransformedSymbol(NodeRealizer context) {
563       AffineTransform transform = new AffineTransform();
564       Rectangle2D.Double bounds = getRawBounds();
565       transform.translate(context.getX() - bounds.getX(), context.getY() - bounds.getY());
566       transform.scale(getXScale(context), getYScale(context));
567       return getSymbol().createTransformedShape(transform);
568     }
569 
570     protected double getYScale(NodeRealizer context) {
571       return context.getHeight() / getRawBounds().getHeight();
572     }
573 
574     protected double getXScale(NodeRealizer context) {
575       return context.getWidth() / getRawBounds().getWidth();
576     }
577 
578     private double getLeftOffset() {
579       return getSymbol().getBounds2D().getX() - getRawBounds().getX();
580     }
581 
582     private double getRightOffset() {
583       return getRawBounds().getMaxX() - getSymbol().getBounds2D().getMaxX();
584     }
585   }
586 
587   private static class AndGateConfiguration extends LogicGateConfiguration {
588     public AndGateConfiguration(boolean inverted) {
589       super(inverted);
590 
591       symbol = new GeneralPath();
592       symbol.moveTo(70f, 0f);
593       symbol.lineTo(20f, 0f);
594       symbol.lineTo(20f, 60f);
595       symbol.lineTo(70f, 60f);
596       symbol.append(new Arc2D.Double(40, 0, 60, 60, 270, 180, Arc2D.OPEN), true);
597 
598       // Draw negation symbol for the NAND case.
599       if (isInverted()) {
600         Ellipse2D negationSymbol = new Ellipse2D.Double();
601         negationSymbol.setFrame(100, 26, 8, 8);
602         symbol.append(negationSymbol, false);
603       }
604     }
605   }
606 
607   private static class NotGateConfiguration extends LogicGateConfiguration {
608     public NotGateConfiguration() {
609       super(true);
610       
611       symbol = new GeneralPath();
612       symbol.moveTo(30f, 0f);
613       symbol.lineTo(90f, 30f);
614       symbol.lineTo(30f, 60f);
615       symbol.closePath();
616 
617       Ellipse2D negationSymbol = new Ellipse2D.Double();
618       negationSymbol.setFrame(90, 26, 8, 8);
619       symbol.append(negationSymbol, false);
620     }
621   }
622 }