comparison java/src/uk/ac/qmul/eecs/ccmi/gui/CCmIPopupMenu.java @ 3:9e67171477bc

PHANTOM Omni Heptic device release
author Fiore Martin <fiore@eecs.qmul.ac.uk>
date Wed, 25 Apr 2012 17:09:09 +0100
parents
children d66dd5880081
comparison
equal deleted inserted replaced
2:4b2f975e35fa 3:9e67171477bc
1 /*
2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
3
4 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19 package uk.ac.qmul.eecs.ccmi.gui;
20
21 import java.awt.Component;
22 import java.awt.event.ActionEvent;
23 import java.awt.event.ActionListener;
24 import java.text.MessageFormat;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.ResourceBundle;
31 import java.util.Set;
32
33 import javax.swing.JMenuItem;
34 import javax.swing.JOptionPane;
35 import javax.swing.JPopupMenu;
36
37 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
38 import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties;
39 import uk.ac.qmul.eecs.ccmi.network.AwarenessMessage;
40 import uk.ac.qmul.eecs.ccmi.network.Command;
41 import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
42 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
43
44 /**
45 * This class provides the two menus to handle nodes and edges on the visual graph. This class
46 * provides an abstract implementation common to both the node and edge menus. The specific
47 * implementations are internal static classes, inheriting from this class.
48 *
49 */
50 @SuppressWarnings("serial")
51 public abstract class CCmIPopupMenu extends JPopupMenu {
52 /**
53 * This constructor is called by subclasses constructor.
54 *
55 * @param reference the element this menu refers to (it popped up by right-clicking on it)
56 * @param parentComponent the component where the menu is going to be displayed
57 * @param modelUpdater the model updater to make changed to {@code reference}
58 * @param selectedElements other elements eventually selected on the graph, which are going
59 * to undergo the same changes as {@code reference}, being selected together with it.
60 */
61 protected CCmIPopupMenu(DiagramElement reference,
62 Component parentComponent, DiagramModelUpdater modelUpdater,
63 Set<DiagramElement> selectedElements) {
64 super();
65 this.modelUpdater = modelUpdater;
66 this.parentComponent = parentComponent;
67 this.reference = reference;
68 this.selectedElements = selectedElements;
69 }
70
71 /**
72 * Returns the the element this menu refers to.
73 * @return the element this menu refers to.
74 */
75 public DiagramElement getElement(){
76 return reference;
77 }
78
79 /**
80 * Add the a menu item to this menu. A menu item, once clicked on, will prompt the user for a new name
81 * for the referee element and will execute the update through the modelUpdater passed as argument
82 * to the constructor.
83 */
84 protected void addNameMenuItem() {
85 /* add set name menu item */
86 JMenuItem setNameMenuItem = new JMenuItem(
87 resources.getString("menu.set_name"));
88 setNameMenuItem.addActionListener(new ActionListener() {
89 @Override
90 public void actionPerformed(ActionEvent e) {
91 String type = (reference instanceof Node) ? "node" : "edge";
92 if (!modelUpdater.getLock(reference, Lock.NAME,
93 new DiagramEventActionSource(DiagramEventSource.GRPH,
94 Command.Name.SET_NODE_NAME, reference.getId(),reference.getName()))) {
95 iLog("Could not get the lock on " + type + " for renaming",
96 DiagramElement.toLogString(reference));
97 JOptionPane.showMessageDialog(parentComponent,
98 MessageFormat.format(resources
99 .getString("dialog.lock_failure.name"),
100 type));
101 return;
102 }
103 iLog("open rename " + type + " dialog",
104 DiagramElement.toLogString(reference));
105 String name = JOptionPane.showInputDialog(parentComponent,
106 MessageFormat.format(
107 resources.getString("dialog.input.name"),
108 reference.getName()), reference.getName());
109 if (name == null)
110 iLog("cancel rename " + type + " dialog",
111 DiagramElement.toLogString(reference));
112 else
113 /* node has been locked at selection time */
114 modelUpdater.setName(reference, name.trim(),
115 DiagramEventSource.GRPH);
116 modelUpdater.yieldLock(reference, Lock.NAME,
117 new DiagramEventActionSource(DiagramEventSource.GRPH,
118 Command.Name.SET_NODE_NAME, reference.getId(),reference.getName()));
119 }
120
121 });
122 add(setNameMenuItem);
123 }
124
125 /**
126 * Add the a delete item to this menu. A menu item, once clicked on, will prompt the user for a confirmation
127 * for the deletion of referee element and will execute the update through the modelUpdater passed as argument
128 * to the constructor.
129 */
130 protected void addDeleteMenuItem() {
131 JMenuItem deleteMenuItem = new JMenuItem(resources.getString("menu.delete"));
132 deleteMenuItem.addActionListener(new ActionListener() {
133 @Override
134 public void actionPerformed(ActionEvent evt) {
135 /* create a new Set to maintain iterator consistency as elementTakenOut will change selectedItems */
136 List<DiagramElement> workList = new ArrayList<DiagramElement>(selectedElements);
137 /* right click on an element with no selection involved */
138 if(workList.isEmpty()){
139 workList.add(reference);
140 /* right click on an element with other elements selected, thus we ignore the *
141 * currently selected elements and try to delete only the right clicked one. */
142 }else if(!workList.contains(reference)){
143 workList.clear();
144 workList.add(reference);
145 }else{
146 /* If the right clicked element selected together with other elements, try to *
147 * delete them all. First delete all edges and then all nodes to keep consistency. *
148 * We are deleting a bunch of objects and if a node is deleted before an edge *
149 * attached to it, then an exception will be triggered at the moment of edge *
150 * deletion because the edge will be already deleted as a result of the node deletion */
151 Collections.sort(workList, new Comparator<DiagramElement>(){
152 @Override
153 public int compare(DiagramElement e1,DiagramElement e2) {
154 boolean e1isEdge = e1 instanceof Edge;
155 boolean e2isEdge = e2 instanceof Edge;
156 if(e1isEdge && !e2isEdge){
157 return -1;
158 }
159 if(!e1isEdge && e2isEdge){
160 return 1;
161 }
162 return 0;
163 }
164 });
165 }
166
167 List<DiagramElement>alreadyLockedElements = new ArrayList<DiagramElement>(workList.size());
168 /* check which, of the selected elements, can be deleted and which ones are currently held by *
169 * other clients. If an element is locked it's removed from the list and put into a separated set */
170 for(Iterator<DiagramElement> itr=workList.iterator(); itr.hasNext();){
171 DiagramElement selected = itr.next();
172 boolean isNode = selected instanceof Node;
173 if(!modelUpdater.getLock(selected,
174 Lock.DELETE,
175 new DiagramEventActionSource(
176 DiagramEventSource.GRPH,
177 isNode ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE,
178 selected.getId(),selected.getName()))){
179 itr.remove();
180 alreadyLockedElements.add(selected);
181 }
182 }
183
184 /* all the elements are locked by other clients */
185 if(workList.isEmpty()){
186 iLog("Could not get lock on any selected element for deletion","");
187 JOptionPane.showMessageDialog(
188 JOptionPane.getFrameForComponent(parentComponent),
189 alreadyLockedElements.size() == 1 ? // singular vs plural
190 resources.getString("dialog.lock_failure.delete") :
191 resources.getString("dialog.lock_failure.deletes"));
192 return;
193 }
194
195 String warning = "";
196 if(!alreadyLockedElements.isEmpty()){
197 StringBuilder builder = new StringBuilder(resources.getString("dialog.lock_failure.deletes_warning"));
198 for(DiagramElement alreadyLocked : alreadyLockedElements)
199 builder.append(alreadyLocked.getName()).append(' ');
200 warning = builder.append('\n').toString();
201 iLog("Could not get lock on some selected element for deletion",warning);
202 }
203
204 iLog("open delete dialog",warning);
205 int answer = JOptionPane.showConfirmDialog(
206 JOptionPane.getFrameForComponent(parentComponent),
207 warning+resources.getString("dialog.confirm.deletions"),
208 resources.getString("dialog.confirm.title"),
209 SpeechOptionPane.YES_NO_OPTION);
210 if(answer == JOptionPane.YES_OPTION){
211 /* the user chose to delete the elements, proceed (locks *
212 * will be automatically removed upon deletion by the server ) */
213 for(DiagramElement selected : workList){
214 modelUpdater.takeOutFromCollection(selected,DiagramEventSource.GRPH);
215 modelUpdater.sendAwarenessMessage(
216 AwarenessMessage.Name.STOP_A,
217 new DiagramEventActionSource(DiagramEventSource.TREE,
218 (selected instanceof Node) ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE,
219 selected.getId(),selected.getName())
220 );
221 }
222 }else{
223 /* the user chose not to delete the elements, release the acquired locks */
224 for(DiagramElement selected : workList){
225 /* if it's a node all its attached edges were locked as well */
226 /*if(selected instanceof Node){ DONE IN THE SERVER
227 Node n = (Node)selected;
228 for(int i=0; i<n.getEdgesNum();i++){
229 modelUpdater.yieldLock(n.getEdgeAt(i), Lock.DELETE);
230 }}*/
231 boolean isNode = selected instanceof Node;
232 modelUpdater.yieldLock(selected,
233 Lock.DELETE,
234 new DiagramEventActionSource(
235 DiagramEventSource.GRPH,
236 isNode ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE,
237 selected.getId(),selected.getName()));
238 }
239 iLog("cancel delete node dialog","");
240 }
241 }
242 });
243 add(deleteMenuItem);
244 }
245
246 /**
247 * Performs the log in the InteractionLog.
248 * @param action the action to log.
249 * @param args additional arguments to add to the log.
250 *
251 * @see uk.ac.qmul.eecs.ccmi.utils#InteractionLog
252 */
253 protected void iLog(String action, String args) {
254 InteractionLog.log("GRAPH", action, args);
255 }
256
257 /**
258 *
259 * A popup menu to perform changes (e.g. delete, rename etc.) to a node from the visual graph.
260 *
261 */
262 public static class NodePopupMenu extends CCmIPopupMenu {
263 /**
264 *
265 * @param node the node this menu refers to.
266 * @param parentComponent the component where the menu is going to be displayed.
267 * @param modelUpdater the model updater used to make changed to {@code node}.
268 * @param selectedElements other elements eventually selected on the graph, which are going
269 * to undergo the same changes as {@code node}, being selected together with it.
270 */
271 NodePopupMenu(Node node, Component parentComponent,
272 DiagramModelUpdater modelUpdater,
273 Set<DiagramElement> selectedElements) {
274 super(node, parentComponent, modelUpdater, selectedElements);
275 addNameMenuItem();
276 addPropertyMenuItem();
277 addDeleteMenuItem();
278 }
279
280 private void addPropertyMenuItem() {
281 final Node nodeRef = (Node) reference;
282 /* if the node has no properties defined, then don't add the menu item */
283 if(nodeRef.getProperties().isNull())
284 return;
285 /* add set property menu item*/
286 JMenuItem setPropertiesMenuItem = new JMenuItem(resources.getString("menu.set_properties"));
287 setPropertiesMenuItem.addActionListener(new ActionListener(){
288 @Override
289 public void actionPerformed(ActionEvent e) {
290 if(!modelUpdater.getLock(nodeRef, Lock.PROPERTIES, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_PROPERTIES,nodeRef.getId(),reference.getName()))){
291 iLog("Could not get the lock on node for properties",DiagramElement.toLogString(nodeRef));
292 JOptionPane.showMessageDialog(parentComponent, resources.getString("dialog.lock_failure.properties"));
293 return;
294 }
295 iLog("open edit properties dialog",DiagramElement.toLogString(nodeRef));
296 NodeProperties properties = PropertyEditorDialog.showDialog(JOptionPane.getFrameForComponent(parentComponent),nodeRef.getPropertiesCopy());
297 if(properties == null){ // user clicked on cancel
298 iLog("cancel edit properties dialog",DiagramElement.toLogString(nodeRef));
299 modelUpdater.yieldLock(nodeRef, Lock.PROPERTIES, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_PROPERTIES,nodeRef.getId(),reference.getName()));
300 return;
301 }
302 if(!properties.isNull())
303 modelUpdater.setProperties(nodeRef,properties,DiagramEventSource.GRPH);
304 modelUpdater.yieldLock(nodeRef, Lock.PROPERTIES,new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_PROPERTIES,nodeRef.getId(),reference.getName()));
305 }
306 });
307 add(setPropertiesMenuItem);
308 }
309 }
310
311 /**
312 * A popup menu to perform changes (e.g. delete, rename etc.) to a edge from the visual graph.
313 */
314 public static class EdgePopupMenu extends CCmIPopupMenu {
315 /**
316 * Constructs an {@code EdgePopupMenu} to perform changes to an edge from the visual diagram.
317 * This constructor is normally called when the user clicks in the neighbourhood of a node
318 * connected to this edge. the menu will then include items to change an end label or
319 * an arrow head.
320 * @param edge the edge this menu refers to.
321 * @param node one attached node some menu item will refer to.
322 * @param parentComponent the component where the menu is going to be displayed.
323 * @param modelUpdater the model updater used to make changed to {@code edge}.
324 * @param selectedElements other elements eventually selected on the graph, which are going
325 * to undergo the same changes as {@code edge}, being selected together with it.
326 */
327 public EdgePopupMenu( Edge edge, Node node, Component parentComponent, DiagramModelUpdater modelUpdater,
328 Set<DiagramElement> selectedElements){
329 super(edge,parentComponent,modelUpdater,selectedElements);
330 addNameMenuItem();
331 if(node != null){
332 nodeRef = node;
333 Object[] arrowHeads = new Object[edge.getAvailableEndDescriptions().length + 1];
334 for(int i=0;i<edge.getAvailableEndDescriptions().length;i++){
335 arrowHeads[i] = edge.getAvailableEndDescriptions()[i].toString();
336 }
337 arrowHeads[arrowHeads.length-1] = Edge.NO_ENDDESCRIPTION_STRING;
338 addEndMenuItems(arrowHeads);
339 }
340 addDeleteMenuItem();
341 }
342
343 /**
344 * Constructs an {@code EdgePopupMenu} to perform changes to an edge from the visual diagram.
345 * This constructor is normally called when the user clicks around the midpoint of the edge
346 * @param edge the edge this menu refers to.
347 * @param parentComponent the component where the menu is going to be displayed.
348 * @param modelUpdater the model updater used to make changed to {@code edge}.
349 * @param selectedElements other elements eventually selected on the graph, which are going
350 * to undergo the same changes as {@code edge}, being selected together with it.
351 */
352 public EdgePopupMenu( Edge edge, Component parentComponent, DiagramModelUpdater modelUpdater,
353 Set<DiagramElement> selectedElements){
354 this(edge,null,parentComponent,modelUpdater,selectedElements);
355 }
356
357 private void addEndMenuItems(final Object[] arrowHeads){
358 final Edge edgeRef = (Edge)reference;
359 /* Label menu item */
360 JMenuItem setLabelMenuItem = new JMenuItem(resources.getString("menu.set_label"));
361 setLabelMenuItem.addActionListener(new ActionListener(){
362 @Override
363 public void actionPerformed(ActionEvent evt) {
364 if(!modelUpdater.getLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDLABEL,edgeRef.getId(),reference.getName()))){
365 iLog("Could not get lock on edge for end label",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
366 JOptionPane.showMessageDialog(parentComponent, resources.getString("dialog.lock_failure.end"));
367 return;
368 }
369 iLog("open edge label dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
370 String text = JOptionPane.showInputDialog(parentComponent,resources.getString("dialog.input.label"));
371 if(text != null)
372 modelUpdater.setEndLabel(edgeRef,nodeRef,text,DiagramEventSource.GRPH);
373 else
374 iLog("cancel edge label dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
375 modelUpdater.yieldLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDLABEL,edgeRef.getId(),reference.getName()));
376 }
377 });
378 add(setLabelMenuItem);
379
380 if(arrowHeads.length > 1){
381 /* arrow head menu item */
382 JMenuItem selectArrowHeadMenuItem = new JMenuItem(resources.getString("menu.choose_arrow_head"));
383 selectArrowHeadMenuItem.addActionListener(new ActionListener(){
384 @Override
385 public void actionPerformed(ActionEvent e) {
386 if(!modelUpdater.getLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDDESCRIPTION,edgeRef.getId(),reference.getName()))){
387 iLog("Could not get lock on edge for arrow head",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
388 JOptionPane.showMessageDialog(parentComponent, resources.getString("dialog.lock_failure.end"));
389 return;
390 }
391 iLog("open select arrow head dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
392 String arrowHead = (String)JOptionPane.showInputDialog(
393 parentComponent,
394 resources.getString("dialog.input.arrow"),
395 resources.getString("dialog.input.arrow.title"),
396 JOptionPane.PLAIN_MESSAGE,
397 null,
398 arrowHeads,
399 arrowHeads);
400 if(arrowHead == null){
401 iLog("cancel select arrow head dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef));
402 modelUpdater.yieldLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDDESCRIPTION,edgeRef.getId(),reference.getName()));
403 return;
404 }
405 for(int i=0; i<edgeRef.getAvailableEndDescriptions().length;i++){
406 if(edgeRef.getAvailableEndDescriptions()[i].toString().equals(arrowHead)){
407 modelUpdater.setEndDescription(edgeRef, nodeRef, i,DiagramEventSource.GRPH);
408 modelUpdater.yieldLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDDESCRIPTION,edgeRef.getId(),reference.getName()));
409 return;
410 }
411 }
412 /* the user selected the none menu item */
413 modelUpdater.setEndDescription(edgeRef,nodeRef, Edge.NO_END_DESCRIPTION_INDEX,DiagramEventSource.GRPH);
414 modelUpdater.yieldLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDDESCRIPTION,edgeRef.getId(),reference.getName()));
415 }
416 });
417 add(selectArrowHeadMenuItem);
418 }
419 }
420
421 private Node nodeRef;
422 }
423
424 /**
425 * the model updater used to make changed to {@code reference}.
426 */
427 protected DiagramModelUpdater modelUpdater;
428 /**
429 * the component where the menu is going to be displayed.
430 */
431 protected Component parentComponent;
432 /**
433 * the element this menu refers to.
434 */
435 protected DiagramElement reference;
436 /**
437 * other elements eventually selected on the graph, which are going
438 * to undergo the same changes as {@code reference}, being selected together with it.
439 */
440 protected Set<DiagramElement> selectedElements;
441 private static ResourceBundle resources = ResourceBundle.getBundle(CCmIPopupMenu.class.getName());
442 }