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 y.base.Node;
31  import y.base.NodeList;
32  import y.geom.YPoint;
33  import y.geom.YRectangle;
34  import y.view.Graph2D;
35  import y.view.HitInfo;
36  import y.view.NodeLabel;
37  import y.view.NodePort;
38  import y.view.NodeRealizer;
39  import y.view.NodeScaledPortLocationModel;
40  import y.view.PopupMode;
41  import y.view.PortLabelModel;
42  import y.view.PortLocationModel;
43  import y.view.ShapeNodeRealizer;
44  
45  import java.awt.Component;
46  import java.awt.Graphics;
47  import java.awt.Graphics2D;
48  import java.awt.event.ActionEvent;
49  import javax.swing.AbstractAction;
50  import javax.swing.Icon;
51  import javax.swing.JMenu;
52  import javax.swing.JPopupMenu;
53  
54  /**
55   * Provides controls to add and remove {@link y.view.NodePort}s.
56   *
57   */
58  final class NodePortPopupMode extends PopupMode {
59    private YPoint point;
60  
61  
62    /**
63     * Provides a control to edit and remove {@link y.view.NodeLabel}s.
64     * @param label the label to be edited or removed.
65     * @return a popup menu that allows for editing and removing labels.
66     */
67    public JPopupMenu getNodeLabelPopup( final NodeLabel label ) {
68      final JPopupMenu jpm = new JPopupMenu("NodeLabel");
69      jpm.add(new AbstractAction("Edit Label") {
70        public void actionPerformed( final ActionEvent e ) {
71          editLabel(label);
72        }
73      });
74      jpm.add(new AbstractAction("Delete Label") {
75        public void actionPerformed( final ActionEvent e ) {
76          final NodeRealizer nr = label.getRealizer();
77          nr.removeLabel(label);
78          getGraph2D().updateViews();
79        }
80      });
81      return jpm;
82    }
83  
84    /**
85     * Provides a control to add {@link y.view.NodePort}s of various styles.
86     * @param v the node to whose visual representation ports may be added.
87     * @return a popup menu that allows for adding ports.
88     */
89    public JPopupMenu getNodePopup( final Node v ) {
90      final JPopupMenu jpm = new JPopupMenu("Node");
91      jpm.add(new AbstractAction("Add NodePort") {
92        {
93          setEnabled(false);
94        }
95        public void actionPerformed( final ActionEvent e ) {
96        }
97      });
98      jpm.add(new AddPortAction(PortConfigurations.INSTANCE.portConfigRectangle, v));
99      jpm.add(new AddPortAction(PortConfigurations.INSTANCE.portConfigDynamic, v));
100     jpm.add(new AddPortAction(PortConfigurations.INSTANCE.portConfigEllipse, v));
101     return jpm;
102   }
103 
104   /**
105    * Provides a control to add a label for the specified port, change the
106    * location policy of the specified port, or to remove the specified port
107    * from its associated node.
108    * @param port the port to be changed or removed.
109    * @return a popup menu that allows for adding labels to ports, changing
110    * location policies of ports, and removing ports.
111    */
112   public JPopupMenu getNodePortPopup( final NodePort port ) {
113     final JPopupMenu jpm = new JPopupMenu("NodePort");
114 
115     jpm.add(new AbstractAction("Add Label") {
116       public void actionPerformed( final ActionEvent e ) {
117         editLabel(addPortLabel(port.getRealizer(), port, "Port"));
118       }
119     });
120 
121     final JMenu policies = new JMenu("Change Location Policy");
122     policies.add(new ChangeLocationPolicyAction(
123             port, NodeScaledPortLocationModel.POLICY_DISCRETE));
124     policies.add(new ChangeLocationPolicyAction(
125             port, NodeScaledPortLocationModel.POLICY_BOUNDARY));
126     policies.add(new ChangeLocationPolicyAction(
127             port, NodeScaledPortLocationModel.POLICY_BOUNDARY_CENTER));
128     policies.add(new ChangeLocationPolicyAction(
129             port, NodeScaledPortLocationModel.POLICY_BOUNDARY_INSIDE));
130     policies.add(new ChangeLocationPolicyAction(
131             port, NodeScaledPortLocationModel.POLICY_FREE));
132     policies.addSeparator();
133     policies.add(new ChangeSideLocationAction(
134             port, SidePortLocationModel.SIDE_TOP));
135     policies.add(new ChangeSideLocationAction(
136             port, SidePortLocationModel.SIDE_LEFT));
137     policies.add(new ChangeSideLocationAction(
138             port, SidePortLocationModel.SIDE_BOTTOM));
139     policies.add(new ChangeSideLocationAction(
140             port, SidePortLocationModel.SIDE_RIGHT));
141     jpm.add(policies);
142 
143     jpm.add(new AbstractAction("Remove Port") {
144       public void actionPerformed( final ActionEvent e ) {
145         final Graph2D graph = getGraph2D();
146         graph.firePreEvent();
147         try {
148           removePortImpl(graph, port);
149         } finally {
150           graph.firePostEvent();
151         }
152 
153         graph.updateViews();
154       }
155 
156       private void removePortImpl( final Graph2D graph, final NodePort port ) {
157         final NodeRealizer nr = port.getRealizer();
158         final Node node = nr.getNode();
159         graph.backupRealizers((new NodeList(node)).nodes());
160 
161         NodePort.remove(port);
162       }
163     });
164     return jpm;
165   }
166 
167   /**
168    * Overwritten to support a specific popup menu for {@link y.view.NodePort}
169    * instances.
170    * @param hitInfo hit test information to the specified event position.
171    * @param x absolute x-coordinate of the triggering event.
172    * @param y absolute y-coordinate of the triggering event.
173    * @param popupType requested popup type.
174    * @return a popup menu for the requested type and position.
175    */
176   protected JPopupMenu getPopup(
177           final HitInfo hitInfo,
178           final double x,
179           final double y,
180           final int popupType
181   ) {
182     point = new YPoint(x ,y);
183     return super.getPopup(hitInfo, x, y, popupType);
184   }
185 
186 
187 
188   private NodeLabel addPortLabel(
189           final NodeRealizer nr,
190           final NodePort port,
191           final String text
192   ) {
193     final NodeLabel nl = nr.createNodeLabel();
194     nl.setText(text);
195     nl.setLabelModel(new PortLabelModel(3),
196         PortLabelModel.createParameter(port, PortLabelModel.NORTH));
197     nr.addLabel(nl);
198     return nl;
199   }
200 
201   private void editLabel( final NodeLabel label ) {
202     final YRectangle bnds = label.getBox();
203     view.openLabelEditor(
204             label,
205             bnds.getX(),
206             bnds.getY(),
207             null,
208             true
209             );
210   }
211 
212 
213   /**
214    * Action that changes the
215    * {@link y.view.NodeScaledPortLocationModel#getPortLocationPolicy() port
216    * location policy} for a {@link y.view.NodePort}.
217    */
218   private final class ChangeLocationPolicyAction extends AbstractAction {
219     private final byte policy;
220     private final NodePort port;
221 
222     /**
223      * Initializes a new <code>ChangeLocationPolicyAction</code>.
224      * @param port the {@link y.view.NodePort} whose policy has to be changed.
225      * @param policy the policy specifier that should be used in the specified
226      * port's location model.
227      */
228     ChangeLocationPolicyAction( final NodePort port, final byte policy ) {
229       super(createActionName(policy));
230       this.policy = policy;
231       this.port = port;
232 
233       final PortLocationModel model = port.getModelParameter().getModel();
234       if (model instanceof NodeScaledPortLocationModel &&
235           ((NodeScaledPortLocationModel) model).getPortLocationPolicy() == policy) {
236         setEnabled(false);
237       }
238     }
239 
240     public void actionPerformed( final ActionEvent e ) {
241       final Graph2D graph = getGraph2D();
242       graph.firePreEvent();
243       try {
244         changeLocationPolicyImpl(port, policy);
245       } finally {
246         graph.firePostEvent();
247       }
248 
249       graph.updateViews();
250     }
251 
252     /**
253      * Changes the port location policy of the specified port's location model
254      * to the specified policy.
255      * @param port the {@link y.view.NodePort} whose policy has to be changed.
256      * @param policy the policy specifier that should be used in the specified
257      * port's location model.
258      */
259     private void changeLocationPolicyImpl(
260             final NodePort port, final byte policy
261     ) {
262       final YPoint location = port.getLocation();
263       final PortLocationModel model = port.getModelParameter().getModel();
264       if (model instanceof NodeScaledPortLocationModel) {
265         ((NodeScaledPortLocationModel) model).setPortLocationPolicy(policy);
266         port.setModelParameter(model.createParameter(port.getRealizer(), location));
267       } else {
268         final NodeScaledPortLocationModel nsplm = new NodeScaledPortLocationModel();
269         nsplm.setPortLocationPolicy(policy);
270         port.setModelParameter(nsplm.createParameter(port.getRealizer(), location));
271       }
272     }
273   }
274 
275   /**
276    * Creates a suitable action name for an action that changes the port location
277    * policy of a {@link y.view.NodePort}'s location model.
278    * @param policy the policy specifier that should be used.
279    * @return a suitable action name.
280    */
281   private static String createActionName( final byte policy ) {
282     switch (policy) {
283       case NodeScaledPortLocationModel.POLICY_DISCRETE:
284         return "Discrete";
285       case NodeScaledPortLocationModel.POLICY_BOUNDARY:
286         return "Boundary";
287       case NodeScaledPortLocationModel.POLICY_BOUNDARY_CENTER:
288         return "Boundary or Center";
289       case NodeScaledPortLocationModel.POLICY_BOUNDARY_INSIDE:
290         return "Inside";
291       case NodeScaledPortLocationModel.POLICY_FREE:
292         return "Free";
293       default:
294         return "Unknown";
295     }
296   }
297 
298   /**
299    * Action that restricts the port location to a specific
300    * {@link demo.view.advanced.ports.SidePortLocationModel#getSides() side}
301    * for a {@link y.view.NodePort}.
302    */
303   private final class ChangeSideLocationAction extends AbstractAction {
304     private final int side;
305     private final NodePort port;
306 
307     /**
308      * Initializes a new <code>ChangeSideLocationAction</code>.
309      * @param port the {@link y.view.NodePort} whose policy has to be changed.
310      * @param side the side specifier that should be used in the specified
311      * port's location model.
312      */
313     ChangeSideLocationAction( final NodePort port, final int side ) {
314       super(createActionName(side));
315       this.side = side;
316       this.port = port;
317 
318       final PortLocationModel model = port.getModelParameter().getModel();
319       if (model instanceof SidePortLocationModel &&
320           ((SidePortLocationModel) model).getSides() == side) {
321         setEnabled(false);
322       }
323     }
324 
325     public void actionPerformed( final ActionEvent e ) {
326       final Graph2D graph = getGraph2D();
327       graph.firePreEvent();
328       try {
329         changeSideLocationImpl(port, side);
330       } finally {
331         graph.firePostEvent();
332       }
333 
334       graph.updateViews();
335     }
336 
337     /**
338      * Changes the port location model of the specified port to one that
339      * restricts port locations to the specified side of the port's owner node.
340      * @param port the {@link y.view.NodePort} whose policy has to be changed.
341      * @param side the side specifier that should be used in the specified
342      * port's location model.
343      */
344     private void changeSideLocationImpl( final NodePort port, final int side ) {
345       final YPoint location = port.getLocation();
346       final SidePortLocationModel splm = SidePortLocationModel.newInstance(side);
347       port.setModelParameter(splm.createParameter(port.getRealizer(), location));
348     }
349   }
350 
351   /**
352    * Creates a suitable action name for an action that changes a
353    * {@link y.view.NodePort}'s location model to one that restricts port
354    * locations to one side of the port's owner node.
355    * @param side the side specifier to use.
356    * @return a suitable action name.
357    */
358   private static String createActionName( final int side ) {
359     switch (side) {
360       case SidePortLocationModel.SIDE_TOP:
361         return "Top only";
362       case SidePortLocationModel.SIDE_LEFT:
363         return "Left only";
364       case SidePortLocationModel.SIDE_BOTTOM:
365         return "Bottom only";
366       case SidePortLocationModel.SIDE_RIGHT:
367         return "Right only";
368       default:
369         return "Unknown";
370     }
371   }
372 
373   /**
374    * Action that adds a specifically configured {@link y.view.NodePort} instance
375    * to the visual representation of a node.
376    */
377   private final class AddPortAction extends AbstractAction {
378     private final Node node;
379     private final String configuration;
380 
381     /**
382      * Initializes an new <code>AddPortAction</code>.
383      * @param configurationName the configuration of the {@link y.view.NodePort}
384      * instance that will be created.
385      * @param node the node to which a {@link y.view.NodePort} instance is
386      * added.
387      */
388     AddPortAction( final String configurationName, final Node node ) {
389       super(createActionName(configurationName));
390       this.configuration = configurationName;
391       this.node = node;
392       putValue(SMALL_ICON, new PortIcon(configurationName));
393     }
394 
395     public void actionPerformed( final ActionEvent e ) {
396       final Graph2D graph = getGraph2D();
397       graph.firePreEvent();
398       try {
399         addPortImpl(graph, node, configuration);
400       } finally {
401         graph.firePostEvent();
402       }
403 
404       graph.updateViews();
405     }
406 
407     /**
408      * Adds a new {@link y.view.NodePort} instance with the specified
409      * configuration to the visual representation of the specified node.
410      * @param graph that graph of the node to which a port is added.
411      * @param node the node to which a port is added.
412      * @param portConfigId the name of the port configuration to use.
413      */
414     private void addPortImpl(
415             final Graph2D graph,
416             final Node node,
417             final String portConfigId
418     ) {
419       graph.backupRealizers((new NodeList(node)).nodes());
420 
421       final NodeRealizer nr = graph.getRealizer(node);
422       final YPoint location =
423               point == null
424               ? new YPoint(nr.getCenterX(), nr.getCenterY())
425               : point;
426 
427       final NodeScaledPortLocationModel model = new NodeScaledPortLocationModel();
428       model.setPortLocationPolicy(NodeScaledPortLocationModel.POLICY_BOUNDARY);
429       final NodePort port = new NodePort();
430       port.setModelParameter(model.createParameter(nr, location));
431       port.setConfiguration(portConfigId);
432       nr.addPort(port);
433 
434       addPortLabel(nr, port, "Port " + nr.portCount());
435     }
436   }
437 
438   /**
439    * Creates a suitable action name for an action that creates
440    * {@link y.view.NodePort} instances that use the specified configuration.
441    * @param portConfigId the name of the configuration.
442    * @return a suitable action name.
443    */
444   private static String createActionName( final String portConfigId ) {
445     String s = portConfigId;
446     if (s != null) {
447       s = s.trim();
448       if (s.length() > 0) {
449         s = s.toUpperCase();
450         if (s.startsWith("PORT_")) {
451           s = s.substring(5);
452         }
453         if (s.length() > 1) {
454           s = s.charAt(0) + s.substring(1).toLowerCase();
455         }
456 
457         return s;
458       }
459     }
460     return null;
461   }
462 
463   /**
464    * A simple icon that display a {@link y.view.NodePort}.
465    */
466   private static final class PortIcon implements Icon {
467     private static final int MARGIN = 2;
468 
469     private final NodePort port;
470     private final double x;
471     private final double y;
472     private final int w;
473     private final int h;
474 
475     PortIcon( final String configuration ) {
476       final NodeRealizer dummy = new ShapeNodeRealizer();
477       dummy.setFrame(0, 0, 10, 10);
478       port = new NodePort();
479       dummy.addPort(port);
480       port.setConfiguration(configuration);
481       final YRectangle bnds = port.getBounds();
482       w = (int) Math.ceil(bnds.getWidth()) + 2*MARGIN;
483       h = (int) Math.ceil(bnds.getHeight())  + 2*MARGIN;
484       x = bnds.getX() - MARGIN;
485       y = bnds.getY() - MARGIN;
486     }
487 
488     public int getIconHeight() {
489       return h;
490     }
491 
492     public int getIconWidth() {
493       return w;
494     }
495 
496     public void paintIcon( final Component c, final Graphics g, final int x, final int y ) {
497       final Graphics2D gfx = (Graphics2D) g.create();
498       gfx.translate(x - this.x, y - this.y);
499       port.paint(gfx);
500       gfx.dispose();
501     }
502   }
503 }
504