View Javadoc
1   /*
2    * Copyright (c) 2003, The JUNG Authors
3    * All rights reserved.
4    * 
5    * This software is open-source under the BSD license; see either "license.txt"
6    * or https://github.com/jrtom/jung/blob/master/LICENSE for a description.
7    * Created on Feb 2, 2005
8    *
9    */
10  package edu.uci.ics.jung.visualization;
11  
12  import java.awt.BorderLayout;
13  import java.awt.Dimension;
14  import java.awt.Rectangle;
15  import java.awt.event.AdjustmentEvent;
16  import java.awt.event.AdjustmentListener;
17  import java.awt.event.ComponentAdapter;
18  import java.awt.event.ComponentEvent;
19  import java.awt.geom.AffineTransform;
20  import java.awt.geom.Line2D;
21  import java.awt.geom.Point2D;
22  import java.awt.geom.Rectangle2D;
23  import java.util.Set;
24  
25  import javax.swing.BoundedRangeModel;
26  import javax.swing.JComponent;
27  import javax.swing.JPanel;
28  import javax.swing.JScrollBar;
29  import javax.swing.event.ChangeEvent;
30  import javax.swing.event.ChangeListener;
31  
32  import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer;
33  import edu.uci.ics.jung.visualization.transform.shape.Intersector;
34  
35  
36  
37  /**
38   * GraphZoomScrollPane is a Container for the Graph's VisualizationViewer
39   * and includes custom horizontal and vertical scrollbars.
40   * GraphZoomScrollPane listens for changes in the scale and
41   * translation of the VisualizationViewer, and will update the
42   * scrollbar positions and sizes accordingly. Changes in the
43   * scrollbar positions will cause the corresponding change in
44   * the translation component (offset) of the VisualizationViewer.
45   * The scrollbars are modified so that they will allow panning
46   * of the graph when the scale has been changed (e.g. zoomed-in
47   * or zoomed-out).
48   * 
49   * The lower-right corner of this component is available to
50   * use as a small button or menu.
51   * 
52   * samples.graph.GraphZoomScrollPaneDemo shows the use of this component.
53   * 
54   * @author Tom Nelson 
55   *
56   * 
57   */
58  @SuppressWarnings("serial")
59  public class GraphZoomScrollPane extends JPanel {
60      protected VisualizationViewer<?, ?> vv;
61      protected JScrollBar horizontalScrollBar;
62      protected JScrollBar verticalScrollBar;
63      protected JComponent corner;
64      protected boolean scrollBarsMayControlAdjusting = true;
65      protected JPanel south;
66      
67      /**
68       * Create an instance of the GraphZoomScrollPane to contain the
69       * VisualizationViewer
70       * @param vv the VisualizationViewer for which this instance is to be created
71       */
72      public GraphZoomScrollPane(VisualizationViewer<?, ?> vv) {
73          super(new BorderLayout());
74          this.vv = vv;
75          addComponentListener(new ResizeListener());        
76          Dimension d = vv.getGraphLayout().getSize();
77          verticalScrollBar = new JScrollBar(JScrollBar.VERTICAL, 0, d.height, 0, d.height);
78          horizontalScrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 0, d.width, 0, d.width);
79          verticalScrollBar.addAdjustmentListener(new VerticalAdjustmentListenerImpl());
80          horizontalScrollBar.addAdjustmentListener(new HorizontalAdjustmentListenerImpl());
81          verticalScrollBar.setUnitIncrement(20);
82          horizontalScrollBar.setUnitIncrement(20);
83          // respond to changes in the VisualizationViewer's transform
84          // and set the scroll bar parameters appropriately
85          vv.addChangeListener(
86                  new ChangeListener(){
87              public void stateChanged(ChangeEvent evt) {
88                  VisualizationViewer<?, ?> vv = 
89                      (VisualizationViewer<?, ?>)evt.getSource();
90                  setScrollBars(vv);
91              }
92          });
93          add(vv);
94          add(verticalScrollBar, BorderLayout.EAST);
95          south = new JPanel(new BorderLayout());
96          south.add(horizontalScrollBar);
97          setCorner(new JPanel());
98          add(south, BorderLayout.SOUTH);
99      }
100     
101     /**
102      * listener for adjustment of the horizontal scroll bar.
103      * Sets the translation of the VisualizationViewer
104      */
105     class HorizontalAdjustmentListenerImpl implements AdjustmentListener {
106         int previous = 0;
107         public void adjustmentValueChanged(AdjustmentEvent e) {
108             int hval = e.getValue();
109             float dh = previous - hval;
110             previous = hval;
111             if(dh != 0 && scrollBarsMayControlAdjusting) {
112                 // get the uniform scale of all transforms
113                 float layoutScale = (float) vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale();
114                 dh *= layoutScale;
115                 AffineTransform at = AffineTransform.getTranslateInstance(dh, 0);
116                 vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).preConcatenate(at);
117             }
118         }
119     }
120     
121     /**
122      * Listener for adjustment of the vertical scroll bar.
123      * Sets the translation of the VisualizationViewer
124      */
125     class VerticalAdjustmentListenerImpl implements AdjustmentListener {
126         int previous = 0;
127         public void adjustmentValueChanged(AdjustmentEvent e) {
128             JScrollBar sb = (JScrollBar)e.getSource();
129             BoundedRangeModel m = sb.getModel();
130             int vval = m.getValue();
131             float dv = previous - vval;
132             previous = vval;
133             if(dv != 0 && scrollBarsMayControlAdjusting) {
134             
135                 // get the uniform scale of all transforms
136                 float layoutScale = (float) vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale();
137                 dv *= layoutScale;
138                 AffineTransform at = AffineTransform.getTranslateInstance(0, dv);
139                 vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).preConcatenate(at);
140             }
141         }
142     }
143     
144     /**
145      * use the supplied vv characteristics to set the position and
146      * dimensions of the scroll bars. Called in response to
147      * a ChangeEvent from the VisualizationViewer
148      * @param xform the transform of the VisualizationViewer
149      */
150     private void setScrollBars(VisualizationViewer<?, ?> vv) {
151         Dimension d = vv.getGraphLayout().getSize();
152         Rectangle2D vvBounds = vv.getBounds();
153         
154         // a rectangle representing the layout
155         Rectangle layoutRectangle = 
156             new Rectangle(0,0,d.width,d.height);
157             		//-d.width/2, -d.height/2, 2*d.width, 2*d.height);
158         
159         BidirectionalTransformer viewTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
160         BidirectionalTransformer layoutTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
161 
162         Point2D h0 = new Point2D.Double(vvBounds.getMinX(), vvBounds.getCenterY());
163         Point2D h1 = new Point2D.Double(vvBounds.getMaxX(), vvBounds.getCenterY());
164         Point2D v0 = new Point2D.Double(vvBounds.getCenterX(), vvBounds.getMinY());
165         Point2D v1 = new Point2D.Double(vvBounds.getCenterX(), vvBounds.getMaxY());
166         
167         h0 = viewTransformer.inverseTransform(h0);
168         h0 = layoutTransformer.inverseTransform(h0);
169         h1 = viewTransformer.inverseTransform(h1);
170         h1 = layoutTransformer.inverseTransform(h1);
171         v0 = viewTransformer.inverseTransform(v0);
172         v0 = layoutTransformer.inverseTransform(v0);
173         v1 = viewTransformer.inverseTransform(v1);
174         v1 = layoutTransformer.inverseTransform(v1);
175         
176         scrollBarsMayControlAdjusting = false;
177         setScrollBarValues(layoutRectangle, h0, h1, v0, v1);
178         scrollBarsMayControlAdjusting = true;
179     }
180     
181     protected void setScrollBarValues(Rectangle rectangle, 
182             Point2D h0, Point2D h1, 
183             Point2D v0, Point2D v1) {
184         boolean containsH0 = rectangle.contains(h0);
185         boolean containsH1 = rectangle.contains(h1);
186         boolean containsV0 = rectangle.contains(v0);
187         boolean containsV1 = rectangle.contains(v1);
188         
189         // horizontal scrollbar:
190         
191         Intersector intersector = new Intersector(rectangle, new Line2D.Double(h0, h1));
192         
193         int min = 0;
194         int ext;
195         int val = 0;
196         int max;
197         
198         Set<Point2D> points = intersector.getPoints();
199         Point2D first = null;
200         Point2D second = null;
201         
202         Point2D[] pointArray = (Point2D[])points.toArray(new Point2D[points.size()]);
203         if(pointArray.length > 1) {
204             first = pointArray[0];
205             second = pointArray[1];
206         } else if(pointArray.length > 0) {
207             first = second = pointArray[0];
208         }
209         
210         if(first != null && second != null) {
211             // correct direction of intersect points
212             if((h0.getX() - h1.getX()) * (first.getX() - second.getX()) < 0) {
213                 // swap them
214                 Point2D temp = first;
215                 first = second;
216                 second = temp;
217             }
218 
219             if(containsH0 && containsH1) {
220                 max = (int)first.distance(second);
221                 val = (int)first.distance(h0);
222                 ext = (int)h0.distance(h1);
223                 
224             } else if(containsH0) {
225                 max = (int)first.distance(second);
226                 val = (int)first.distance(h0);
227                 ext = (int)h0.distance(second);
228                 
229             } else if(containsH1) {
230                 max = (int) first.distance(second);
231                 val = 0;
232                 ext = (int) first.distance(h1);
233                 
234             } else {
235                 max = ext = rectangle.width;
236                 val = min;
237             }
238             horizontalScrollBar.setValues(val, ext+1, min, max);
239         }
240         
241         // vertical scroll bar
242         min = val = 0;
243         
244         intersector.intersectLine(new Line2D.Double(v0, v1));
245         points = intersector.getPoints();
246         
247         pointArray = (Point2D[])points.toArray(new Point2D[points.size()]);
248         if(pointArray.length > 1) {
249             first = pointArray[0];
250             second = pointArray[1];
251         } else if(pointArray.length > 0) {
252             first = second = pointArray[0];
253         }
254         
255         if(first != null && second != null) {
256             
257             // arrange for direction
258             if((v0.getY() - v1.getY()) * (first.getY() - second.getY()) < 0) {
259                 // swap them
260                 Point2D temp = first;
261                 first = second;
262                 second = temp;
263             }
264             
265             if(containsV0 && containsV1) {
266                 max = (int)first.distance(second);
267                 val = (int)first.distance(v0);
268                 ext = (int)v0.distance(v1);
269                 
270             } else if(containsV0) {
271                 max = (int)first.distance(second);
272                 val = (int)first.distance(v0);
273                 ext = (int)v0.distance(second);
274                 
275             } else if(containsV1) {
276                 max = (int) first.distance(second);
277                 val = 0;
278                 ext = (int) first.distance(v1);
279                 
280             } else {
281                 max = ext = rectangle.height;
282                 val = min;
283             }
284             verticalScrollBar.setValues(val, ext+1, min, max);
285         }
286     }
287 
288     /**
289      * Listener to adjust the scroll bar parameters when the window
290      * is resized
291      */
292 	protected class ResizeListener extends ComponentAdapter {
293 
294 		public void componentHidden(ComponentEvent e) {
295 		}
296 
297 		public void componentResized(ComponentEvent e) {
298 		    setScrollBars(vv);
299 		}	
300 		public void componentShown(ComponentEvent e) {
301 		}
302 	}
303 
304     /**
305      * @return Returns the corner component.
306      */
307     public JComponent getCorner() {
308         return corner;
309     }
310 
311     /**
312      * @param corner The cornerButton to set.
313      */
314     public void setCorner(JComponent corner) {
315         this.corner = corner;
316         corner.setPreferredSize(new Dimension(verticalScrollBar.getPreferredSize().width,
317                 horizontalScrollBar.getPreferredSize().height));
318         south.add(this.corner, BorderLayout.EAST);
319     }
320 
321     public JScrollBar getHorizontalScrollBar() {
322         return horizontalScrollBar;
323     }
324 
325     public JScrollBar getVerticalScrollBar() {
326         return verticalScrollBar;
327     }
328 }