View Javadoc

1   /*
2    * TouchGraph LLC. Apache-Style Software License
3    *
4    *
5    * Copyright (c) 2001-2002 Alexander Shapiro. All rights reserved.
6    *
7    * Redistribution and use in source and binary forms, with or without
8    * modification, are permitted provided that the following conditions
9    * are met:
10   *
11   * 1. Redistributions of source code must retain the above copyright
12   *    notice, this list of conditions and the following disclaimer. 
13   *
14   * 2. Redistributions in binary form must reproduce the above copyright
15   *    notice, this list of conditions and the following disclaimer in
16   *    the documentation and/or other materials provided with the
17   *    distribution.
18   *
19   * 3. The end-user documentation included with the redistribution,
20   *    if any, must include the following acknowledgment:  
21   *       "This product includes software developed by 
22   *        TouchGraph LLC (http://www.touchgraph.com/)."
23   *    Alternately, this acknowledgment may appear in the software itself,
24   *    if and wherever such third-party acknowledgments normally appear.
25   *
26   * 4. The names "TouchGraph" or "TouchGraph LLC" must not be used to endorse 
27   *    or promote products derived from this software without prior written 
28   *    permission.  For written permission, please contact 
29   *    alex@touchgraph.com
30   *
31   * 5. Products derived from this software may not be called "TouchGraph",
32   *    nor may "TouchGraph" appear in their name, without prior written
33   *    permission of alex@touchgraph.com.
34   *
35   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38   * DISCLAIMED.  IN NO EVENT SHALL TOUCHGRAPH OR ITS CONTRIBUTORS BE 
39   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
40   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
41   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
42   * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
43   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
44   * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
45   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46   * ====================================================================
47   *
48   */
49  
50  package com.touchgraph.graphlayout.interaction;
51  
52  import java.awt.Point;
53  import java.awt.event.*;
54  
55  import javax.swing.JScrollBar;
56  
57  import com.touchgraph.graphlayout.*;
58  
59  /*** HVScroll:  Allows for scrolling horizontaly+vertically.  This can be
60    * done in all sorts of ways, for instance by using the scrollbars, or by
61    * dragging.
62    *
63    * <p>
64    * This code is more complex then it would seem it should be, because scrolling
65    * has to be independent of the screen being warped by lenses.  HVScroll needs
66    * to use the tgLensSet object because the offset is recorded in real coordinates, while
67    * the user interacts with the drawn coordinates.
68    *
69    * @author   Alexander Shapiro
70    * @version  1.21  $Id: HVScroll.java,v 1.1.1.1 2004/02/06 08:44:07 keesj Exp $
71    */
72  public class HVScroll implements GraphListener {
73  
74      private DScrollbar horizontalSB;
75      private DScrollbar verticalSB;
76  
77      HVLens hvLens;
78      HVDragUI hvDragUI;
79      HVScrollToCenterUI hvScrollToCenterUI;
80      public boolean scrolling;
81  
82      private boolean adjustmentIsInternal;
83      private TGPanel tgPanel;
84      private TGLensSet tgLensSet;
85  
86      TGPoint2D offset;
87  
88    // ............
89  
90     /*** Constructor with a TGPanel <tt>tgp</tt> and TGLensSet <tt>tgls</tt>.
91       */
92      public HVScroll(TGPanel tgp, TGLensSet tgls) {
93          tgPanel=tgp;
94          tgLensSet=tgls;
95  
96          offset=new TGPoint2D(0,0);
97          scrolling = false;
98          adjustmentIsInternal = false;
99  
100         horizontalSB = new DScrollbar(JScrollBar.HORIZONTAL, 0, 100, -1000, 1100);
101         horizontalSB.setBlockIncrement(100);
102         horizontalSB.setUnitIncrement(20);
103 
104         horizontalSB.addAdjustmentListener(new horizAdjustmentListener());
105 
106         verticalSB = new DScrollbar(JScrollBar.VERTICAL, 0, 100, -1000, 1100);
107         verticalSB.setBlockIncrement(100);
108         verticalSB.setUnitIncrement(20);
109 
110         verticalSB.addAdjustmentListener(new vertAdjustmentListener());
111 
112         hvLens=new HVLens();
113         hvDragUI = new HVDragUI(); //Hopefully this approach won't eat too much memory
114         hvScrollToCenterUI = new HVScrollToCenterUI();
115 
116         tgPanel.addGraphListener(this);
117     }
118 
119     public JScrollBar getHorizontalSB() { return horizontalSB; }
120 
121     public JScrollBar getVerticalSB() { return verticalSB; }
122 
123     public HVDragUI getHVDragUI() { return hvDragUI; }
124 
125     public HVLens getLens() { return hvLens; }
126 
127     public TGAbstractClickUI getHVScrollToCenterUI() { return hvScrollToCenterUI; }
128 
129     public TGPoint2D getTopLeftDraw() {
130         TGPoint2D tld = tgPanel.getTopLeftDraw();
131         tld.setLocation(tld.x-tgPanel.getSize().width/4,tld.y-tgPanel.getSize().height/4);
132         return tld;
133     }
134 
135     public TGPoint2D getBottomRightDraw() {
136         TGPoint2D brd = tgPanel.getBottomRightDraw();
137         brd.setLocation(brd.x+tgPanel.getSize().width/4,brd.y+tgPanel.getSize().height/4);
138         return brd;
139     }
140 
141     public TGPoint2D getDrawCenter() { //Should probably be called from tgPanel
142         return new TGPoint2D(tgPanel.getSize().width/2,tgPanel.getSize().height/2);
143     }
144 
145     public void graphMoved() { //From GraphListener interface
146         if (tgPanel.getDragNode()==null && tgPanel.getSize().height>0)    {
147             TGPoint2D drawCenter = getDrawCenter();
148 
149             TGPoint2D tld = getTopLeftDraw();
150             TGPoint2D brd = getBottomRightDraw();
151 
152             double newH = (-(tld.x-drawCenter.x)/(brd.x-tld.x)*2000-1000);
153             double newV = (-(tld.y-drawCenter.y)/(brd.y-tld.y)*2000-1000);
154 
155             boolean beyondBorder;
156             beyondBorder = true;
157 
158             if(newH<horizontalSB.getMaximum() && newH>horizontalSB.getMinimum() &&
159                newV<verticalSB.getMaximum()   && newV>verticalSB.getMinimum() ) beyondBorder=false;
160 
161             adjustmentIsInternal = true;
162             horizontalSB.setDValue(newH);
163             verticalSB.setDValue(newV);
164             adjustmentIsInternal = false;
165 
166             if (beyondBorder) {
167                 adjustHOffset();
168                 adjustVOffset();
169                 tgPanel.repaint();
170             }
171         }
172     }
173 
174     public void graphReset() { //From GraphListener interface
175         horizontalSB.setDValue(0);
176         verticalSB.setDValue(0);
177 
178         adjustHOffset();
179         adjustVOffset();
180     }
181 
182     class DScrollbar extends JScrollBar {
183         private double doubleValue;
184 
185         DScrollbar(int orient, int val, int vis, int min, int max){
186             super(orient, val, vis, min, max);
187             doubleValue=val;
188         }
189         public void setValue(int v) { doubleValue = v; super.setValue(v); }
190         public void setIValue(int v) { super.setValue(v); }
191         public void setDValue(double v) {
192             doubleValue = Math.max(getMinimum(),Math.min(getMaximum(),v));
193             setIValue((int) v);
194         }
195         public double getDValue() { return doubleValue;}
196     }
197 
198     private void adjustHOffset() { //The inverse of the "graphMoved" function.
199         //System.out.println(horizontalSB.getDValue());
200         for(int iterate=0;iterate<3;iterate++) { // Iteration needed to yeild cerrect results depite warping lenses
201             TGPoint2D center= tgPanel.getCenter();
202             TGPoint2D tld = getTopLeftDraw();
203             TGPoint2D brd = getBottomRightDraw();
204 
205             double newx = ((horizontalSB.getDValue()+1000.0)/2000)*(brd.x-tld.x)+tld.x;
206             double newy = tgPanel.getSize().height/2;
207             TGPoint2D newCenter = tgLensSet.convDrawToReal(newx,newy);
208 
209             offset.setX(offset.x+(newCenter.x-center.x));
210             offset.setY(offset.y+(newCenter.y-center.y));
211             
212             tgPanel.processGraphMove();
213         }
214     }
215 
216     private void adjustVOffset() { //The inverse of the "graphMoved" function.
217         for(int iterate=0;iterate<10;iterate++) { // Iteration needed to yeild cerrect results depite warping lenses
218             TGPoint2D center= tgPanel.getCenter();
219             TGPoint2D tld = getTopLeftDraw();
220             TGPoint2D brd = getBottomRightDraw();
221 
222             double newx = tgPanel.getSize().width/2;
223             double newy = ((verticalSB.getDValue()+1000.0)/2000)*(brd.y-tld.y)+tld.y;
224 
225             TGPoint2D newCenter = tgLensSet.convDrawToReal(newx,newy);
226 
227             offset.setX(offset.x+(newCenter.x-center.x));
228             offset.setY(offset.y+(newCenter.y-center.y));
229             
230             tgPanel.processGraphMove();
231         }
232     }
233 
234     private class horizAdjustmentListener implements AdjustmentListener {
235         public void adjustmentValueChanged(AdjustmentEvent e) {
236               if(!adjustmentIsInternal) {
237                   adjustHOffset();
238                 tgPanel.repaintAfterMove();
239             }
240         }
241     }
242 
243     private class vertAdjustmentListener implements AdjustmentListener {
244         public void adjustmentValueChanged(AdjustmentEvent e) {
245             if(!adjustmentIsInternal) {
246                 adjustVOffset();
247                 tgPanel.repaintAfterMove();
248             }
249         }
250     }
251 
252 
253      class HVLens extends TGAbstractLens {
254         protected void applyLens(TGPoint2D p) {
255             p.x=p.x-offset.x;
256             p.y=p.y-offset.y;
257         }
258 
259         protected void undoLens(TGPoint2D p) {
260             p.x=p.x+offset.x;
261             p.y=p.y+offset.y;
262         }
263      }
264 
265      public void setOffset(Point p) {
266         offset.setLocation(p.x,p.y);
267         tgPanel.processGraphMove(); //Adjust draw coordinates to include new offset
268         graphMoved(); //adjusts scrollbars to fit draw coordinates
269      }
270 
271      public Point getOffset() {
272         return new Point((int) offset.x,(int) offset.y);
273      }
274     
275      public void scrollAtoB(TGPoint2D drawFrom, TGPoint2D drawTo) {
276         TGPoint2D from = tgLensSet.convDrawToReal(drawFrom);
277         TGPoint2D to = tgLensSet.convDrawToReal(drawTo);
278         offset.setX(offset.x+(from.x-to.x));
279         offset.setY(offset.y+(from.y-to.y));
280      }
281 
282      public void scrollToCenter(final Node n) {
283          final TGPoint2D drawFrom =new TGPoint2D(n.drawx,n.drawy);
284          final TGPoint2D drawTo = getDrawCenter();
285          scrollAtoB(drawFrom,drawTo);           
286      }
287      
288      public void slowScrollToCenter(final Node n) {
289          final TGPoint2D drawFrom =new TGPoint2D(n.drawx,n.drawy);
290          final TGPoint2D drawTo = getDrawCenter();
291          scrolling = true;
292          Thread scrollThread = new Thread() {
293              public void run() {
294                 double fx= drawFrom.x;
295                 double fy= drawFrom.y;
296                 double tx= drawTo.x;
297                 double ty= drawTo.y;
298                 int scrollSteps = (int) Math.sqrt((fx-tx)*(fx-tx)+(fy-ty)*(fy-ty))/10 + 1;
299 
300                 for(int i=0;i< scrollSteps-1;i++) {
301                     //double fromPcnt = (Math.cos(Math.PI*i/scrollSteps)+1)/2;
302                     //double toPcnt = (Math.cos(Math.PI*(i+1)/scrollSteps)+1)/2;
303 
304                     double fromPcnt = ((double) scrollSteps-i)/scrollSteps;
305                     double toPcnt = ((double) scrollSteps-1-i)/scrollSteps;
306 
307                     double midfx = fx*fromPcnt+tx*(1-fromPcnt);
308                     double midfy = fy*fromPcnt+ty*(1-fromPcnt);
309                     double midtx = fx*toPcnt+tx*(1-toPcnt);
310                     double midty = fy*toPcnt+ty*(1-toPcnt);
311 
312                     scrollAtoB(new TGPoint2D(midfx,midfy), new TGPoint2D(midtx,midty));
313                     tgPanel.repaintAfterMove();
314                     try {
315                            Thread.sleep(50);
316                     } catch (InterruptedException ex) {    }
317                 }
318                 scrollAtoB(new TGPoint2D(n.drawx,n.drawy),getDrawCenter()); //for good measure
319                 tgPanel.repaintAfterMove();
320                 HVScroll.this.scrolling = false;
321             }
322         };
323         scrollThread.start();
324      }
325 
326     class HVScrollToCenterUI extends TGAbstractClickUI {
327         public void mouseClicked(MouseEvent e) {
328             Node mouseOverN=tgPanel.getMouseOverN();
329             if(!scrolling && mouseOverN!=null)
330                 slowScrollToCenter(mouseOverN);
331         }
332     }
333 
334     class HVDragUI extends TGAbstractDragUI{
335         TGPoint2D lastMousePos;
336         HVDragUI() { super(HVScroll.this.tgPanel); }
337 
338         public void preActivate() {}
339         public void preDeactivate() {}
340 
341         public void mousePressed(MouseEvent e) {
342             lastMousePos = new TGPoint2D(e.getX(), e.getY());
343         }
344         public void mouseReleased(MouseEvent e) {}
345         public void mouseDragged(MouseEvent e) {
346             if(!scrolling) scrollAtoB(lastMousePos, new TGPoint2D(e.getX(), e.getY()));
347             lastMousePos.setLocation(e.getX(),e.getY());
348             this.tgPanel.repaintAfterMove();
349         }
350     }
351 
352 } // end com.touchgraph.graphlayout.interaction.HVScroll