1   /****************************************************************************
2    **
3    ** This file is part of yFiles-2.6. 
4    ** 
5    ** yWorks proprietary/confidential. Use is subject to license terms.
6    **
7    ** Redistribution of this file or of an unauthorized byte-code version
8    ** of this file is strictly forbidden.
9    **
10   ** Copyright (c) 2000-2008 by yWorks GmbH, Vor dem Kreuzberg 28, 
11   ** 72070 Tuebingen, Germany. All rights reserved.
12   **
13   ***************************************************************************/
14  package demo.view.realizer;
15  
16  import y.view.AbstractMouseInputEditor;
17  import y.view.EditMode;
18  import y.view.Graph2DView;
19  import y.view.HitInfo;
20  import y.view.Mouse2DEvent;
21  import y.view.MouseInputEditor;
22  import y.view.NodeRealizer;
23  import y.view.MouseInputEditorProvider;
24  
25  import javax.swing.JFrame;
26  import javax.swing.JRootPane;
27  import java.awt.Color;
28  import java.awt.Graphics2D;
29  import java.awt.geom.GeneralPath;
30  import java.awt.geom.Rectangle2D;
31  
32  /**
33   * Demonstrates how scrollbars inside a NodeRealizer can be implemented.
34   */
35  public class ScrollingNodeRealizer extends NodeRealizer implements MouseInputEditorProvider {
36  
37    private double viewWidth = 300;
38    private double viewHeight = 300;
39    private ScrollBar verticalScrollBar;
40    private ScrollBar horizontalScrollBar;
41  
42    public ScrollingNodeRealizer() {
43      super();
44      init();
45    }
46  
47    public ScrollingNodeRealizer(NodeRealizer nr) {
48      super(nr);
49      init();
50    }
51  
52    private void init() {
53      this.verticalScrollBar = new ScrollBar(true, 100, this);
54      this.horizontalScrollBar = new ScrollBar(false, 100, this);
55      boundsChanged();
56    }
57  
58    protected void boundsChanged() {
59      super.boundsChanged();
60      this.horizontalScrollBar.setBounds(
61          new Rectangle2D.Double(0, getViewPortHeight(), getViewPortWidth(), getHeight() - getViewPortHeight() - 1));
62      this.verticalScrollBar.setBounds(
63          new Rectangle2D.Double(getViewPortWidth(), 0, getWidth() - getViewPortWidth() - 1, getViewPortHeight()));
64      this.horizontalScrollBar.setMaxPosition(Math.max(0, getViewWidth() - getViewPortWidth()));
65      this.horizontalScrollBar.setExtent(Math.max(10, getViewPortWidth() * getViewPortWidth() / getViewWidth()));
66      this.verticalScrollBar.setMaxPosition(Math.max(0, getViewHeight() - getViewPortHeight()));
67      this.verticalScrollBar.setExtent(Math.max(10, getViewPortHeight() * getViewPortHeight() / getViewHeight()));
68    }
69  
70    public NodeRealizer createCopy(NodeRealizer nr) {
71      return new ScrollingNodeRealizer(nr);
72    }
73  
74    public Rectangle2D getViewPortBounds() {
75      return new Rectangle2D.Double(0, 0, getViewPortWidth(), getViewPortHeight());
76    }
77  
78    public double getViewPortWidth() {
79      return getWidth() - 10;
80    }
81  
82    public double getViewPortHeight() {
83      return getHeight() - 10;
84    }
85  
86    public double getViewWidth() {
87      return viewWidth;
88    }
89  
90    public double getViewHeight() {
91      return viewHeight;
92    }
93  
94    protected void paintNode(Graphics2D g) {
95      Rectangle2D.Double rect = new Rectangle2D.Double(x, y, width, height);
96      g.setColor(getFillColor());
97      g.fill(rect);
98      if (isSelected()) {
99        paintHotSpots(g);
100     }
101     g = (Graphics2D) g.create();
102     g.translate(x, y);
103     horizontalScrollBar.paint(g);
104     verticalScrollBar.paint(g);
105     rect.setFrame(getViewPortBounds());
106     g.clip(rect);
107     g.translate(rect.x - horizontalScrollBar.getPosition(), rect.y - verticalScrollBar.getPosition());
108     g.setColor(Color.black);
109     renderContent(g);
110     g.dispose();
111   }
112 
113   private void renderContent(Graphics2D g) {
114     g.setColor(getLineColor());
115     for (int i = 0; i < 10; i ++) {
116       g.drawString("Test" + i, i, 10 * i);
117     }
118   }
119 
120   /**
121    * returns a mouse input editor provider for this realizer. This realizer implements the
122    * {@link MouseInputEditorProvider} interface, so this method just returns this realizer.
123    * This method is not really necessary, since the implementation in the super class {@link NodeRealizer}
124    * would also return this realizer, after checking that this realizer implements the
125    * {@link MouseInputEditorProvider} interface. The reimplementation in this class just
126    * removes the check. In principle, it would also be possible to return an instance of
127    * an external class here if needed.
128    * @return this realizer
129    * @see #findMouseInputEditor(Graph2DView, double, double, HitInfo)
130    */
131   public MouseInputEditorProvider getMouseInputEditorProvider() {
132     return this;
133   }
134 
135   public MouseInputEditor findMouseInputEditor(Graph2DView view, double x, double y, HitInfo hitInfo) {
136     x -= getX();
137     y -= getY();
138     MouseInputEditor editor = verticalScrollBar.findEditor(x, y);
139     if (editor != null) {
140       return editor;
141     }
142     editor = horizontalScrollBar.findEditor(x, y);
143     return editor;
144   }
145 
146   static final class ScrollBar {
147     private double x;
148     private double y;
149     private double w;
150     private double h;
151     private double position;
152     private double extent;
153     private double maxPosition;
154     private boolean vertical;
155     private NodeRealizer context;
156     private ScrollBarInputEditor currentEditor;
157 
158     public ScrollBar(boolean vertical, double maxPosition, NodeRealizer context) {
159       this.maxPosition = maxPosition;
160       this.vertical = vertical;
161       this.context = context;
162     }
163 
164     public Rectangle2D getBounds(Rectangle2D rect) {
165       rect.setFrame(x, y, w, h);
166       return rect;
167     }
168 
169     public boolean contains(double x, double y) {
170       return x >= this.x && y >= this.y && x < this.x + this.w && y < this.y + this.h;
171     }
172 
173     public double getExtent() {
174       return extent;
175     }
176 
177     public void setExtent(double extent) {
178       this.extent = extent;
179     }
180 
181     public void setBounds(Rectangle2D bounds) {
182       this.x = bounds.getX();
183       this.y = bounds.getY();
184       this.w = bounds.getWidth();
185       this.h = bounds.getHeight();
186     }
187 
188     public double getPosition() {
189       return position;
190     }
191 
192     public void setPosition(double position) {
193       double oldPosition = this.position;
194       this.position = Math.max(0, Math.min(position, maxPosition));
195       if (oldPosition != this.position) {
196         repaint();
197       }
198     }
199 
200     public void setMaxPosition(double maxPosition) {
201       if (this.maxPosition != maxPosition) {
202         this.maxPosition = maxPosition;
203         this.position = Math.min(this.position, maxPosition);
204         repaint();
205       }
206     }
207 
208     public double getMaxPosition() {
209       return maxPosition;
210     }
211 
212     public void repaint() {
213       context.repaint();
214     }
215 
216     public void paint(Graphics2D g) {
217       if (maxPosition == 0) {
218         return;
219       }
220       Rectangle2D.Double bounds = new Rectangle2D.Double();
221       getBounds(bounds);
222       g = (Graphics2D) g.create();
223       g.setColor(isHighlighted() ? context.getFillColor().brighter() : context.getFillColor());
224       g.fill(bounds);
225       g.setColor(context.getLineColor());
226       g.draw(bounds);
227       GeneralPath p = new GeneralPath(GeneralPath.WIND_EVEN_ODD, 5);
228       if (vertical) {
229         p.moveTo((float) (bounds.getCenterX()), (float) (bounds.getY() + 4));
230         p.lineTo((float) (bounds.getCenterX() + 3), (float) (bounds.getY() + 10));
231         p.lineTo((float) (bounds.getCenterX() - 3), (float) (bounds.getY() + 10));
232         p.closePath();
233         g.fill(p);
234 
235         p.reset();
236         p.moveTo((float) (bounds.getCenterX()), (float) (bounds.getMaxY() - 4));
237         p.lineTo((float) (bounds.getCenterX() + 3), (float) (bounds.getMaxY() - 10));
238         p.lineTo((float) (bounds.getCenterX() - 3), (float) (bounds.getMaxY() - 10));
239         p.closePath();
240         g.fill(p);
241 
242         getThumbBounds(bounds);
243         g.fill(bounds);
244 
245         g.dispose();
246       } else {
247         p.moveTo((float) (bounds.getX() + 4), (float) (bounds.getCenterY()));
248         p.lineTo((float) (bounds.getX() + 10), (float) (bounds.getCenterY() + 3));
249         p.lineTo((float) (bounds.getX() + 10), (float) (bounds.getCenterY() - 3));
250         p.closePath();
251         g.fill(p);
252 
253         p.reset();
254         p.moveTo((float) (bounds.getMaxX() - 4), (float) (bounds.getCenterY()));
255         p.lineTo((float) (bounds.getMaxX() - 10), (float) (bounds.getCenterY() + 3));
256         p.lineTo((float) (bounds.getMaxX() - 10), (float) (bounds.getCenterY() - 3));
257         p.closePath();
258         g.fill(p);
259         getThumbBounds(bounds);
260         g.fill(bounds);
261         g.dispose();
262       }
263     }
264 
265     private void getThumbBounds(Rectangle2D bounds) {
266       getBounds(bounds);
267       if (vertical) {
268         bounds.setFrame(bounds.getX() + 1, bounds.getY() + 12, bounds.getWidth() - 2, bounds.getHeight() - 24);
269         double p = getPosition() / getMaxPosition();
270         bounds.setFrame(bounds.getX(), bounds.getY() + (bounds.getHeight() - getExtent()) * p, bounds.getWidth(),
271             getExtent());
272       } else {
273         bounds.setFrame(bounds.getX() + 12, bounds.getY() + 1, bounds.getWidth() - 24, bounds.getHeight() - 2);
274         double p = getPosition() / getMaxPosition();
275         bounds.setFrame(bounds.getX() + (bounds.getWidth() - getExtent()) * p, bounds.getY(), getExtent(),
276             bounds.getHeight());
277       }
278     }
279 
280     private boolean isHighlighted() {
281       return currentEditor != null && currentEditor.isHighlighted();
282     }
283 
284     public MouseInputEditor findEditor(double x, double y) {
285       if (maxPosition == 0) {
286         return null;
287       }
288       Rectangle2D.Double bounds = new Rectangle2D.Double();
289       getBounds(bounds);
290 
291       if (contains(x, y)) {
292         this.currentEditor = new ScrollBarInputEditor();
293         return currentEditor;
294       }
295       return null;
296     }
297 
298     class ScrollBarInputEditor extends AbstractMouseInputEditor {
299 
300       private boolean down;
301       private double initialX;
302       private double initialY;
303       private double initialPos;
304       private boolean highlighted;
305 
306       public boolean startsEditing(Mouse2DEvent event) {
307         double x = event.getX() - context.getX();
308         double y = event.getY() - context.getY();
309         if (contains(x, y)) {
310           highlighted = true;
311           repaint();
312           return true;
313         }
314         return false;
315       }
316 
317       public void mouse2DEventHappened(Mouse2DEvent event) {
318         if (context != null) {
319           if (event.getId() == Mouse2DEvent.MOUSE_MOVED ||
320               event.getId() == Mouse2DEvent.MOUSE_RELEASED) {
321             double x = event.getX() - context.getX();
322             double y = event.getY() - context.getY();
323             if (!contains(x, y)) {
324               highlighted = false;
325               ScrollBar.this.currentEditor = null;
326               repaint();
327               stopEditing();
328             }
329           } else {
330             double x = event.getX() - context.getX();
331             double y = event.getY() - context.getY();
332             switch (event.getId()) {
333               case Mouse2DEvent.MOUSE_CLICKED:
334                 if (vertical) {
335                   setPosition(getPosition() + (y > (ScrollBar.this.y + ScrollBar.this.h * 0.5d) ? 10 : -10));
336                 } else {
337                   setPosition(getPosition() + (x > (ScrollBar.this.x + ScrollBar.this.w * 0.5d) ? 10 : -10));
338                 }
339                 break;
340               case Mouse2DEvent.MOUSE_PRESSED:
341                 initialX = x;
342                 initialY = y;
343                 initialPos = getPosition();
344                 down = true;
345                 break;
346               case Mouse2DEvent.MOUSE_RELEASED:
347                 if (down) {
348                   double dx = x - initialX;
349                   double dy = y - initialY;
350                   setPosition(initialPos + (vertical ? dy : dx));
351                   down = false;
352                 }
353                 break;
354               case Mouse2DEvent.MOUSE_DRAGGED:
355                 if (down) {
356                   double dx = x - initialX;
357                   double dy = y - initialY;
358                   setPosition(initialPos + (vertical ? dy : dx));
359                 }
360                 break;
361             }
362           }
363         } else {
364           ScrollBar.this.currentEditor = null;
365           stopEditing();
366         }
367       }
368 
369       public boolean isHighlighted() {
370         return highlighted;
371       }
372     }
373   }
374 
375 
376 
377   public static void addContentTo( final JRootPane rootPane )
378   {
379     final ScrollingNodeRealizer r = new ScrollingNodeRealizer();
380     r.setSize(100, 40);
381 
382     final EditMode editMode = new EditMode();
383     editMode.getMouseInputMode().setNodeSearchingEnabled(true);
384     editMode.allowMouseInput(true);
385 
386     final Graph2DView view = new Graph2DView();
387     view.getGraph2D().setDefaultNodeRealizer(r.createCopy());
388     view.setAntialiasedPainting(true);
389     view.getGraph2D().createNode();
390     view.addViewMode(editMode);
391     view.fitContent();
392 
393     rootPane.setContentPane(view);
394   }
395 
396   /**
397    * Launcher method. Execute this class to see a sample instantiation of
398    * this node realizer in action.
399    */
400   public static void main(String[] args)
401   {
402     final JFrame frame = new JFrame();
403     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
404     addContentTo(frame.getRootPane());
405     frame.pack();
406     frame.setLocationRelativeTo(null);
407     frame.setVisible(true);
408   }
409 }
410