comparison java/src/uk/ac/qmul/eecs/ccmi/gui/GraphPanel.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 9418ab7b7f3f
children d66dd5880081
comparison
equal deleted inserted replaced
2:4b2f975e35fa 3:9e67171477bc
22 22
23 import java.awt.Color; 23 import java.awt.Color;
24 import java.awt.Dimension; 24 import java.awt.Dimension;
25 import java.awt.Graphics; 25 import java.awt.Graphics;
26 import java.awt.Graphics2D; 26 import java.awt.Graphics2D;
27 import java.awt.event.ActionEvent;
28 import java.awt.event.InputEvent; 27 import java.awt.event.InputEvent;
29 import java.awt.event.KeyEvent;
30 import java.awt.event.MouseAdapter; 28 import java.awt.event.MouseAdapter;
31 import java.awt.event.MouseEvent; 29 import java.awt.event.MouseEvent;
32 import java.awt.event.MouseMotionAdapter; 30 import java.awt.event.MouseMotionAdapter;
33 import java.awt.geom.Point2D; 31 import java.awt.geom.Point2D;
34 import java.awt.geom.Rectangle2D; 32 import java.awt.geom.Rectangle2D;
38 import java.util.LinkedList; 36 import java.util.LinkedList;
39 import java.util.List; 37 import java.util.List;
40 import java.util.ResourceBundle; 38 import java.util.ResourceBundle;
41 import java.util.Set; 39 import java.util.Set;
42 40
43 import javax.swing.AbstractAction;
44 import javax.swing.JOptionPane; 41 import javax.swing.JOptionPane;
45 import javax.swing.JPanel; 42 import javax.swing.JPanel;
46 import javax.swing.KeyStroke;
47 43
48 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionEvent; 44 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionEvent;
49 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionListener; 45 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionListener;
50 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel; 46 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel;
51 import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException; 47 import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException;
52 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement; 48 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
53 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramModelTreeNode;
54 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode; 49 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
50 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
55 import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent; 51 import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
52 import uk.ac.qmul.eecs.ccmi.network.Command;
53 import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
56 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog; 54 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
57 55
58 /** 56 /**
59 * A panel to draw a graph 57 * A panel to draw a graph
60 */ 58 */
86 84
87 selectedElements = new HashSet<DiagramElement>(); 85 selectedElements = new HashSet<DiagramElement>();
88 moveLockedElements = new HashSet<Object>(); 86 moveLockedElements = new HashSet<Object>();
89 87
90 toolbar.addEdgeCreatedListener(new innerEdgeListener()); 88 toolbar.addEdgeCreatedListener(new innerEdgeListener());
91
92 getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0),"delete");
93 getActionMap().put("delete", new AbstractAction(){
94 @Override
95 public void actionPerformed(ActionEvent evt) {
96 /* nothing selected DELETE key has no effect */
97 if(selectedElements.isEmpty())
98 return;
99 /* create a new Set to maintain iterator consistency as elementTakenOut will change selectedItems */
100 HashSet<DiagramElement> iterationSet = new HashSet<DiagramElement>(selectedElements);
101 HashSet<DiagramElement>alreadyLockedElements = new HashSet<DiagramElement>();
102 /* check which, of the selected elements, can be deleted and which ones are currently held by *
103 * other clients. If an element is locked it's removed from the list and put into a separated set */
104 for(Iterator<DiagramElement> itr=iterationSet.iterator(); itr.hasNext();){
105 DiagramElement selected = itr.next();
106 if(!modelUpdater.getLock(selected, Lock.DELETE)){
107 itr.remove();
108 alreadyLockedElements.add(selected);
109 }
110 }
111 ResourceBundle resources = ResourceBundle.getBundle(EditorFrame.class.getName());
112 /* all the elements are locked by other clients */
113 if(iterationSet.isEmpty()){
114 iLog("Could not get lock on any selected element for deletion","");
115 JOptionPane.showMessageDialog(
116 JOptionPane.getFrameForComponent(GraphPanel.this),
117 alreadyLockedElements.size() == 1 ? // singular vs plural
118 resources.getString("dialog.lock_failure.delete") :
119 resources.getString("dialog.lock_failure.deletes"));
120 return;
121 }
122
123 String warning = "";
124 if(!alreadyLockedElements.isEmpty()){
125 StringBuilder builder = new StringBuilder(resources.getString("dialog.lock_failure.deletes_warning"));
126 for(DiagramElement alreadyLocked : alreadyLockedElements)
127 builder.append(alreadyLocked.getName()).append(' ');
128 warning = builder.append('\n').toString();
129 iLog("Could not get lock on some selected element for deletion",warning);
130 }
131
132 iLog("open delete dialog",warning);
133 int answer = JOptionPane.showConfirmDialog(
134 JOptionPane.getFrameForComponent(GraphPanel.this),
135 warning+resources.getString("dialog.confirm.deletions"),
136 resources.getString("dialog.confirm.title"),
137 SpeechOptionPane.YES_NO_OPTION);
138 if(answer == JOptionPane.YES_OPTION){
139 /* the user chose to delete the elements, proceed (locks *
140 * will be automatically removed upon deletion by the server ) */
141 for(DiagramElement selected : iterationSet)
142 modelUpdater.takeOutFromCollection(selected);
143 }else{
144 /* the user chose not to delete the elements, release the acquired locks */
145 for(DiagramElement selected : iterationSet){
146 /* if it's a node all its attached edges were locked as well */
147 /*if(selected instanceof Node){ DONE IN THE SERVER
148 Node n = (Node)selected;
149 for(int i=0; i<n.getEdgesNum();i++){
150 modelUpdater.yieldLock(n.getEdgeAt(i), Lock.DELETE);
151 }
152 }*/
153 modelUpdater.yieldLock(selected, Lock.DELETE);
154 }
155 iLog("cancel delete node dialog","");
156 }
157 }});
158 89
159 /* ---- COLLECTION LISTENER ---- 90 /* ---- COLLECTION LISTENER ----
160 * Adding a collection listener. This listener reacts at changes in the model 91 * Adding a collection listener. This listener reacts at changes in the model
161 * by any source, and thus the graph itself. Basically it refreshes the graph 92 * by any source, and thus the graph itself. Basically it refreshes the graph
162 * and paints again all the nodes and edges. 93 * and paints again all the nodes and edges.
168 if(element instanceof Node) 99 if(element instanceof Node)
169 nodes.add((Node)element); 100 nodes.add((Node)element);
170 else 101 else
171 edges.add((Edge)element); 102 edges.add((Edge)element);
172 checkBounds(element,false); 103 checkBounds(element,false);
173 if(e.getDiagramElement() instanceof Node && e.getSource().equals(model) ){ //FIXME change model into this model source changes 104 if(e.getDiagramElement() instanceof Node && e.getSource().equals(DiagramEventSource.GRPH)){
174 setElementSelected(e.getDiagramElement()); 105 setElementSelected(e.getDiagramElement());
175 dragMode = DRAG_NODE; 106 dragMode = DRAG_NODE;
176 } 107 }
177 revalidate(); 108 revalidate();
178 repaint(); 109 repaint();
179 } 110 }
180 @Override 111 @Override
181 public void elementTakenOut(final CollectionEvent e) { 112 public void elementTakenOut(final CollectionEvent e) {
182 DiagramElement element = e.getDiagramElement(); 113 DiagramElement element = e.getDiagramElement();
183 if(element instanceof Node){ 114 if(element instanceof Node){
184 if(nodePopup != null && nodePopup.nodeRef.equals(element)) 115 if(nodePopup != null && nodePopup.getElement().equals(element))
185 nodePopup.setVisible(false); 116 nodePopup.setVisible(false);
186 nodes.remove(element); 117 nodes.remove(element);
187 } 118 }
188 else{ 119 else{
189 if(edgePopup != null && edgePopup.edgeRef.equals(element)) 120 if(edgePopup != null && edgePopup.getElement().equals(element))
190 edgePopup.setVisible(false); 121 edgePopup.setVisible(false);
191 edges.remove(element); 122 edges.remove(element);
192 } 123 }
193 checkBounds(e.getDiagramElement(),true); 124 checkBounds(e.getDiagramElement(),true);
194 removeElementFromSelection(e.getDiagramElement()); 125 removeElementFromSelection(e.getDiagramElement());
227 if((event.getModifiers() & InputEvent.BUTTON1_MASK) == 0) { 158 if((event.getModifiers() & InputEvent.BUTTON1_MASK) == 0) {
228 if(e != null){ 159 if(e != null){
229 if( e.contains(mousePoint)){ 160 if( e.contains(mousePoint)){
230 Node extremityNode = e.getClosestNode(mousePoint,EDGE_END_MIN_CLICK_DIST); 161 Node extremityNode = e.getClosestNode(mousePoint,EDGE_END_MIN_CLICK_DIST);
231 if(extremityNode == null){ // click far from the attached nodes, only prompt with set name item 162 if(extremityNode == null){ // click far from the attached nodes, only prompt with set name item
232 EdgePopupMenu pop = new EdgePopupMenu(e,GraphPanel.this,modelUpdater); 163 CCmIPopupMenu.EdgePopupMenu pop = new CCmIPopupMenu.EdgePopupMenu(e,GraphPanel.this,modelUpdater,selectedElements);
233 edgePopup = pop; 164 edgePopup = pop;
234 pop.show(GraphPanel.this, event.getX(), event.getY()); 165 pop.show(GraphPanel.this, event.getX(), event.getY());
235 }else{ // click near an attached nodes, prompt for name change, set end label and select arrow head 166 }else{ // click near an attached nodes, prompt for name change, set end label and select arrow head
236 EdgePopupMenu pop = new EdgePopupMenu(e,extremityNode,GraphPanel.this,modelUpdater); 167 CCmIPopupMenu.EdgePopupMenu pop = new CCmIPopupMenu.EdgePopupMenu(e,extremityNode,GraphPanel.this,modelUpdater,selectedElements);
237 edgePopup = pop; 168 edgePopup = pop;
238 pop.show(GraphPanel.this, event.getX(), event.getY()); 169 pop.show(GraphPanel.this, event.getX(), event.getY());
239 } 170 }
240 } 171 }
241 }else if(n != null){ 172 }else if(n != null){
242 NodePopupMenu pop = new NodePopupMenu(n,GraphPanel.this,modelUpdater); 173 CCmIPopupMenu.NodePopupMenu pop = new CCmIPopupMenu.NodePopupMenu(n,GraphPanel.this,modelUpdater,selectedElements);
243 nodePopup = pop; 174 nodePopup = pop;
244 pop.show(GraphPanel.this, event.getX(), event.getY()); 175 pop.show(GraphPanel.this, event.getX(), event.getY());
245 }else 176 }else
246 return; 177 return;
247 } 178 }
248 179
249 /* - one click && palette == select - */ 180 /* - one click && palette == select - */
250 else if (tool == null){ 181 else if (tool == null){
251 if(n != null){ // node selected 182 if(n != null){ // node selected
252 if (isCtrl) 183 if (isCtrl){
253 addElementToSelection(n,false); 184 addElementToSelection(n,false);
254 else 185 }else{
255 setElementSelected(n); 186 setElementSelected(n);
187 }
256 dragMode = DRAG_NODE; 188 dragMode = DRAG_NODE;
257 }else if (e != null){ // edge selected 189 }else if (e != null){ // edge selected
258 if (isCtrl){ 190 if (isCtrl){
259 addElementToSelection(e,false); 191 addElementToSelection(e,false);
260 dragMode = DRAG_NODE; 192 dragMode = DRAG_NODE;
261 }else{ 193 }else{
262 setElementSelected(e); 194 setElementSelected(e);
263 modelUpdater.startMove(e, mousePoint); 195 modelUpdater.startMove(e, mousePoint,DiagramEventSource.GRPH);
264 dragMode = DRAG_EDGE; 196 dragMode = DRAG_EDGE;
265 } 197 }
266 }else{ // nothing selected : make selection lasso 198 }else{ // nothing selected : make selection lasso
267 if (!isCtrl) 199 if (!isCtrl)
268 clearSelection(); 200 clearSelection();
282 Node prototype = (Node) tool; 214 Node prototype = (Node) tool;
283 Node newNode = (Node) prototype.clone(); 215 Node newNode = (Node) prototype.clone();
284 Rectangle2D bounds = newNode.getBounds(); 216 Rectangle2D bounds = newNode.getBounds();
285 /* perform the translation from the origin */ 217 /* perform the translation from the origin */
286 newNode.translate(new Point2D.Double(), mousePoint.getX() - bounds.getX(), 218 newNode.translate(new Point2D.Double(), mousePoint.getX() - bounds.getX(),
287 mousePoint.getY() - bounds.getY()); 219 mousePoint.getY() - bounds.getY(),DiagramEventSource.NONE);
288 /* log stuff */ 220 /* log stuff */
289 iLog("insert node",""+((newNode.getId() == DiagramElement.NO_ID) ? "(no id)" : newNode.getId())); 221 iLog("insert node",""+((newNode.getId() == DiagramElement.NO_ID) ? "(no id)" : newNode.getId()));
290 /* insert the node into the model (no lock needed) */ 222 /* insert the node into the model (no lock needed) */
291 modelUpdater.insertInCollection(newNode); 223 modelUpdater.insertInCollection(newNode,DiagramEventSource.GRPH);
292 } 224 }
293 } 225 }
294 226
295 lastMousePoint = mousePoint; 227 lastMousePoint = mousePoint;
296 mouseDownPoint = mousePoint; 228 mouseDownPoint = mousePoint;
302 final Point2D mousePoint = new Point2D.Double( 234 final Point2D mousePoint = new Point2D.Double(
303 (event.getX()+minX)/zoom, 235 (event.getX()+minX)/zoom,
304 (event.getY()+minY)/zoom 236 (event.getY()+minY)/zoom
305 ); 237 );
306 if(lastSelected != null){ 238 if(lastSelected != null){
307 if(lastSelected instanceof Node){ 239 if(lastSelected instanceof Node || selectedElements.size() > 1){ // differentiate between translate and edge bending
308 if(wasMoving){ 240 if(wasMoving){
309 iLog("move selected stop",mousePoint.getX()+" "+ mousePoint.getY()); 241 iLog("move selected stop",mousePoint.getX()+" "+ mousePoint.getY());
310 for(Object element : moveLockedElements){ 242 for(Object element : moveLockedElements){
311 modelUpdater.stopMove((GraphElement)element); 243 modelUpdater.stopMove((GraphElement)element,DiagramEventSource.GRPH);
312 modelUpdater.yieldLock((DiagramModelTreeNode)element, Lock.MOVE); 244 boolean isNode = element instanceof Node;
245 modelUpdater.yieldLock((DiagramTreeNode)element,
246 Lock.MOVE,
247 new DiagramEventActionSource(
248 DiagramEventSource.GRPH,
249 isNode ? Command.Name.STOP_NODE_MOVE : Command.Name.STOP_EDGE_MOVE,
250 ((DiagramElement)element).getId(),((DiagramElement)element).getName()));
313 } 251 }
314 moveLockedElements.clear(); 252 moveLockedElements.clear();
315 } 253 }
316 }else{ // instanceof Edge 254 }else{ // instanceof Edge && selectedelements.size() = 1. Bending
317 if(wasMoving){ 255 if(wasMoving){
318 iLog("bend edge stop",mousePoint.getX()+" "+ mousePoint.getY()); 256 iLog("bend edge stop",mousePoint.getX()+" "+ mousePoint.getY());
319 if(moveLockedEdge != null){ 257 if(moveLockedEdge != null){
320 modelUpdater.stopMove(moveLockedEdge); 258 modelUpdater.stopMove(moveLockedEdge,DiagramEventSource.GRPH);
321 modelUpdater.yieldLock(moveLockedEdge, Lock.MOVE); 259 modelUpdater.yieldLock(moveLockedEdge, Lock.MOVE, new DiagramEventActionSource(
260 DiagramEventSource.GRPH,
261 Command.Name.BEND,
262 moveLockedEdge.getId(),
263 moveLockedEdge.getName()));
322 moveLockedEdge = null; 264 moveLockedEdge = null;
323 } 265 }
324 } 266 }
325 } 267 }
326 } 268 }
346 wasMoving = true; 288 wasMoving = true;
347 /* when the motion starts, we need to get the move-lock from the server */ 289 /* when the motion starts, we need to get the move-lock from the server */
348 Iterator<DiagramElement> iterator = selectedElements.iterator(); 290 Iterator<DiagramElement> iterator = selectedElements.iterator();
349 while(iterator.hasNext()){ 291 while(iterator.hasNext()){
350 DiagramElement element = iterator.next(); 292 DiagramElement element = iterator.next();
351 if(modelUpdater.getLock(element, Lock.MOVE)){ 293 boolean isNode = element instanceof Node;
294 if(modelUpdater.getLock(element,
295 Lock.MOVE,
296 new DiagramEventActionSource(
297 DiagramEventSource.GRPH,
298 isNode ? Command.Name.TRANSLATE_NODE : Command.Name.TRANSLATE_EDGE,
299 element.getId(),
300 element.getName()))){
352 moveLockedElements.add(element); 301 moveLockedElements.add(element);
353 }else{ 302 }else{
354 iLog("Could not get move lock for element",DiagramElement.toLogString(element)); 303 iLog("Could not get move lock for element",DiagramElement.toLogString(element));
355 iterator.remove(); 304 iterator.remove();
356 } 305 }
358 iLog("move selected start",mousePoint.getX()+" "+ mousePoint.getY()); 307 iLog("move selected start",mousePoint.getX()+" "+ mousePoint.getY());
359 } 308 }
360 309
361 for (DiagramElement selected : selectedElements){ 310 for (DiagramElement selected : selectedElements){
362 if(selected instanceof Node) 311 if(selected instanceof Node)
363 modelUpdater.translate((Node)selected, lastMousePoint, dx, dy); 312 modelUpdater.translate((Node)selected, lastMousePoint, dx, dy,DiagramEventSource.GRPH);
364 else 313 else
365 modelUpdater.translate((Edge)selected, lastMousePoint, dx, dy); 314 modelUpdater.translate((Edge)selected, lastMousePoint, dx, dy,DiagramEventSource.GRPH);
366 } 315 }
367 } else if(dragMode == DRAG_EDGE){ 316 } else if(dragMode == DRAG_EDGE){
368 if(!wasMoving){ 317 if(!wasMoving){
369 wasMoving = true; 318 wasMoving = true;
370 if(modelUpdater.getLock(lastSelected, Lock.MOVE)) 319 if(modelUpdater.getLock(lastSelected,
320 Lock.MOVE,
321 new DiagramEventActionSource(
322 DiagramEventSource.GRPH,
323 Command.Name.BEND,
324 lastSelected.getId(),
325 lastSelected.getName()))
326 ){
371 moveLockedEdge = (Edge)lastSelected; 327 moveLockedEdge = (Edge)lastSelected;
372 else 328 }else{
373 iLog("Could not get move lock for element",DiagramElement.toLogString(lastSelected)); 329 iLog("Could not get move lock for element",DiagramElement.toLogString(lastSelected));
330 }
374 iLog("bend edge start",mousePoint.getX()+" "+ mousePoint.getY()); 331 iLog("bend edge start",mousePoint.getX()+" "+ mousePoint.getY());
375 } 332 }
376 if(moveLockedEdge != null) 333 if(moveLockedEdge != null)
377 modelUpdater.bend(moveLockedEdge, new Point2D.Double(mousePoint.getX(), mousePoint.getY())); 334 modelUpdater.bend(moveLockedEdge, new Point2D.Double(mousePoint.getX(), mousePoint.getY()),DiagramEventSource.GRPH);
378 } else if (dragMode == DRAG_LASSO){ 335 } else if (dragMode == DRAG_LASSO){
379 double x1 = mouseDownPoint.getX(); 336 double x1 = mouseDownPoint.getX();
380 double y1 = mouseDownPoint.getY(); 337 double y1 = mouseDownPoint.getY();
381 double x2 = mousePoint.getX(); 338 double x2 = mousePoint.getX();
382 double y2 = mousePoint.getY(); 339 double y2 = mousePoint.getY();
696 if(element instanceof Node) 653 if(element instanceof Node)
697 nodesToConnect.add((Node)element); 654 nodesToConnect.add((Node)element);
698 } 655 }
699 try { 656 try {
700 e.connect(nodesToConnect); 657 e.connect(nodesToConnect);
701 modelUpdater.insertInCollection(e); 658 /* perform the command, no lock is needed for inserting */
659 modelUpdater.insertInCollection(e,DiagramEventSource.GRPH);
702 } catch (ConnectNodesException cnEx) { 660 } catch (ConnectNodesException cnEx) {
703 JOptionPane.showMessageDialog(GraphPanel.this, 661 JOptionPane.showMessageDialog(GraphPanel.this,
704 cnEx.getLocalizedMessage(), 662 cnEx.getLocalizedMessage(),
705 ResourceBundle.getBundle(EditorFrame.class.getName()).getString("dialog.error.title"), 663 ResourceBundle.getBundle(EditorFrame.class.getName()).getString("dialog.error.title"),
706 JOptionPane.ERROR_MESSAGE); 664 JOptionPane.ERROR_MESSAGE);
714 private DiagramModelUpdater modelUpdater; 672 private DiagramModelUpdater modelUpdater;
715 private CollectionModel<Node,Edge> model; 673 private CollectionModel<Node,Edge> model;
716 674
717 private Grid grid; 675 private Grid grid;
718 private GraphToolbar toolbar; 676 private GraphToolbar toolbar;
719 private NodePopupMenu nodePopup; 677 private CCmIPopupMenu.NodePopupMenu nodePopup;
720 private EdgePopupMenu edgePopup; 678 private CCmIPopupMenu.EdgePopupMenu edgePopup;
721 679
722 private double zoom; 680 private double zoom;
723 private double gridSize; 681 private double gridSize;
724 private boolean hideGrid; 682 private boolean hideGrid;
725 private boolean wasMoving; 683 private boolean wasMoving;