fiore@0
|
1 /*
|
fiore@0
|
2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
|
fiore@0
|
3
|
fiore@0
|
4 Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
|
fiore@0
|
5 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
|
fiore@0
|
6
|
fiore@0
|
7 This program is free software: you can redistribute it and/or modify
|
fiore@0
|
8 it under the terms of the GNU General Public License as published by
|
fiore@0
|
9 the Free Software Foundation, either version 3 of the License, or
|
fiore@0
|
10 (at your option) any later version.
|
fiore@0
|
11
|
fiore@0
|
12 This program is distributed in the hope that it will be useful,
|
fiore@0
|
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
fiore@0
|
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
fiore@0
|
15 GNU General Public License for more details.
|
fiore@0
|
16
|
fiore@0
|
17 You should have received a copy of the GNU General Public License
|
fiore@0
|
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
|
fiore@0
|
19 */
|
fiore@0
|
20
|
fiore@0
|
21 package uk.ac.qmul.eecs.ccmi.gui;
|
fiore@0
|
22
|
fiore@0
|
23 import java.awt.Color;
|
fiore@0
|
24 import java.awt.Dimension;
|
fiore@0
|
25 import java.awt.Graphics;
|
fiore@0
|
26 import java.awt.Graphics2D;
|
fiore@0
|
27 import java.awt.event.InputEvent;
|
fiore@0
|
28 import java.awt.event.MouseAdapter;
|
fiore@0
|
29 import java.awt.event.MouseEvent;
|
fiore@0
|
30 import java.awt.event.MouseMotionAdapter;
|
fiore@0
|
31 import java.awt.geom.Point2D;
|
fiore@0
|
32 import java.awt.geom.Rectangle2D;
|
fiore@0
|
33 import java.util.ArrayList;
|
fiore@0
|
34 import java.util.HashSet;
|
fiore@0
|
35 import java.util.Iterator;
|
fiore@0
|
36 import java.util.LinkedList;
|
fiore@0
|
37 import java.util.List;
|
fiore@0
|
38 import java.util.ResourceBundle;
|
fiore@0
|
39 import java.util.Set;
|
fiore@0
|
40
|
fiore@0
|
41 import javax.swing.JOptionPane;
|
fiore@0
|
42 import javax.swing.JPanel;
|
fiore@0
|
43
|
fiore@0
|
44 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionEvent;
|
fiore@0
|
45 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionListener;
|
fiore@0
|
46 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel;
|
fiore@0
|
47 import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException;
|
fiore@0
|
48 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
|
fiore@0
|
49 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode;
|
fiore@3
|
50 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
|
fiore@0
|
51 import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent;
|
fiore@3
|
52 import uk.ac.qmul.eecs.ccmi.network.Command;
|
fiore@3
|
53 import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
|
fiore@0
|
54 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
|
fiore@0
|
55
|
fiore@0
|
56 /**
|
fiore@0
|
57 * A panel to draw a graph
|
fiore@0
|
58 */
|
fiore@0
|
59 @SuppressWarnings("serial")
|
fiore@0
|
60 public class GraphPanel extends JPanel{
|
fiore@0
|
61 /**
|
fiore@0
|
62 * Constructs a graph.
|
fiore@0
|
63 * @param aDiagram a diagram to paint in the graph
|
fiore@5
|
64 * @param aToolbar a toolbar containing the node and edges prototypes for creating
|
fiore@0
|
65 * elements in the graph.
|
fiore@0
|
66 */
|
fiore@0
|
67
|
fiore@0
|
68 public GraphPanel(Diagram aDiagram, GraphToolbar aToolbar) {
|
fiore@0
|
69 grid = new Grid();
|
fiore@0
|
70 gridSize = GRID;
|
fiore@0
|
71 grid.setGrid((int) gridSize, (int) gridSize);
|
fiore@0
|
72 zoom = 1;
|
fiore@0
|
73 toolbar = aToolbar;
|
fiore@0
|
74 setBackground(Color.WHITE);
|
fiore@0
|
75 wasMoving = false;
|
fiore@0
|
76 minBounds = null;
|
fiore@0
|
77
|
fiore@0
|
78 this.model = aDiagram.getCollectionModel();
|
fiore@0
|
79 synchronized(model.getMonitor()){
|
fiore@0
|
80 edges = new LinkedList<Edge>(model.getEdges());
|
fiore@0
|
81 nodes = new LinkedList<Node>(model.getNodes());
|
fiore@0
|
82 }
|
fiore@0
|
83 setModelUpdater(aDiagram.getModelUpdater());
|
fiore@0
|
84
|
fiore@0
|
85 selectedElements = new HashSet<DiagramElement>();
|
fiore@0
|
86 moveLockedElements = new HashSet<Object>();
|
fiore@0
|
87
|
fiore@5
|
88 toolbar.setEdgeCreatedListener(new innerEdgeListener());
|
fiore@0
|
89
|
fiore@0
|
90 /* ---- COLLECTION LISTENER ----
|
fiore@0
|
91 * Adding a collection listener. This listener reacts at changes in the model
|
fiore@0
|
92 * by any source, and thus the graph itself. Basically it refreshes the graph
|
fiore@0
|
93 * and paints again all the nodes and edges.
|
fiore@0
|
94 */
|
fiore@0
|
95 model.addCollectionListener(new CollectionListener(){
|
fiore@0
|
96 @Override
|
fiore@0
|
97 public void elementInserted(final CollectionEvent e) {
|
fiore@0
|
98 DiagramElement element = e.getDiagramElement();
|
fiore@0
|
99 if(element instanceof Node)
|
fiore@0
|
100 nodes.add((Node)element);
|
fiore@0
|
101 else
|
fiore@0
|
102 edges.add((Edge)element);
|
fiore@0
|
103 checkBounds(element,false);
|
fiore@3
|
104 if(e.getDiagramElement() instanceof Node && e.getSource().equals(DiagramEventSource.GRPH)){
|
fiore@0
|
105 setElementSelected(e.getDiagramElement());
|
fiore@0
|
106 dragMode = DRAG_NODE;
|
fiore@0
|
107 }
|
fiore@0
|
108 revalidate();
|
fiore@0
|
109 repaint();
|
fiore@0
|
110 }
|
fiore@0
|
111 @Override
|
fiore@0
|
112 public void elementTakenOut(final CollectionEvent e) {
|
fiore@0
|
113 DiagramElement element = e.getDiagramElement();
|
fiore@0
|
114 if(element instanceof Node){
|
fiore@3
|
115 if(nodePopup != null && nodePopup.getElement().equals(element))
|
fiore@0
|
116 nodePopup.setVisible(false);
|
fiore@0
|
117 nodes.remove(element);
|
fiore@0
|
118 }
|
fiore@0
|
119 else{
|
fiore@3
|
120 if(edgePopup != null && edgePopup.getElement().equals(element))
|
fiore@0
|
121 edgePopup.setVisible(false);
|
fiore@0
|
122 edges.remove(element);
|
fiore@0
|
123 }
|
fiore@0
|
124 checkBounds(e.getDiagramElement(),true);
|
fiore@0
|
125 removeElementFromSelection(e.getDiagramElement());
|
fiore@0
|
126 revalidate();
|
fiore@0
|
127 repaint();
|
fiore@0
|
128 }
|
fiore@0
|
129 @Override
|
fiore@0
|
130 public void elementChanged(final ElementChangedEvent e) {
|
fiore@0
|
131 /* we changed the position of an element and might need to update the boundaries */
|
fiore@0
|
132 if(e.getChangeType().equals("stop_move")){
|
fiore@0
|
133 checkBounds(e.getDiagramElement(),false);
|
fiore@0
|
134 }
|
fiore@0
|
135 revalidate();
|
fiore@0
|
136 repaint();
|
fiore@0
|
137 }
|
fiore@0
|
138 });
|
fiore@0
|
139 /* --------------------------------------------------------------------------- */
|
fiore@0
|
140
|
fiore@0
|
141 /* ------------- MOUSE LISTENERS --------------------------------------------
|
fiore@0
|
142 * For pressed and released mouse click and moved mouse
|
fiore@0
|
143 */
|
fiore@0
|
144 addMouseListener(new MouseAdapter(){
|
fiore@0
|
145 @Override
|
fiore@0
|
146 public void mousePressed(MouseEvent event){
|
fiore@0
|
147 requestFocusInWindow();
|
fiore@0
|
148 final Point2D mousePoint = new Point2D.Double(
|
fiore@0
|
149 (event.getX()+minX)/zoom,
|
fiore@0
|
150 (event.getY()+minY)/zoom
|
fiore@0
|
151 );
|
fiore@0
|
152 boolean isCtrl = (event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0;
|
fiore@0
|
153 Node n = Finder.findNode(mousePoint,nodes);
|
fiore@0
|
154 Edge e = Finder.findEdge(mousePoint,edges);
|
fiore@0
|
155
|
fiore@0
|
156 Object tool = toolbar.getSelectedTool();
|
fiore@0
|
157 /* - right click - */
|
fiore@0
|
158 if((event.getModifiers() & InputEvent.BUTTON1_MASK) == 0) {
|
fiore@5
|
159 if(n != null){
|
fiore@5
|
160 CCmIPopupMenu.NodePopupMenu pop = new CCmIPopupMenu.NodePopupMenu(n,GraphPanel.this,modelUpdater,selectedElements);
|
fiore@5
|
161 nodePopup = pop;
|
fiore@5
|
162 pop.show(GraphPanel.this, event.getX(), event.getY());
|
fiore@5
|
163 }else if(e != null){
|
fiore@0
|
164 if( e.contains(mousePoint)){
|
fiore@0
|
165 Node extremityNode = e.getClosestNode(mousePoint,EDGE_END_MIN_CLICK_DIST);
|
fiore@0
|
166 if(extremityNode == null){ // click far from the attached nodes, only prompt with set name item
|
fiore@3
|
167 CCmIPopupMenu.EdgePopupMenu pop = new CCmIPopupMenu.EdgePopupMenu(e,GraphPanel.this,modelUpdater,selectedElements);
|
fiore@0
|
168 edgePopup = pop;
|
fiore@0
|
169 pop.show(GraphPanel.this, event.getX(), event.getY());
|
fiore@0
|
170 }else{ // click near an attached nodes, prompt for name change, set end label and select arrow head
|
fiore@3
|
171 CCmIPopupMenu.EdgePopupMenu pop = new CCmIPopupMenu.EdgePopupMenu(e,extremityNode,GraphPanel.this,modelUpdater,selectedElements);
|
fiore@0
|
172 edgePopup = pop;
|
fiore@0
|
173 pop.show(GraphPanel.this, event.getX(), event.getY());
|
fiore@0
|
174 }
|
fiore@0
|
175 }
|
fiore@5
|
176 }else {
|
fiore@0
|
177 return;
|
fiore@5
|
178 }
|
fiore@0
|
179 }
|
fiore@0
|
180
|
fiore@0
|
181 /* - one click && palette == select - */
|
fiore@0
|
182 else if (tool == null){
|
fiore@0
|
183 if(n != null){ // node selected
|
fiore@3
|
184 if (isCtrl){
|
fiore@0
|
185 addElementToSelection(n,false);
|
fiore@3
|
186 }else{
|
fiore@0
|
187 setElementSelected(n);
|
fiore@3
|
188 }
|
fiore@0
|
189 dragMode = DRAG_NODE;
|
fiore@0
|
190 }else if (e != null){ // edge selected
|
fiore@0
|
191 if (isCtrl){
|
fiore@0
|
192 addElementToSelection(e,false);
|
fiore@0
|
193 dragMode = DRAG_NODE;
|
fiore@0
|
194 }else{
|
fiore@0
|
195 setElementSelected(e);
|
fiore@3
|
196 modelUpdater.startMove(e, mousePoint,DiagramEventSource.GRPH);
|
fiore@0
|
197 dragMode = DRAG_EDGE;
|
fiore@0
|
198 }
|
fiore@0
|
199 }else{ // nothing selected : make selection lasso
|
fiore@0
|
200 if (!isCtrl)
|
fiore@0
|
201 clearSelection();
|
fiore@0
|
202 dragMode = DRAG_LASSO;
|
fiore@0
|
203 }
|
fiore@0
|
204 }
|
fiore@0
|
205 /* - one click && palette == node - */
|
fiore@0
|
206 else {
|
fiore@0
|
207 /* click on an already existing node = select it*/
|
fiore@0
|
208 if (n != null){
|
fiore@0
|
209 if (isCtrl)
|
fiore@0
|
210 addElementToSelection(n,false);
|
fiore@0
|
211 else
|
fiore@0
|
212 setElementSelected(n);
|
fiore@0
|
213 dragMode = DRAG_NODE;
|
fiore@0
|
214 }else{
|
fiore@0
|
215 Node prototype = (Node) tool;
|
fiore@0
|
216 Node newNode = (Node) prototype.clone();
|
fiore@0
|
217 Rectangle2D bounds = newNode.getBounds();
|
fiore@0
|
218 /* perform the translation from the origin */
|
fiore@0
|
219 newNode.translate(new Point2D.Double(), mousePoint.getX() - bounds.getX(),
|
fiore@3
|
220 mousePoint.getY() - bounds.getY(),DiagramEventSource.NONE);
|
fiore@0
|
221 /* log stuff */
|
fiore@0
|
222 iLog("insert node",""+((newNode.getId() == DiagramElement.NO_ID) ? "(no id)" : newNode.getId()));
|
fiore@0
|
223 /* insert the node into the model (no lock needed) */
|
fiore@3
|
224 modelUpdater.insertInCollection(newNode,DiagramEventSource.GRPH);
|
fiore@0
|
225 }
|
fiore@0
|
226 }
|
fiore@0
|
227
|
fiore@0
|
228 lastMousePoint = mousePoint;
|
fiore@0
|
229 mouseDownPoint = mousePoint;
|
fiore@0
|
230 repaint();
|
fiore@0
|
231 }
|
fiore@0
|
232
|
fiore@0
|
233 @Override
|
fiore@0
|
234 public void mouseReleased(MouseEvent event){
|
fiore@0
|
235 final Point2D mousePoint = new Point2D.Double(
|
fiore@0
|
236 (event.getX()+minX)/zoom,
|
fiore@0
|
237 (event.getY()+minY)/zoom
|
fiore@0
|
238 );
|
fiore@0
|
239 if(lastSelected != null){
|
fiore@3
|
240 if(lastSelected instanceof Node || selectedElements.size() > 1){ // differentiate between translate and edge bending
|
fiore@0
|
241 if(wasMoving){
|
fiore@0
|
242 iLog("move selected stop",mousePoint.getX()+" "+ mousePoint.getY());
|
fiore@0
|
243 for(Object element : moveLockedElements){
|
fiore@3
|
244 modelUpdater.stopMove((GraphElement)element,DiagramEventSource.GRPH);
|
fiore@3
|
245 boolean isNode = element instanceof Node;
|
fiore@3
|
246 modelUpdater.yieldLock((DiagramTreeNode)element,
|
fiore@3
|
247 Lock.MOVE,
|
fiore@3
|
248 new DiagramEventActionSource(
|
fiore@3
|
249 DiagramEventSource.GRPH,
|
fiore@3
|
250 isNode ? Command.Name.STOP_NODE_MOVE : Command.Name.STOP_EDGE_MOVE,
|
fiore@3
|
251 ((DiagramElement)element).getId(),((DiagramElement)element).getName()));
|
fiore@0
|
252 }
|
fiore@0
|
253 moveLockedElements.clear();
|
fiore@0
|
254 }
|
fiore@3
|
255 }else{ // instanceof Edge && selectedelements.size() = 1. Bending
|
fiore@0
|
256 if(wasMoving){
|
fiore@0
|
257 iLog("bend edge stop",mousePoint.getX()+" "+ mousePoint.getY());
|
fiore@0
|
258 if(moveLockedEdge != null){
|
fiore@3
|
259 modelUpdater.stopMove(moveLockedEdge,DiagramEventSource.GRPH);
|
fiore@3
|
260 modelUpdater.yieldLock(moveLockedEdge, Lock.MOVE, new DiagramEventActionSource(
|
fiore@3
|
261 DiagramEventSource.GRPH,
|
fiore@3
|
262 Command.Name.BEND,
|
fiore@3
|
263 moveLockedEdge.getId(),
|
fiore@3
|
264 moveLockedEdge.getName()));
|
fiore@0
|
265 moveLockedEdge = null;
|
fiore@0
|
266 }
|
fiore@0
|
267 }
|
fiore@0
|
268 }
|
fiore@0
|
269 }
|
fiore@0
|
270 dragMode = DRAG_NONE;
|
fiore@0
|
271 wasMoving = false;
|
fiore@0
|
272 repaint();
|
fiore@0
|
273 }
|
fiore@0
|
274 });
|
fiore@0
|
275
|
fiore@0
|
276 addMouseMotionListener(new MouseMotionAdapter(){
|
fiore@0
|
277 public void mouseDragged(MouseEvent event){
|
fiore@0
|
278 Point2D mousePoint = new Point2D.Double(
|
fiore@0
|
279 (event.getX()+minX)/zoom,
|
fiore@0
|
280 (event.getY()+minY)/zoom
|
fiore@0
|
281 );
|
fiore@0
|
282 boolean isCtrl = (event.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0;
|
fiore@0
|
283
|
fiore@0
|
284 if (dragMode == DRAG_NODE){
|
fiore@0
|
285 /* translate selected nodes (edges as well) */
|
fiore@0
|
286 double dx = mousePoint.getX() - lastMousePoint.getX();
|
fiore@0
|
287 double dy = mousePoint.getY() - lastMousePoint.getY();
|
fiore@0
|
288 if(!wasMoving){
|
fiore@0
|
289 wasMoving = true;
|
fiore@0
|
290 /* when the motion starts, we need to get the move-lock from the server */
|
fiore@0
|
291 Iterator<DiagramElement> iterator = selectedElements.iterator();
|
fiore@0
|
292 while(iterator.hasNext()){
|
fiore@0
|
293 DiagramElement element = iterator.next();
|
fiore@3
|
294 boolean isNode = element instanceof Node;
|
fiore@3
|
295 if(modelUpdater.getLock(element,
|
fiore@3
|
296 Lock.MOVE,
|
fiore@3
|
297 new DiagramEventActionSource(
|
fiore@3
|
298 DiagramEventSource.GRPH,
|
fiore@3
|
299 isNode ? Command.Name.TRANSLATE_NODE : Command.Name.TRANSLATE_EDGE,
|
fiore@3
|
300 element.getId(),
|
fiore@3
|
301 element.getName()))){
|
fiore@0
|
302 moveLockedElements.add(element);
|
fiore@0
|
303 }else{
|
fiore@0
|
304 iLog("Could not get move lock for element",DiagramElement.toLogString(element));
|
fiore@0
|
305 iterator.remove();
|
fiore@0
|
306 }
|
fiore@0
|
307 }
|
fiore@0
|
308 iLog("move selected start",mousePoint.getX()+" "+ mousePoint.getY());
|
fiore@0
|
309 }
|
fiore@0
|
310
|
fiore@0
|
311 for (DiagramElement selected : selectedElements){
|
fiore@0
|
312 if(selected instanceof Node)
|
fiore@3
|
313 modelUpdater.translate((Node)selected, lastMousePoint, dx, dy,DiagramEventSource.GRPH);
|
fiore@0
|
314 else
|
fiore@3
|
315 modelUpdater.translate((Edge)selected, lastMousePoint, dx, dy,DiagramEventSource.GRPH);
|
fiore@0
|
316 }
|
fiore@0
|
317 } else if(dragMode == DRAG_EDGE){
|
fiore@0
|
318 if(!wasMoving){
|
fiore@0
|
319 wasMoving = true;
|
fiore@3
|
320 if(modelUpdater.getLock(lastSelected,
|
fiore@3
|
321 Lock.MOVE,
|
fiore@3
|
322 new DiagramEventActionSource(
|
fiore@3
|
323 DiagramEventSource.GRPH,
|
fiore@3
|
324 Command.Name.BEND,
|
fiore@3
|
325 lastSelected.getId(),
|
fiore@3
|
326 lastSelected.getName()))
|
fiore@3
|
327 ){
|
fiore@0
|
328 moveLockedEdge = (Edge)lastSelected;
|
fiore@3
|
329 }else{
|
fiore@0
|
330 iLog("Could not get move lock for element",DiagramElement.toLogString(lastSelected));
|
fiore@3
|
331 }
|
fiore@0
|
332 iLog("bend edge start",mousePoint.getX()+" "+ mousePoint.getY());
|
fiore@0
|
333 }
|
fiore@0
|
334 if(moveLockedEdge != null)
|
fiore@3
|
335 modelUpdater.bend(moveLockedEdge, new Point2D.Double(mousePoint.getX(), mousePoint.getY()),DiagramEventSource.GRPH);
|
fiore@0
|
336 } else if (dragMode == DRAG_LASSO){
|
fiore@0
|
337 double x1 = mouseDownPoint.getX();
|
fiore@0
|
338 double y1 = mouseDownPoint.getY();
|
fiore@0
|
339 double x2 = mousePoint.getX();
|
fiore@0
|
340 double y2 = mousePoint.getY();
|
fiore@0
|
341 Rectangle2D.Double lasso = new Rectangle2D.Double(Math.min(x1, x2),
|
fiore@0
|
342 Math.min(y1, y2), Math.abs(x1 - x2), Math.abs(y1 - y2));
|
fiore@0
|
343 for (Node n : GraphPanel.this.nodes){
|
fiore@0
|
344 Rectangle2D bounds = n.getBounds();
|
fiore@0
|
345 if(!isCtrl && !lasso.contains(bounds)){
|
fiore@0
|
346 removeElementFromSelection(n);
|
fiore@0
|
347 }
|
fiore@0
|
348 else if (lasso.contains(bounds)){
|
fiore@0
|
349 addElementToSelection(n,true);
|
fiore@0
|
350 }
|
fiore@0
|
351 }
|
fiore@0
|
352 if(selectedElements.size() != oldLazoSelectedNum){
|
fiore@0
|
353 StringBuilder builder = new StringBuilder();
|
fiore@0
|
354 for(DiagramElement de : selectedElements)
|
fiore@0
|
355 builder.append(DiagramElement.toLogString(de)).append(' ');
|
fiore@0
|
356 iLog("added by lazo",builder.toString());
|
fiore@0
|
357 }
|
fiore@0
|
358 oldLazoSelectedNum = selectedElements.size();
|
fiore@0
|
359 }
|
fiore@0
|
360 lastMousePoint = mousePoint;
|
fiore@0
|
361 }
|
fiore@0
|
362 });
|
fiore@0
|
363 }
|
fiore@0
|
364 /* --------------------------------------------------------------------------- */
|
fiore@0
|
365
|
fiore@0
|
366 @Override
|
fiore@0
|
367 public void paintComponent(Graphics g){
|
fiore@0
|
368 super.paintComponent(g);
|
fiore@0
|
369 paintGraph(g);
|
fiore@0
|
370 }
|
fiore@0
|
371
|
fiore@5
|
372 /**
|
fiore@5
|
373 * Paints the graph on the graphics passed as argument. This function is called
|
fiore@5
|
374 * each time the component is painted.
|
fiore@5
|
375 *
|
fiore@5
|
376 * @see #paintComponent(Graphics)
|
fiore@5
|
377 * @param g the graphics object used to paint this graph
|
fiore@5
|
378 */
|
fiore@0
|
379 public void paintGraph(Graphics g){
|
fiore@0
|
380 Graphics2D g2 = (Graphics2D) g;
|
fiore@0
|
381 g2.translate(-minX, -minY);
|
fiore@0
|
382 g2.scale(zoom, zoom);
|
fiore@0
|
383 Rectangle2D bounds = getBounds();
|
fiore@0
|
384 Rectangle2D graphBounds = getGraphBounds();
|
fiore@0
|
385 if (!hideGrid) grid.draw(g2, new Rectangle2D.Double(minX, minY,
|
fiore@0
|
386 Math.max(bounds.getMaxX() / zoom, graphBounds.getMaxX()),
|
fiore@0
|
387 Math.max(bounds.getMaxY() / zoom, graphBounds.getMaxY())));
|
fiore@0
|
388
|
fiore@0
|
389 /* draw nodes and edges */
|
fiore@0
|
390 for (Edge e : edges)
|
fiore@0
|
391 e.draw(g2);
|
fiore@0
|
392 for (Node n : nodes)
|
fiore@0
|
393 n.draw(g2);
|
fiore@0
|
394
|
fiore@0
|
395 for(DiagramElement selected : selectedElements){
|
fiore@0
|
396 if (selected instanceof Node){
|
fiore@0
|
397 Rectangle2D grabberBounds = ((Node) selected).getBounds();
|
fiore@0
|
398 drawGrabber(g2, grabberBounds.getMinX(), grabberBounds.getMinY());
|
fiore@0
|
399 drawGrabber(g2, grabberBounds.getMinX(), grabberBounds.getMaxY());
|
fiore@0
|
400 drawGrabber(g2, grabberBounds.getMaxX(), grabberBounds.getMinY());
|
fiore@0
|
401 drawGrabber(g2, grabberBounds.getMaxX(), grabberBounds.getMaxY());
|
fiore@0
|
402 }
|
fiore@0
|
403 else if (selected instanceof Edge){
|
fiore@0
|
404 for(Point2D p : ((Edge)selected).getConnectionPoints())
|
fiore@0
|
405 drawGrabber(g2, p.getX(), p.getY());
|
fiore@0
|
406 }
|
fiore@0
|
407 }
|
fiore@0
|
408
|
fiore@0
|
409 if (dragMode == DRAG_LASSO){
|
fiore@0
|
410 Color oldColor = g2.getColor();
|
fiore@0
|
411 g2.setColor(GRABBER_COLOR);
|
fiore@0
|
412 double x1 = mouseDownPoint.getX();
|
fiore@0
|
413 double y1 = mouseDownPoint.getY();
|
fiore@0
|
414 double x2 = lastMousePoint.getX();
|
fiore@0
|
415 double y2 = lastMousePoint.getY();
|
fiore@0
|
416 Rectangle2D.Double lasso = new Rectangle2D.Double(Math.min(x1, x2),
|
fiore@0
|
417 Math.min(y1, y2), Math.abs(x1 - x2) , Math.abs(y1 - y2));
|
fiore@0
|
418 g2.draw(lasso);
|
fiore@0
|
419 g2.setColor(oldColor);
|
fiore@0
|
420 repaint();
|
fiore@0
|
421 }
|
fiore@0
|
422 }
|
fiore@0
|
423
|
fiore@0
|
424 /**
|
fiore@0
|
425 * Draws a single "grabber", a filled square
|
fiore@0
|
426 * @param g2 the graphics context
|
fiore@0
|
427 * @param x the x coordinate of the center of the grabber
|
fiore@0
|
428 * @param y the y coordinate of the center of the grabber
|
fiore@0
|
429 */
|
fiore@0
|
430 static void drawGrabber(Graphics2D g2, double x, double y){
|
fiore@0
|
431 final int SIZE = 5;
|
fiore@0
|
432 Color oldColor = g2.getColor();
|
fiore@0
|
433 g2.setColor(GRABBER_COLOR);
|
fiore@0
|
434 g2.fill(new Rectangle2D.Double(x - SIZE / 2, y - SIZE / 2, SIZE, SIZE));
|
fiore@0
|
435 g2.setColor(oldColor);
|
fiore@0
|
436 }
|
fiore@0
|
437
|
fiore@0
|
438 @Override
|
fiore@0
|
439 public Dimension getPreferredSize(){
|
fiore@0
|
440 Rectangle2D graphBounds = getGraphBounds();
|
fiore@0
|
441 return new Dimension((int) (zoom * graphBounds.getMaxX()),
|
fiore@0
|
442 (int) (zoom * graphBounds.getMaxY()));
|
fiore@0
|
443 }
|
fiore@0
|
444
|
fiore@0
|
445 /**
|
fiore@0
|
446 * Changes the zoom of this panel. The zoom is 1 by default and is multiplied
|
fiore@0
|
447 * by sqrt(2) for each positive stem or divided by sqrt(2) for each negative
|
fiore@0
|
448 * step.
|
fiore@0
|
449 * @param steps the number of steps by which to change the zoom. A positive
|
fiore@0
|
450 * value zooms in, a negative value zooms out.
|
fiore@0
|
451 */
|
fiore@0
|
452 public void changeZoom(int steps){
|
fiore@0
|
453 final double FACTOR = Math.sqrt(2);
|
fiore@0
|
454 for (int i = 1; i <= steps; i++)
|
fiore@0
|
455 zoom *= FACTOR;
|
fiore@0
|
456 for (int i = 1; i <= -steps; i++)
|
fiore@0
|
457 zoom /= FACTOR;
|
fiore@0
|
458 revalidate();
|
fiore@0
|
459 repaint();
|
fiore@0
|
460 }
|
fiore@0
|
461
|
fiore@0
|
462 /**
|
fiore@0
|
463 * Changes the grid size of this panel. The zoom is 10 by default and is
|
fiore@0
|
464 * multiplied by sqrt(2) for each positive stem or divided by sqrt(2) for
|
fiore@0
|
465 * each negative step.
|
fiore@0
|
466 * @param steps the number of steps by which to change the zoom. A positive
|
fiore@0
|
467 * value zooms in, a negative value zooms out.
|
fiore@0
|
468 */
|
fiore@0
|
469 public void changeGridSize(int steps){
|
fiore@0
|
470 final double FACTOR = Math.sqrt(2);
|
fiore@0
|
471 for (int i = 1; i <= steps; i++)
|
fiore@0
|
472 gridSize *= FACTOR;
|
fiore@0
|
473 for (int i = 1; i <= -steps; i++)
|
fiore@0
|
474 gridSize /= FACTOR;
|
fiore@0
|
475 grid.setGrid((int) gridSize, (int) gridSize);
|
fiore@0
|
476 repaint();
|
fiore@0
|
477 }
|
fiore@0
|
478
|
fiore@0
|
479 private void addElementToSelection(DiagramElement element, boolean byLasso){
|
fiore@0
|
480 /* if not added to selected elements by including it in the lasso, the element is moved *
|
fiore@0
|
481 * to the back of the collection so that it will be painted on the top on the next refresh */
|
fiore@0
|
482 if(!byLasso)
|
fiore@0
|
483 if(element instanceof Node){
|
fiore@0
|
484 /* put the node in the last position so that it will be drawn on the top */
|
fiore@0
|
485 nodes.remove(element);
|
fiore@0
|
486 nodes.add((Node)element);
|
fiore@0
|
487 iLog("addeded node to selected",DiagramElement.toLogString(element));
|
fiore@0
|
488 }else{
|
fiore@0
|
489 /* put the edge in the last position so that it will be drawn on the top */
|
fiore@0
|
490 edges.remove(element);
|
fiore@0
|
491 edges.add((Edge)element);
|
fiore@0
|
492 iLog("addeded edge to selected",DiagramElement.toLogString(element));
|
fiore@0
|
493 }
|
fiore@0
|
494 if(selectedElements.contains(element)){
|
fiore@0
|
495 lastSelected = element;
|
fiore@0
|
496 return;
|
fiore@0
|
497 }
|
fiore@0
|
498 lastSelected = element;
|
fiore@0
|
499 selectedElements.add(element);
|
fiore@0
|
500 return;
|
fiore@0
|
501 }
|
fiore@0
|
502
|
fiore@0
|
503 private void removeElementFromSelection(DiagramElement element){
|
fiore@0
|
504 if (element == lastSelected){
|
fiore@0
|
505 lastSelected = null;
|
fiore@0
|
506 }
|
fiore@0
|
507 if(selectedElements.contains(element)){
|
fiore@0
|
508 selectedElements.remove(element);
|
fiore@0
|
509 }
|
fiore@0
|
510 }
|
fiore@0
|
511
|
fiore@0
|
512 private void setElementSelected(DiagramElement element){
|
fiore@0
|
513 /* clear the selection */
|
fiore@0
|
514 selectedElements.clear();
|
fiore@0
|
515 lastSelected = element;
|
fiore@0
|
516 selectedElements.add(element);
|
fiore@0
|
517 if(element instanceof Node){
|
fiore@0
|
518 nodes.remove(element);
|
fiore@0
|
519 nodes.add((Node)element);
|
fiore@0
|
520 iLog("node selected",DiagramElement.toLogString(element));
|
fiore@0
|
521 }else{
|
fiore@0
|
522 edges.remove(element);
|
fiore@0
|
523 edges.add((Edge)element);
|
fiore@0
|
524 iLog("edge selected",DiagramElement.toLogString(element));
|
fiore@0
|
525 }
|
fiore@0
|
526 }
|
fiore@0
|
527
|
fiore@0
|
528 private void clearSelection(){
|
fiore@0
|
529 iLog("selection cleared","");
|
fiore@0
|
530 selectedElements.clear();
|
fiore@0
|
531 lastSelected = null;
|
fiore@0
|
532 }
|
fiore@0
|
533
|
fiore@0
|
534 /**
|
fiore@0
|
535 * Sets the value of the hideGrid property
|
fiore@0
|
536 * @param newValue true if the grid is being hidden
|
fiore@0
|
537 */
|
fiore@0
|
538 public void setHideGrid(boolean newValue){
|
fiore@0
|
539 hideGrid = newValue;
|
fiore@0
|
540 repaint();
|
fiore@0
|
541 }
|
fiore@0
|
542
|
fiore@0
|
543 /**
|
fiore@0
|
544 * Gets the value of the hideGrid property
|
fiore@0
|
545 * @return true if the grid is being hidden
|
fiore@0
|
546 */
|
fiore@0
|
547 public boolean getHideGrid(){
|
fiore@0
|
548 return hideGrid;
|
fiore@0
|
549 }
|
fiore@0
|
550
|
fiore@0
|
551 /**
|
fiore@5
|
552 * Gets the smallest rectangle enclosing the graph
|
fiore@5
|
553 * @return the bounding rectangle
|
fiore@0
|
554 */
|
fiore@0
|
555 public Rectangle2D getMinBounds() { return minBounds; }
|
fiore@5
|
556
|
fiore@5
|
557 /**
|
fiore@5
|
558 * Sets the smallest rectangle enclosing the graph
|
fiore@5
|
559 * @param newValue the bounding rectangle
|
fiore@5
|
560 */
|
fiore@0
|
561 public void setMinBounds(Rectangle2D newValue) { minBounds = newValue; }
|
fiore@0
|
562
|
fiore@5
|
563 /**
|
fiore@5
|
564 * Returns the smallest rectangle enclosing the graph, that is all the nodes
|
fiore@5
|
565 * and all the edges in the diagram.
|
fiore@5
|
566 *
|
fiore@5
|
567 * @return the bounding rectangle
|
fiore@5
|
568 */
|
fiore@0
|
569 public Rectangle2D getGraphBounds(){
|
fiore@0
|
570 Rectangle2D r = minBounds;
|
fiore@0
|
571 for (Node n : nodes){
|
fiore@0
|
572 Rectangle2D b = n.getBounds();
|
fiore@0
|
573 if (r == null) r = b;
|
fiore@0
|
574 else r.add(b);
|
fiore@0
|
575 }
|
fiore@0
|
576 for (Edge e : edges){
|
fiore@0
|
577 r.add(e.getBounds());
|
fiore@0
|
578 }
|
fiore@0
|
579 return r == null ? new Rectangle2D.Double() : new Rectangle2D.Double(r.getX(), r.getY(),
|
fiore@0
|
580 r.getWidth() + Node.SHADOW_GAP + Math.abs(minX), r.getHeight() + Node.SHADOW_GAP + Math.abs(minY));
|
fiore@0
|
581 }
|
fiore@0
|
582
|
fiore@5
|
583 /**
|
fiore@5
|
584 * Sets the model updater for this graph. The model updater is used
|
fiore@5
|
585 * to make changes to the diagram (e.g. adding, removing, renaming nodes and edges)
|
fiore@5
|
586 *
|
fiore@5
|
587 * @param modelUpdater the model updater for this graph panel
|
fiore@5
|
588 */
|
fiore@0
|
589 public void setModelUpdater(DiagramModelUpdater modelUpdater){
|
fiore@0
|
590 this.modelUpdater = modelUpdater;
|
fiore@0
|
591 }
|
fiore@0
|
592
|
fiore@0
|
593 private void iLog(String action,String args){
|
fiore@0
|
594 InteractionLog.log("GRAPH",action,args);
|
fiore@0
|
595 }
|
fiore@0
|
596
|
fiore@0
|
597 private void checkBounds(DiagramElement de, boolean wasRemoved){
|
fiore@0
|
598 GraphElement ge;
|
fiore@0
|
599 if(de instanceof Node)
|
fiore@0
|
600 ge = (Node)de;
|
fiore@0
|
601 else
|
fiore@0
|
602 ge = (Edge)de;
|
fiore@0
|
603 if(wasRemoved){
|
fiore@0
|
604 if(ge == top){
|
fiore@0
|
605 top = null;
|
fiore@0
|
606 minY = 0;
|
fiore@0
|
607 Rectangle2D bounds;
|
fiore@0
|
608 for(Edge e : edges){
|
fiore@0
|
609 bounds = e.getBounds();
|
fiore@0
|
610 if(bounds.getY() < minY){
|
fiore@0
|
611 top = e;
|
fiore@0
|
612 minY = bounds.getY();
|
fiore@0
|
613 }
|
fiore@0
|
614 }
|
fiore@0
|
615 for(Node n : nodes){
|
fiore@0
|
616 bounds = n.getBounds();
|
fiore@0
|
617 if(bounds.getY() < minY){
|
fiore@0
|
618 top = n;
|
fiore@0
|
619 minY = bounds.getY();
|
fiore@0
|
620 }
|
fiore@0
|
621 }
|
fiore@0
|
622 }
|
fiore@0
|
623 if(ge == left){
|
fiore@0
|
624 minX = 0;
|
fiore@0
|
625 left = null;
|
fiore@0
|
626 synchronized(model.getMonitor()){
|
fiore@0
|
627 Rectangle2D bounds;
|
fiore@0
|
628 for(Edge e : model.getEdges()){
|
fiore@0
|
629 bounds = e.getBounds();
|
fiore@0
|
630 if(bounds.getX() < minX){
|
fiore@0
|
631 left = e;
|
fiore@0
|
632 minX = bounds.getX();
|
fiore@0
|
633 }
|
fiore@0
|
634 }
|
fiore@0
|
635 for(Node n : model.getNodes()){
|
fiore@0
|
636 bounds = n.getBounds();
|
fiore@0
|
637 if(bounds.getX() < minX){
|
fiore@0
|
638 left = n;
|
fiore@0
|
639 minX = bounds.getX();
|
fiore@0
|
640 }
|
fiore@0
|
641 }
|
fiore@0
|
642 }
|
fiore@0
|
643 }
|
fiore@0
|
644 }else{ // was added or translated
|
fiore@0
|
645 Rectangle2D bounds = ge.getBounds();
|
fiore@0
|
646 if(top == null){
|
fiore@0
|
647 if(bounds.getY() < 0){
|
fiore@0
|
648 top = ge;
|
fiore@0
|
649 minY = bounds.getY();
|
fiore@0
|
650 }
|
fiore@0
|
651 }else if(ge == top){ //the top-most has been translated recalculate the new top-most, as itf it were deleted
|
fiore@0
|
652 checkBounds(de, true);
|
fiore@0
|
653 }else if(bounds.getY() < top.getBounds().getY()){
|
fiore@0
|
654 top = ge;
|
fiore@0
|
655 minY = bounds.getY();
|
fiore@0
|
656 }
|
fiore@0
|
657
|
fiore@0
|
658 if(left == null){
|
fiore@0
|
659 if(bounds.getX() < 0){
|
fiore@0
|
660 left = ge;
|
fiore@0
|
661 minX = bounds.getX();
|
fiore@0
|
662 }
|
fiore@0
|
663 }else if(ge == left){
|
fiore@0
|
664 checkBounds(de,true);//the left-most has been translated recalculate the new left-most, as if it were deleted
|
fiore@0
|
665 }
|
fiore@0
|
666 else if(bounds.getX() < left.getBounds().getX()){
|
fiore@0
|
667 left = ge;
|
fiore@0
|
668 minX = bounds.getX();
|
fiore@0
|
669 }
|
fiore@0
|
670 }
|
fiore@0
|
671 }
|
fiore@0
|
672
|
fiore@0
|
673 private class innerEdgeListener implements GraphToolbar.EdgeCreatedListener {
|
fiore@0
|
674 @Override
|
fiore@0
|
675 public void edgeCreated(Edge e) {
|
fiore@0
|
676 ArrayList<DiagramNode> nodesToConnect = new ArrayList<DiagramNode>(selectedElements.size());
|
fiore@6
|
677
|
fiore@0
|
678 for(DiagramElement element : selectedElements){
|
fiore@6
|
679 if(element instanceof Node){
|
fiore@6
|
680 if(!modelUpdater.getLock(element,
|
fiore@6
|
681 Lock.MUST_EXIST,
|
fiore@6
|
682 new DiagramEventActionSource(DiagramEventSource.GRPH,
|
fiore@6
|
683 Command.Name.SELECT_NODE_FOR_EDGE_CREATION,
|
fiore@6
|
684 element.getId(),
|
fiore@6
|
685 element.getName()))){
|
fiore@6
|
686 /* unlock the nodes locked so far */
|
fiore@6
|
687 yieldLocks(nodesToConnect);
|
fiore@6
|
688 /* notify user */
|
fiore@6
|
689 JOptionPane.showMessageDialog(GraphPanel.this,
|
fiore@6
|
690 ResourceBundle.getBundle(EditorFrame.class.getName()).getString("dialog.lock_failure.no_edge_creation"));
|
fiore@6
|
691 return;
|
fiore@6
|
692 }
|
fiore@0
|
693 nodesToConnect.add((Node)element);
|
fiore@6
|
694 }
|
fiore@0
|
695 }
|
fiore@0
|
696 try {
|
fiore@0
|
697 e.connect(nodesToConnect);
|
fiore@3
|
698 modelUpdater.insertInCollection(e,DiagramEventSource.GRPH);
|
fiore@6
|
699 /* release the must-exist lock on nodes now that the edge is created */
|
fiore@6
|
700 yieldLocks(nodesToConnect);
|
fiore@0
|
701 } catch (ConnectNodesException cnEx) {
|
fiore@0
|
702 JOptionPane.showMessageDialog(GraphPanel.this,
|
fiore@0
|
703 cnEx.getLocalizedMessage(),
|
fiore@0
|
704 ResourceBundle.getBundle(EditorFrame.class.getName()).getString("dialog.error.title"),
|
fiore@0
|
705 JOptionPane.ERROR_MESSAGE);
|
fiore@0
|
706 iLog("insert edge error",cnEx.getMessage());
|
fiore@0
|
707 }
|
fiore@0
|
708 }
|
fiore@6
|
709
|
fiore@6
|
710 /* release all locks */
|
fiore@6
|
711 private void yieldLocks(ArrayList<DiagramNode> nodesToConnect){
|
fiore@6
|
712 for(DiagramNode node : nodesToConnect){
|
fiore@6
|
713 modelUpdater.yieldLock(node,
|
fiore@6
|
714 Lock.MUST_EXIST,
|
fiore@6
|
715 new DiagramEventActionSource(
|
fiore@6
|
716 DiagramEventSource.GRPH,
|
fiore@6
|
717 Command.Name.UNSELECT_NODE_FOR_EDGE_CREATION,
|
fiore@6
|
718 node.getId(),
|
fiore@6
|
719 node.getName())
|
fiore@6
|
720 );
|
fiore@6
|
721 }
|
fiore@6
|
722 }
|
fiore@0
|
723 }
|
fiore@0
|
724
|
fiore@0
|
725 private List<Edge> edges;
|
fiore@0
|
726 private List<Node> nodes;
|
fiore@0
|
727 private DiagramModelUpdater modelUpdater;
|
fiore@0
|
728 private CollectionModel<Node,Edge> model;
|
fiore@0
|
729
|
fiore@0
|
730 private Grid grid;
|
fiore@0
|
731 private GraphToolbar toolbar;
|
fiore@3
|
732 private CCmIPopupMenu.NodePopupMenu nodePopup;
|
fiore@3
|
733 private CCmIPopupMenu.EdgePopupMenu edgePopup;
|
fiore@0
|
734
|
fiore@0
|
735 private double zoom;
|
fiore@0
|
736 private double gridSize;
|
fiore@0
|
737 private boolean hideGrid;
|
fiore@0
|
738 private boolean wasMoving;
|
fiore@0
|
739
|
fiore@0
|
740 private GraphElement top;
|
fiore@0
|
741 private GraphElement left;
|
fiore@0
|
742 private double minX;
|
fiore@0
|
743 private double minY;
|
fiore@0
|
744
|
fiore@0
|
745 private DiagramElement lastSelected;
|
fiore@0
|
746 private Edge moveLockedEdge;
|
fiore@0
|
747 private Set<DiagramElement> selectedElements;
|
fiore@0
|
748 private Set<Object> moveLockedElements;
|
fiore@0
|
749
|
fiore@0
|
750 private Point2D lastMousePoint;
|
fiore@0
|
751 private Point2D mouseDownPoint;
|
fiore@0
|
752 private Rectangle2D minBounds;
|
fiore@0
|
753 private int dragMode;
|
fiore@0
|
754
|
fiore@0
|
755 private int oldLazoSelectedNum;
|
fiore@0
|
756
|
fiore@0
|
757 /* button is not down, mouse motion will habe no effects */
|
fiore@0
|
758 private static final int DRAG_NONE = 0;
|
fiore@0
|
759 /* one or more nodes (and eventually some edges) have been selected, mouse motion will result in a translation */
|
fiore@0
|
760 private static final int DRAG_NODE = 1;
|
fiore@0
|
761 /* one edge has been selected, mouse motion will result in an edge bending */
|
fiore@0
|
762 private static final int DRAG_EDGE = 2;
|
fiore@0
|
763 /* mouse button down but nothing selected, mouse motion will result in a lasso */
|
fiore@0
|
764 private static final int DRAG_LASSO = 3; // multiple selection
|
fiore@0
|
765
|
fiore@0
|
766 private static final int GRID = 10;
|
fiore@0
|
767 private static final double EDGE_END_MIN_CLICK_DIST = 10;
|
fiore@0
|
768
|
fiore@0
|
769 public static final Color GRABBER_COLOR = new Color(0,128,255);
|
fiore@0
|
770 } |