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