Mercurial > hg > ccmieditor
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 } |