1
2
3
4
5
6 package net.kwfgrid.gwui;
7
8 import org.glassbox.gui.*;
9 import org.glassbox.graphview.GraphGroup;
10 import org.glassbox.Theme;
11 import org.glassbox.SwingFactory;
12 import org.glassbox.SwingThread;
13
14 import de.fzi.wim.guibase.graphview.lens.*;
15 import de.fzi.wim.guibase.graphview.view.*;
16 import de.fzi.wim.guibase.graphview.graph.*;
17
18 import javax.swing.*;
19 import javax.swing.event.ChangeListener;
20 import javax.swing.event.ChangeEvent;
21 import java.awt.*;
22 import java.awt.event.*;
23 import java.awt.event.ActionEvent;
24 import java.awt.geom.Point2D;
25 import java.beans.PropertyChangeListener;
26 import java.beans.PropertyChangeEvent;
27 import java.util.*;
28
29 import org.apache.log4j.Logger;
30
31 /***
32 A member to manage the lenses of a <code>JGraphPane</code>. Must be member of a <code>GraphGroup</code>.
33 Offers zooming and background dragging.
34 */
35 public class LensManager extends AbstractGroup implements VisibleMember, ChangeListener, ZoomGroup {
36 public static final String IDENTIFIER = "net.kwfgrid.gwui.LensManager";
37
38 private static final String BUTTON_THEME_PREFIX = "kwfgrid.Toolbar.button";
39 private static final String PANEL_THEME_PREFIX = "kwfgrid.Toolbar";
40 private static final String SLIDER_THEME_PREFIX = "kwfgrid.Toolbar.button";
41 private static final String ZOOM_IN_ICON_KEY = "kwfgrid.LensManager.zoom-in.icon";
42 private static final String ZOOM_OUT_ICON_KEY = "kwfgrid.LensManager.zoom-out.icon";
43 private static final String ZOOM_FULL_ICON_KEY = "kwfgrid.LensManager.zoom-full.icon";
44 private static final Icon ZOOM_IN_ICON = Theme.getIcon(ZOOM_IN_ICON_KEY);
45 private static final Icon ZOOM_OUT_ICON = Theme.getIcon(ZOOM_OUT_ICON_KEY);
46 private static final Icon ZOOM_FULL_ICON = Theme.getIcon(ZOOM_FULL_ICON_KEY);
47 private static final String LAYOUT_GAP_KEY = "kwfgrid.Toolbar.layout-gap.size";
48 private static final Dimension LAYOUT_GAP = Theme.getSize(LAYOUT_GAP_KEY);
49 private static final String GRAPH_BORDER_KEY = "kwfgrid.LensManager.graph-border.size";
50 private static final Dimension GRAPH_BORDER = Theme.getSize(GRAPH_BORDER_KEY);
51 private static final String DRAG_CURSOR_KEY = "kwfgrid.LensManager.drag.cursor";
52 private static final Cursor DRAG_CURSOR = Theme.getCursor(DRAG_CURSOR_KEY);
53
54 private static Logger logger = Logger.getLogger(LensManager.class);
55
56 private Manipulator _dragger = new AbstractManipulator() {
57 private Point _grabpoint = null;
58 private Cursor _cursor = null;
59
60 public String getName() {
61 return "net.kwfgrid.gwui.LensManager._dragger";
62 }
63
64 public boolean isDragging() {
65 return _grabpoint!=null;
66 }
67
68 public void mousePressed(MouseEvent e) {
69 Point point=e.getPoint();
70 if (m_graphPane.getNodeAtPoint(point)==null && m_graphPane.getNearestEdge(point)==null) {
71 _grabpoint=point;
72 _cursor = m_graphPane.getCursor();
73 m_graphPane.setCursor(DRAG_CURSOR);
74 e.consume();
75 }
76 }
77
78 public void mouseReleased(MouseEvent e) {
79 if (isDragging()) {
80 performDrag(e.getPoint());
81 m_graphPane.setCursor(_cursor);
82 _cursor = null;
83 _grabpoint = null;
84 e.consume();
85 }
86 }
87
88 public void mouseDragged(MouseEvent e) {
89 if (isDragging()) {
90 performDrag(e.getPoint());
91 e.consume();
92 }
93 }
94
95 protected void performDrag(Point position) {
96 _p3.setLocation( _grabpoint.x-position.x, _grabpoint.y-position.y);
97 _zoomlens.undoLens(_p3);
98
99 _p3.setLocation(_translatelens.getTranslateX() - _p3.getX(),
100 _translatelens.getTranslateY() - _p3.getY());
101
102 correctTranslate(_p3);
103
104 _translatelens.setTranslate(_p3.getX(), _p3.getY());
105
106 _grabpoint = position;
107 }
108 };
109
110 private class ZoomFullAction extends AbstractAction {
111 public ZoomFullAction(Icon icon) {
112 super(null, icon);
113 }
114
115 public void actionPerformed(ActionEvent e) {
116 focusWholeGraph();
117 }
118 }
119
120 private class ZoomInAction extends AbstractAction {
121 public ZoomInAction(Icon icon) {
122 super(null, icon);
123 }
124
125 public void actionPerformed(ActionEvent e) {
126 setZoomFactor(_zoomlens.getZoomFactor()+0.05d);
127 }
128 }
129
130 private class ZoomOutAction extends AbstractAction {
131 public ZoomOutAction(Icon icon) {
132 super(null, icon);
133 }
134
135 public void actionPerformed(ActionEvent e) {
136 setZoomFactor(_zoomlens.getZoomFactor()-0.05d);
137 }
138 }
139
140 private JGraphPane _graphpane;
141 private JPanel _view;
142 private JSlider _slider;
143 private JButton _zoominbutton;
144 private JButton _zoomoutbutton;
145 private JButton _zoomfullbutton;
146 private ZoomLens _zoomlens;
147 private TranslateLens _translatelens;
148 private LensSet _lensset;
149 private Rectangle _r1;
150 private Rectangle _r2;
151 private Point2D.Double _p1, _p2, _p3;
152 private Group _group;
153 private boolean _internalsliderchange;
154
155 public LensManager() {
156 super();
157 _r1 = new Rectangle();
158 _r2 = new Rectangle();
159 _p1 = new Point2D.Double();
160 _p2 = new Point2D.Double();
161 _p3 = new Point2D.Double();
162 _view = null;
163 _group = null;
164 _zoomlens = new ZoomLens();
165 _translatelens = new TranslateLens();
166 _lensset = new LensSet();
167 _lensset.addLens(_translatelens);
168 _lensset.addLens(_zoomlens);
169 _internalsliderchange = false;
170 setProperty(ZOOM_FACTOR_KEY, new Double(1d));
171 }
172
173 public LensManager(JGraphPane graphpane) {
174 this();
175 setGraphPane(graphpane);
176 }
177
178 public void setGraphPane(JGraphPane graphpane) {
179 _graphpane = graphpane;
180 _graphpane.setLens(_lensset);
181 _graphpane.addManipulator(_dragger);
182 }
183
184
185
186
187
188 protected void calculateGraphBoundingBoxOnScreen(Rectangle target) {
189 Point2D b = new Point2D.Double();
190 b.setLocation(GRAPH_BORDER.width, GRAPH_BORDER.height);
191
192 logger.debug("GraphBorder: "+b);
193
194 _zoomlens.applyLens(b);
195
196 logger.debug("GraphBorder after zoom: "+b);
197
198 double bw = b.getX();
199 double bh = b.getY();
200
201 if (_graphpane!=null && _graphpane.getGraph()!=null) {
202 boolean first = true;
203 Rectangle box = new Rectangle();
204
205 Iterator nodes = _graphpane.getGraph().getNodes().iterator(); while (nodes.hasNext()) {
206 Node node = (Node)nodes.next();
207 _graphpane.getNodeScreenBounds(node, box);
208
209 logger.debug("Adding node screenbounds for node "+node+": "+box);
210
211 if (first) {
212 first = false;
213 target.setBounds(box.x, box.y, box.width, box.height);
214 } else {
215 target.add(box);
216 }
217 }
218
219 Iterator edges = _graphpane.getGraph().getEdges().iterator(); while (edges.hasNext()) {
220 Edge edge = (Edge)edges.next();
221 _graphpane.getEdgeScreenBounds(edge, box);
222
223 logger.debug("Adding edge screenbounds for edge "+edge+": "+box);
224
225 target.add(box);
226 }
227 } else {
228 target.setSize(0, 0);
229 target.setLocation(0, 0);
230 }
231
232 logger.debug("Graph bounding box on screen without border: "+target);
233
234 target.grow((int)bw, (int)bh);
235
236 logger.debug("Calculated graph bounding box on screen: "+target);
237 }
238
239 protected void calculateGraphBoundingBox(Rectangle target) {
240 if (_graphpane!=null && _graphpane.getGraph()!=null) {
241 boolean first = true;
242 Rectangle nodebox = new Rectangle();
243
244 nodebox.setSize(100, 100);
245
246 Iterator nodes = _graphpane.getGraph().getNodes().iterator(); while (nodes.hasNext()) {
247 Node node = (Node)nodes.next();
248 nodebox.setLocation((int)node.getX() - nodebox.width / 2, (int)node.getY() - nodebox.height / 2);
249
250 logger.debug("Adding bounds for node "+node+": "+node.getX()+","+node.getY());
251
252 if (first) {
253 first = false;
254 target.setBounds(nodebox);
255 } else {
256 target.add(nodebox);
257 }
258 }
259 } else {
260 target.setSize(0, 0);
261 target.setLocation(0, 0);
262 }
263
264 target.grow(GRAPH_BORDER.width, GRAPH_BORDER.height);
265
266 logger.debug("Calculated graph bounding box: "+target);
267 }
268
269 /***
270 Correct a calculated translation so that the graph is always in the view.
271 <code>translate</code> is assumed before _zoomlens.applyLens() and is returned after _zoomlens.undoLens() so that
272 it can be directly set to _translatelens.
273 Uses _r1, _p1, _p2.
274 */
275 protected void correctTranslate(Point2D translate) {
276 _zoomlens.applyLens(translate);
277
278 calculateGraphBoundingBox(_r1);
279 _p1.setLocation(_r1.getX(), _r1.getY());
280 _p2.setLocation(_r1.getWidth(), _r1.getHeight());
281 _zoomlens.applyLens(_p1);
282 _zoomlens.applyLens(_p2);
283
284 if (_p2.getX() <= _graphpane.getWidth()) {
285 translate.setLocation(- _p1.getX() - _graphpane.getWidth() / 2 + (_graphpane.getWidth() - _p2.getX()) / 2,
286 translate.getY());
287 } else {
288 if (translate.getX() > - _p1.getX() - _graphpane.getWidth() / 2) {
289 translate.setLocation(-_p1.getX() - _graphpane.getWidth() / 2,
290 translate.getY());
291 } else if (translate.getX() < - _p1.getX() - _p2.getX() + _graphpane.getWidth() / 2) {
292 translate.setLocation(- _p1.getX() - _p2.getX() + _graphpane.getWidth() / 2,
293 translate.getY());
294 }
295 }
296
297 if (_p2.getY() <= _graphpane.getHeight()) {
298 translate.setLocation(translate.getX(),
299 - _p1.getY() - _graphpane.getHeight() / 2 + (_graphpane.getHeight() - _p2.getY()) / 2);
300 } else {
301 if (translate.getY() > -_p1.getY() - _graphpane.getHeight() / 2) {
302 translate.setLocation(translate.getX(),
303 -_p1.getY() - _graphpane.getHeight() / 2);
304 } else if (translate.getY() < _graphpane.getHeight() / 2 - _p1.getY() - _p2.getY()) {
305 translate.setLocation(translate.getX(),
306 _graphpane.getHeight() / 2 - _p1.getY() - _p2.getY());
307 }
308 }
309
310 _zoomlens.undoLens(translate);
311 }
312
313 protected void updateState() {
314 if (getGroup()!=null && _view!=null) {
315 Object modalmember = getGroup().getProperty(VisibleGroup.MODAL_MEMBER_KEY);
316 Object status = getGroup().getProperty(WorkflowGroup.WORKFLOW_STATUS_KEY);
317 Object appstatus = getGroup().getProperty(Group.APPLICATION_STATUS_KEY);
318
319 if (status==null) status = WorkflowGroup.STATUS_UNDEFINED;
320
321 if (Group.EXITING.equals(appstatus) ||
322 WorkflowGroup.STATUS_UNDEFINED.equals(status)) {
323 _view.setVisible(false);
324 } else {
325 _view.setVisible(true);
326 if (modalmember!=null) {
327 _slider.setEnabled(false);
328 _slider.setToolTipText(null);
329 } else {
330 _slider.setEnabled(true);
331 _slider.setToolTipText("Adjust Zoom Factor");
332 }
333 }
334 }
335 }
336
337
338
339
340
341 /***
342 Focus 0,0.
343 */
344 public void focusNull() {
345 _translatelens.setTranslate(0d, 0d);
346 }
347
348 /***
349 Focus the specified node by setting the translate lens to the according values.
350 Not implemented yet.
351 */
352 public void focusNode(Node node) {
353
354 }
355
356 /***
357 Change zoom factor.
358 */
359 public void setZoomFactor(double zoomfactor) {
360 if (zoomfactor > 1d) zoomfactor = 1d;
361 else if (zoomfactor < 0d) zoomfactor = 0d;
362 _slider.setValue((int)(zoomfactor * 80d));
363 }
364
365 /***
366 Fit the whole graph into the visible area.
367 */
368 public void focusWholeGraph() {
369 logger.debug("focusWholeGraph()");
370
371 calculateGraphBoundingBox(_r1);
372
373 _r2.setLocation(0, 0);
374 _r2.setSize(_graphpane.getWidth(), _graphpane.getHeight());
375
376 logger.debug("Viewport is "+_r2);
377
378 double zf = Math.min(.6d, Math.min((double)_r2.getHeight() / (double)_r1.getHeight(),
379 (double)_r2.getWidth() / (double)_r1.getWidth()));
380
381 logger.debug("Calculated zoom-factor: "+zf);
382
383 setProperty(ZOOM_FACTOR_KEY, new Double(zf));
384 _zoomlens.setZoomFactor(zf);
385
386 Point2D smallgbb_location = new Point2D.Double();
387 smallgbb_location.setLocation(_r1.x, _r1.y);
388 _zoomlens.applyLens(smallgbb_location);
389 Point2D smallgbb_size = new Point2D.Double();
390 smallgbb_size.setLocation(_r1.width, _r1.height);
391 _zoomlens.applyLens(smallgbb_size);
392 double scrwidth = (double)_r2.width;
393 double scrheight = (double)_r2.height;
394
395 Point2D d = new Point2D.Double();
396 d.setLocation(- smallgbb_location.getX() - scrwidth / 2 + (scrwidth - smallgbb_size.getX()) / 2,
397 - smallgbb_location.getY() - scrheight / 2 + (scrheight - smallgbb_size.getY()) /2);
398 _zoomlens.undoLens(d);
399
400 logger.debug("Calculated translate: "+d);
401
402 _translatelens.setTranslate(d.getX(), d.getY());
403
404 logger.debug("Setting slider value to "+((int)(zf * 80d)));
405
406 _internalsliderchange = true;
407 setZoomFactor(zf);
408 _internalsliderchange = false;
409
410 logger.debug("focusWholeGraph() exited");
411 }
412
413 /***
414 Focus the graph center by setting the translate lens to the according values.
415 Not implemented yet.
416 */
417 public void focusGraphCenter() {
418
419 }
420
421
422
423
424
425 public void stateChanged(ChangeEvent e) {
426 if (!_internalsliderchange) {
427 double zf = ((double)_slider.getValue())/80d;
428 setProperty(ZOOM_FACTOR_KEY, new Double(zf));
429 _zoomlens.setZoomFactor(zf);
430
431 _p3.setLocation(_translatelens.getTranslateX(),
432 _translatelens.getTranslateY());
433
434 correctTranslate(_p3);
435
436 _translatelens.setTranslate(_p3.getX(), _p3.getY());
437 }
438 }
439
440
441
442
443
444 public Group getGroup() {
445 return _group;
446 }
447
448 public void setGroup(Group group) {
449 _group = group;
450 }
451
452 public String getIdentifier() {
453 return IDENTIFIER;
454 }
455
456 public void groupPropertyChanged(String name, Object oldvalue, Object newvalue) {
457 logger.debug("groupPropertyChanged("+name+", "+oldvalue+", "+newvalue+")");
458
459 if (name.equals(Group.APPLICATION_STATUS_KEY)) {
460 updateState();
461 } else if (name.equals(VisibleGroup.MODAL_MEMBER_KEY)) {
462 updateState();
463 } else if (name.equals(WorkflowGroup.WORKFLOW_STATUS_KEY)) {
464 updateState();
465 }
466
467 logger.debug("groupPropertyChanged.exit");
468 }
469
470 public JComponent getView() {
471 if (_view==null) {
472 _slider = SwingFactory.createSlider(SLIDER_THEME_PREFIX);
473 _slider.setOrientation(SwingConstants.HORIZONTAL);
474 _slider.setMaximum(80);
475 _slider.setMinimum(0);
476 _slider.setMinorTickSpacing(1);
477 _slider.setPaintLabels(false);
478 _slider.setPaintTicks(false);
479 _slider.setPaintTrack(true);
480 _slider.setSnapToTicks(true);
481 _slider.setValue(40);
482 _slider.addChangeListener(this);
483
484 _zoomfullbutton = SwingFactory.createButton(BUTTON_THEME_PREFIX);
485 _zoomfullbutton.setAction(new ZoomFullAction(ZOOM_FULL_ICON));
486
487 _zoominbutton = SwingFactory.createButton(BUTTON_THEME_PREFIX);
488 _zoominbutton.setAction(new ZoomInAction(ZOOM_IN_ICON));
489
490 _zoomoutbutton = SwingFactory.createButton(BUTTON_THEME_PREFIX);
491 _zoomoutbutton.setAction(new ZoomOutAction(ZOOM_OUT_ICON));
492
493 _view = SwingFactory.createPanel(PANEL_THEME_PREFIX);
494 _view.setLayout(new FlowLayout(FlowLayout.LEFT, LAYOUT_GAP.width, LAYOUT_GAP.height));
495 _view.add(_zoomoutbutton);
496 _view.add(_zoomfullbutton);
497 _view.add(_zoominbutton);
498 _view.add(_slider);
499 _view.setSize(_view.getLayout().preferredLayoutSize(_view));
500 _view.validate();
501 updateState();
502 }
503 return _view;
504 }
505 }