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