1 /**
2  * Copyright (c) 2008, Gaudenz Alder
3  */
4 package com.mxgraph.swing;
5 
6 import java.awt.BasicStroke;
7 import java.awt.Color;
8 import java.awt.Cursor;
9 import java.awt.Dimension;
10 import java.awt.Graphics;
11 import java.awt.Graphics2D;
12 import java.awt.Point;
13 import java.awt.Rectangle;
14 import java.awt.Stroke;
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.event.ComponentListener;
20 import java.awt.event.MouseEvent;
21 import java.awt.event.MouseListener;
22 import java.awt.event.MouseMotionListener;
23 import java.awt.geom.AffineTransform;
24 import java.awt.image.BufferedImage;
25 import java.util.logging.Level;
26 import java.util.logging.Logger;
27 
28 import javax.swing.JComponent;
29 import javax.swing.JScrollBar;
30 
31 import com.mxgraph.util.mxEvent;
32 import com.mxgraph.util.mxEventObject;
33 import com.mxgraph.util.mxEventSource.mxIEventListener;
34 import com.mxgraph.util.mxPoint;
35 import com.mxgraph.util.mxRectangle;
36 import com.mxgraph.util.mxUtils;
37 import com.mxgraph.view.mxGraphView;
38 
39 /**
40  * An outline view for a specific graph component.
41  */
42 public class mxGraphOutline extends JComponent
43 {
44 
45 	private static final Logger log = Logger.getLogger(mxGraphOutline.class.getName());
46 
47 	/**
48 	 *
49 	 */
50 	private static final long serialVersionUID = -2521103946905154267L;
51 
52 	/**
53 	 *
54 	 */
55 	public static Color DEFAULT_ZOOMHANDLE_FILL = new Color(0, 255, 255);
56 
57 	/**
58 	 *
59 	 */
60 	protected mxGraphComponent graphComponent;
61 
62 	/**
63 	 * TODO: Not yet implemented.
64 	 */
65 	protected BufferedImage tripleBuffer;
66 
67 	/**
68 	 * Holds the graphics of the triple buffer.
69 	 */
70 	protected Graphics2D tripleBufferGraphics;
71 
72 	/**
73 	 * True if the triple buffer needs a full repaint.
74 	 */
75 	protected boolean repaintBuffer = false;
76 
77 	/**
78 	 * Clip of the triple buffer to be repainted.
79 	 */
80 	protected mxRectangle repaintClip = null;
81 
82 	/**
83 	 *
84 	 */
85 	protected boolean tripleBuffered = true;
86 
87 	/**
88 	 *
89 	 */
90 	protected Rectangle finderBounds = new Rectangle();
91 
92 	/**
93 	 *
94 	 */
95 	protected Point zoomHandleLocation = null;
96 
97 	/**
98 	 *
99 	 */
100 	protected boolean finderVisible = true;
101 
102 	/**
103 	 *
104 	 */
105 	protected boolean zoomHandleVisible = true;
106 
107 	/**
108 	 *
109 	 */
110 	protected boolean useScaledInstance = false;
111 
112 	/**
113 	 *
114 	 */
115 	protected boolean antiAlias = false;
116 
117 	/**
118 	 *
119 	 */
120 	protected boolean drawLabels = false;
121 
122 	/**
123 	 * Specifies if the outline should be zoomed to the page if the graph
124 	 * component is in page layout mode. Default is true.
125 	 */
126 	protected boolean fitPage = true;
127 
128 	/**
129 	 * Not yet implemented.
130 	 *
131 	 * Border to add around the page bounds if wholePage is true.
132 	 * Default is 4.
133 	 */
134 	protected int outlineBorder = 10;
135 
136 	/**
137 	 *
138 	 */
139 	protected MouseTracker tracker = new MouseTracker();
140 
141 	/**
142 	 *
143 	 */
144 	protected double scale = 1;
145 
146 	/**
147 	 *
148 	 */
149 	protected Point translate = new Point();
150 
151 	/**
152 	 *
153 	 */
154 	protected transient boolean zoomGesture = false;
155 
156 	/**
157 	 *
158 	 */
159 	protected mxIEventListener repaintHandler = new mxIEventListener()
160 	{
161 		public void invoke(Object source, mxEventObject evt)
162 		{
163 			updateScaleAndTranslate();
164 			mxRectangle dirty = (mxRectangle) evt.getProperty("region");
165 
166 			if (dirty != null)
167 			{
168 				repaintClip = new mxRectangle(dirty);
169 			}
170 			else
171 			{
172 				repaintBuffer = true;
173 			}
174 
175 			if (dirty != null)
176 			{
177 				updateFinder(true);
178 
179 				dirty.grow(1 / scale);
180 
181 				dirty.setX(dirty.getX() * scale + translate.x);
182 				dirty.setY(dirty.getY() * scale + translate.y);
183 				dirty.setWidth(dirty.getWidth() * scale);
184 				dirty.setHeight(dirty.getHeight() * scale);
185 
186 				repaint(dirty.getRectangle());
187 			}
188 			else
189 			{
190 				updateFinder(false);
191 				repaint();
192 			}
193 		}
194 	};
195 
196 	/**
197 	 *
198 	 */
199 	protected ComponentListener componentHandler = new ComponentAdapter()
200 	{
201 		public void componentResized(ComponentEvent e)
202 		{
203 			if (updateScaleAndTranslate())
204 			{
205 				repaintBuffer = true;
206 				updateFinder(false);
207 				repaint();
208 			}
209 			else
210 			{
211 				updateFinder(true);
212 			}
213 		}
214 	};
215 
216 	/**
217 	 *
218 	 */
219 	protected AdjustmentListener adjustmentHandler = new AdjustmentListener()
220 	{
221 
222 		/**
223 		 *
224 		 */
225 		public void adjustmentValueChanged(AdjustmentEvent e)
226 		{
227 			if (updateScaleAndTranslate())
228 			{
229 				repaintBuffer = true;
230 				updateFinder(false);
231 				repaint();
232 			}
233 			else
234 			{
235 				updateFinder(true);
236 			}
237 		}
238 
239 	};
240 
241 	/**
242 	 *
243 	 */
mxGraphOutline(mxGraphComponent graphComponent)244 	public mxGraphOutline(mxGraphComponent graphComponent)
245 	{
246 		addComponentListener(componentHandler);
247 		addMouseMotionListener(tracker);
248 		addMouseListener(tracker);
249 		setGraphComponent(graphComponent);
250 		setEnabled(true);
251 		setOpaque(true);
252 	}
253 
254 	/**
255 	 * Fires a property change event for <code>tripleBuffered</code>.
256 	 *
257 	 * @param tripleBuffered the tripleBuffered to set
258 	 */
setTripleBuffered(boolean tripleBuffered)259 	public void setTripleBuffered(boolean tripleBuffered)
260 	{
261 		boolean oldValue = this.tripleBuffered;
262 		this.tripleBuffered = tripleBuffered;
263 
264 		if (!tripleBuffered)
265 		{
266 			destroyTripleBuffer();
267 		}
268 
269 		firePropertyChange("tripleBuffered", oldValue, tripleBuffered);
270 	}
271 
272 	/**
273 	 *
274 	 */
isTripleBuffered()275 	public boolean isTripleBuffered()
276 	{
277 		return tripleBuffered;
278 	}
279 
280 	/**
281 	 * Fires a property change event for <code>drawLabels</code>.
282 	 *
283 	 * @param drawLabels the drawLabels to set
284 	 */
setDrawLabels(boolean drawLabels)285 	public void setDrawLabels(boolean drawLabels)
286 	{
287 		boolean oldValue = this.drawLabels;
288 		this.drawLabels = drawLabels;
289 		repaintTripleBuffer(null);
290 
291 		firePropertyChange("drawLabels", oldValue, drawLabels);
292 	}
293 
294 	/**
295 	 *
296 	 */
isDrawLabels()297 	public boolean isDrawLabels()
298 	{
299 		return drawLabels;
300 	}
301 
302 	/**
303 	 * Fires a property change event for <code>antiAlias</code>.
304 	 *
305 	 * @param antiAlias the antiAlias to set
306 	 */
setAntiAlias(boolean antiAlias)307 	public void setAntiAlias(boolean antiAlias)
308 	{
309 		boolean oldValue = this.antiAlias;
310 		this.antiAlias = antiAlias;
311 		repaintTripleBuffer(null);
312 
313 		firePropertyChange("antiAlias", oldValue, antiAlias);
314 	}
315 
316 	/**
317 	 * @return the antiAlias
318 	 */
isAntiAlias()319 	public boolean isAntiAlias()
320 	{
321 		return antiAlias;
322 	}
323 
324 	/**
325 	 *
326 	 */
setVisible(boolean visible)327 	public void setVisible(boolean visible)
328 	{
329 		super.setVisible(visible);
330 
331 		// Frees memory if the outline is hidden
332 		if (!visible)
333 		{
334 			destroyTripleBuffer();
335 		}
336 	}
337 
338 	/**
339 	 *
340 	 */
setFinderVisible(boolean visible)341 	public void setFinderVisible(boolean visible)
342 	{
343 		finderVisible = visible;
344 	}
345 
346 	/**
347 	 *
348 	 */
setZoomHandleVisible(boolean visible)349 	public void setZoomHandleVisible(boolean visible)
350 	{
351 		zoomHandleVisible = visible;
352 	}
353 
354 	/**
355 	 * Fires a property change event for <code>fitPage</code>.
356 	 *
357 	 * @param fitPage the fitPage to set
358 	 */
setFitPage(boolean fitPage)359 	public void setFitPage(boolean fitPage)
360 	{
361 		boolean oldValue = this.fitPage;
362 		this.fitPage = fitPage;
363 
364 		if (updateScaleAndTranslate())
365 		{
366 			repaintBuffer = true;
367 			updateFinder(false);
368 		}
369 
370 		firePropertyChange("fitPage", oldValue, fitPage);
371 	}
372 
373 	/**
374 	 *
375 	 */
isFitPage()376 	public boolean isFitPage()
377 	{
378 		return fitPage;
379 	}
380 
381 	/**
382 	 *
383 	 */
getGraphComponent()384 	public mxGraphComponent getGraphComponent()
385 	{
386 		return graphComponent;
387 	}
388 
389 	/**
390 	 * Fires a property change event for <code>graphComponent</code>.
391 	 *
392 	 * @param graphComponent the graphComponent to set
393 	 */
setGraphComponent(mxGraphComponent graphComponent)394 	public void setGraphComponent(mxGraphComponent graphComponent)
395 	{
396 		mxGraphComponent oldValue = this.graphComponent;
397 
398 		if (this.graphComponent != null)
399 		{
400 			this.graphComponent.getGraph().removeListener(repaintHandler);
401 			this.graphComponent.getGraphControl().removeComponentListener(
402 					componentHandler);
403 			this.graphComponent.getHorizontalScrollBar()
404 					.removeAdjustmentListener(adjustmentHandler);
405 			this.graphComponent.getVerticalScrollBar()
406 					.removeAdjustmentListener(adjustmentHandler);
407 		}
408 
409 		this.graphComponent = graphComponent;
410 
411 		if (this.graphComponent != null)
412 		{
413 			this.graphComponent.getGraph().addListener(mxEvent.REPAINT,
414 					repaintHandler);
415 			this.graphComponent.getGraphControl().addComponentListener(
416 					componentHandler);
417 			this.graphComponent.getHorizontalScrollBar().addAdjustmentListener(
418 					adjustmentHandler);
419 			this.graphComponent.getVerticalScrollBar().addAdjustmentListener(
420 					adjustmentHandler);
421 		}
422 
423 		if (updateScaleAndTranslate())
424 		{
425 			repaintBuffer = true;
426 			repaint();
427 		}
428 
429 		firePropertyChange("graphComponent", oldValue, graphComponent);
430 	}
431 
432 	/**
433 	 * Checks if the triple buffer exists and creates a new one if
434 	 * it does not. Also compares the size of the buffer with the
435 	 * size of the graph and drops the buffer if it has a
436 	 * different size.
437 	 */
checkTripleBuffer()438 	public void checkTripleBuffer()
439 	{
440 		if (tripleBuffer != null)
441 		{
442 			if (tripleBuffer.getWidth() != getWidth()
443 					|| tripleBuffer.getHeight() != getHeight())
444 			{
445 				// Resizes the buffer (destroys existing and creates new)
446 				destroyTripleBuffer();
447 			}
448 		}
449 
450 		if (tripleBuffer == null)
451 		{
452 			createTripleBuffer(getWidth(), getHeight());
453 		}
454 	}
455 
456 	/**
457 	 * Creates the tripleBufferGraphics and tripleBuffer for the given
458 	 * dimension and draws the complete graph onto the triplebuffer.
459 	 *
460 	 * @param width
461 	 * @param height
462 	 */
createTripleBuffer(int width, int height)463 	protected void createTripleBuffer(int width, int height)
464 	{
465 		try
466 		{
467 			tripleBuffer = mxUtils.createBufferedImage(width, height, null);
468 			tripleBufferGraphics = tripleBuffer.createGraphics();
469 
470 			// Repaints the complete buffer
471 			repaintTripleBuffer(null);
472 		}
473 		catch (OutOfMemoryError error)
474 		{
475 			log.log(Level.SEVERE, "Failed to create a triple buffer", error);
476 		}
477 	}
478 
479 	/**
480 	 * Destroys the tripleBuffer and tripleBufferGraphics objects.
481 	 */
destroyTripleBuffer()482 	public void destroyTripleBuffer()
483 	{
484 		if (tripleBuffer != null)
485 		{
486 			tripleBuffer = null;
487 			tripleBufferGraphics.dispose();
488 			tripleBufferGraphics = null;
489 		}
490 	}
491 
492 	/**
493 	 * Clears and repaints the triple buffer at the given rectangle or repaints
494 	 * the complete buffer if no rectangle is specified.
495 	 *
496 	 * @param clip
497 	 */
repaintTripleBuffer(Rectangle clip)498 	public void repaintTripleBuffer(Rectangle clip)
499 	{
500 		if (tripleBuffered && tripleBufferGraphics != null)
501 		{
502 			if (clip == null)
503 			{
504 				clip = new Rectangle(tripleBuffer.getWidth(),
505 						tripleBuffer.getHeight());
506 			}
507 
508 			// Clears and repaints the dirty rectangle using the
509 			// graphics canvas of the graph component as a renderer
510 			mxUtils.clearRect(tripleBufferGraphics, clip, null);
511 			tripleBufferGraphics.setClip(clip);
512 			paintGraph(tripleBufferGraphics);
513 			tripleBufferGraphics.setClip(null);
514 
515 			repaintBuffer = false;
516 			repaintClip = null;
517 		}
518 	}
519 
520 	/**
521 	 *
522 	 */
updateFinder(boolean repaint)523 	public void updateFinder(boolean repaint)
524 	{
525 		Rectangle rect = graphComponent.getViewport().getViewRect();
526 
527 		int x = (int) Math.round(rect.x * scale);
528 		int y = (int) Math.round(rect.y * scale);
529 		int w = (int) Math.round((rect.x + rect.width) * scale) - x;
530 		int h = (int) Math.round((rect.y + rect.height) * scale) - y;
531 
532 		updateFinderBounds(new Rectangle(x + translate.x, y + translate.y,
533 				w + 1, h + 1), repaint);
534 	}
535 
536 	/**
537 	 *
538 	 */
updateFinderBounds(Rectangle bounds, boolean repaint)539 	public void updateFinderBounds(Rectangle bounds, boolean repaint)
540 	{
541 		if (bounds != null && !bounds.equals(finderBounds))
542 		{
543 			Rectangle old = new Rectangle(finderBounds);
544 			finderBounds = bounds;
545 
546 			// LATER: Fix repaint region to be smaller
547 			if (repaint)
548 			{
549 				old = old.union(finderBounds);
550 				old.grow(3, 3);
551 				repaint(old);
552 			}
553 		}
554 	}
555 
556 	/**
557 	 *
558 	 */
paintComponent(Graphics g)559 	public void paintComponent(Graphics g)
560 	{
561 		super.paintComponent(g);
562 		paintBackground(g);
563 
564 		if (graphComponent != null)
565 		{
566 			// Creates or destroys the triple buffer as needed
567 			if (tripleBuffered)
568 			{
569 				checkTripleBuffer();
570 			}
571 			else if (tripleBuffer != null)
572 			{
573 				destroyTripleBuffer();
574 			}
575 
576 			// Updates the dirty region from the buffered graph image
577 			if (tripleBuffer != null)
578 			{
579 				if (repaintBuffer)
580 				{
581 					repaintTripleBuffer(null);
582 				}
583 				else if (repaintClip != null)
584 				{
585 					repaintClip.grow(1 / scale);
586 
587 					repaintClip.setX(repaintClip.getX() * scale + translate.x);
588 					repaintClip.setY(repaintClip.getY() * scale + translate.y);
589 					repaintClip.setWidth(repaintClip.getWidth() * scale);
590 					repaintClip.setHeight(repaintClip.getHeight() * scale);
591 
592 					repaintTripleBuffer(repaintClip.getRectangle());
593 				}
594 
595 				mxUtils.drawImageClip(g, tripleBuffer, this);
596 			}
597 
598 			// Paints the graph directly onto the graphics
599 			else
600 			{
601 				paintGraph(g);
602 			}
603 
604 			paintForeground(g);
605 		}
606 	}
607 
608 	/**
609 	 * Paints the background.
610 	 */
paintBackground(Graphics g)611 	protected void paintBackground(Graphics g)
612 	{
613 		if (graphComponent != null)
614 		{
615 			Graphics2D g2 = (Graphics2D) g;
616 			AffineTransform tx = g2.getTransform();
617 
618 			try
619 			{
620 				// Draws the background of the outline if a graph exists
621 				g.setColor(graphComponent.getPageBackgroundColor());
622 				mxUtils.fillClippedRect(g, 0, 0, getWidth(), getHeight());
623 
624 				g2.translate(translate.x, translate.y);
625 				g2.scale(scale, scale);
626 
627 				// Draws the scaled page background
628 				if (!graphComponent.isPageVisible())
629 				{
630 					Color bg = graphComponent.getBackground();
631 
632 					if (graphComponent.getViewport().isOpaque())
633 					{
634 						bg = graphComponent.getViewport().getBackground();
635 					}
636 
637 					g.setColor(bg);
638 					Dimension size = graphComponent.getGraphControl().getSize();
639 
640 					// Paints the background of the drawing surface
641 					mxUtils.fillClippedRect(g, 0, 0, size.width, size.height);
642 					g.setColor(g.getColor().darker().darker());
643 					g.drawRect(0, 0, size.width, size.height);
644 				}
645 				else
646 				{
647 					// Paints the page background using the graphics scaling
648 					graphComponent.paintBackgroundPage(g);
649 				}
650 			}
651 			finally
652 			{
653 				g2.setTransform(tx);
654 			}
655 		}
656 		else
657 		{
658 			// Draws the background of the outline if no graph exists
659 			g.setColor(getBackground());
660 			mxUtils.fillClippedRect(g, 0, 0, getWidth(), getHeight());
661 		}
662 	}
663 
664 	/**
665 	 * Paints the graph outline.
666 	 */
paintGraph(Graphics g)667 	public void paintGraph(Graphics g)
668 	{
669 		if (graphComponent != null)
670 		{
671 			Graphics2D g2 = (Graphics2D) g;
672 			AffineTransform tx = g2.getTransform();
673 
674 			try
675 			{
676 				Point tr = graphComponent.getGraphControl().getTranslate();
677 				g2.translate(translate.x + tr.getX() * scale,
678 						translate.y + tr.getY() * scale);
679 				g2.scale(scale, scale);
680 
681 				// Draws the scaled graph
682 				graphComponent.getGraphControl().drawGraph(g2, drawLabels);
683 			}
684 			finally
685 			{
686 				g2.setTransform(tx);
687 			}
688 		}
689 	}
690 
691 	/**
692 	 * Paints the foreground. Foreground is dynamic and should never be made
693 	 * part of the triple buffer. It is painted on top of the buffer.
694 	 */
paintForeground(Graphics g)695 	protected void paintForeground(Graphics g)
696 	{
697 		if (graphComponent != null)
698 		{
699 			Graphics2D g2 = (Graphics2D) g;
700 
701 			Stroke stroke = g2.getStroke();
702 			g.setColor(Color.BLUE);
703 			g2.setStroke(new BasicStroke(3));
704 			g.drawRect(finderBounds.x, finderBounds.y, finderBounds.width,
705 					finderBounds.height);
706 
707 			if (zoomHandleVisible)
708 			{
709 				g2.setStroke(stroke);
710 				g.setColor(DEFAULT_ZOOMHANDLE_FILL);
711 				g.fillRect(finderBounds.x + finderBounds.width - 6, finderBounds.y
712 						+ finderBounds.height - 6, 8, 8);
713 				g.setColor(Color.BLACK);
714 				g.drawRect(finderBounds.x + finderBounds.width - 6, finderBounds.y
715 						+ finderBounds.height - 6, 8, 8);
716 			}
717 		}
718 	}
719 
720 	/**
721 	 * Returns true if the scale or translate has changed.
722 	 */
updateScaleAndTranslate()723 	public boolean updateScaleAndTranslate()
724 	{
725 		double newScale = 1;
726 		int dx = 0;
727 		int dy = 0;
728 
729 		if (this.graphComponent != null)
730 		{
731 			Dimension graphSize = graphComponent.getGraphControl().getSize();
732 			Dimension outlineSize = getSize();
733 
734 			int gw = (int) graphSize.getWidth();
735 			int gh = (int) graphSize.getHeight();
736 
737 			if (gw > 0 && gh > 0)
738 			{
739 				boolean magnifyPage = graphComponent.isPageVisible()
740 						&& isFitPage()
741 						&& graphComponent.getHorizontalScrollBar().isVisible()
742 						&& graphComponent.getVerticalScrollBar().isVisible();
743 				double graphScale = graphComponent.getGraph().getView()
744 						.getScale();
745 				mxPoint trans = graphComponent.getGraph().getView()
746 						.getTranslate();
747 
748 				int w = (int) outlineSize.getWidth() - 2 * outlineBorder;
749 				int h = (int) outlineSize.getHeight() - 2 * outlineBorder;
750 
751 				if (magnifyPage)
752 				{
753 					gw -= 2 * Math.round(trans.getX() * graphScale);
754 					gh -= 2 * Math.round(trans.getY() * graphScale);
755 				}
756 
757 				newScale = Math.min((double) w / gw, (double) h / gh);
758 
759 				dx += (int) Math
760 						.round((outlineSize.getWidth() - gw * newScale) / 2);
761 				dy += (int) Math
762 						.round((outlineSize.getHeight() - gh * newScale) / 2);
763 
764 				if (magnifyPage)
765 				{
766 					dx -= Math.round(trans.getX() * newScale * graphScale);
767 					dy -= Math.round(trans.getY() * newScale * graphScale);
768 				}
769 			}
770 		}
771 
772 		if (newScale != scale || translate.x != dx || translate.y != dy)
773 		{
774 			scale = newScale;
775 			translate.setLocation(dx, dy);
776 
777 			return true;
778 		}
779 		else
780 		{
781 			return false;
782 		}
783 	}
784 
785 	/**
786 	 *
787 	 */
788 	public class MouseTracker implements MouseListener, MouseMotionListener
789 	{
790 		/**
791 		 *
792 		 */
793 		protected Point start = null;
794 
795 		/*
796 		 * (non-Javadoc)
797 		 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
798 		 */
mousePressed(MouseEvent e)799 		public void mousePressed(MouseEvent e)
800 		{
801 			zoomGesture = hitZoomHandle(e.getX(), e.getY());
802 
803 			if (graphComponent != null && !e.isConsumed()
804 					&& !e.isPopupTrigger()
805 					&& (finderBounds.contains(e.getPoint()) || zoomGesture))
806 			{
807 				start = e.getPoint();
808 			}
809 		}
810 
811 		/*
812 		 * (non-Javadoc)
813 		 * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
814 		 */
mouseDragged(MouseEvent e)815 		public void mouseDragged(MouseEvent e)
816 		{
817 			if (isEnabled() && start != null)
818 			{
819 				if (zoomGesture)
820 				{
821 					Rectangle bounds = graphComponent.getViewport()
822 							.getViewRect();
823 					double viewRatio = bounds.getWidth() / bounds.getHeight();
824 
825 					bounds = new Rectangle(finderBounds);
826 					bounds.width = (int) Math
827 							.max(0, (e.getX() - bounds.getX()));
828 					bounds.height = (int) Math.max(0,
829 							(bounds.getWidth() / viewRatio));
830 
831 					updateFinderBounds(bounds, true);
832 				}
833 				else
834 				{
835 					// TODO: To enable constrained moving, that is, moving
836 					// into only x- or y-direction when shift is pressed,
837 					// we need the location of the first mouse event, since
838 					// the movement can not be constrained for incremental
839 					// steps as used below.
840 					int dx = (int) ((e.getX() - start.getX()) / scale);
841 					int dy = (int) ((e.getY() - start.getY()) / scale);
842 
843 					// Keeps current location as start for delta movement
844 					// of the scrollbars
845 					start = e.getPoint();
846 
847 					graphComponent.getHorizontalScrollBar().setValue(
848 							graphComponent.getHorizontalScrollBar().getValue()
849 									+ dx);
850 					graphComponent.getVerticalScrollBar().setValue(
851 							graphComponent.getVerticalScrollBar().getValue()
852 									+ dy);
853 				}
854 			}
855 		}
856 
857 		/*
858 		 * (non-Javadoc)
859 		 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
860 		 */
mouseReleased(MouseEvent e)861 		public void mouseReleased(MouseEvent e)
862 		{
863 			if (start != null)
864 			{
865 				if (zoomGesture)
866 				{
867 					double dx = e.getX() - start.getX();
868 					double w = finderBounds.getWidth();
869 
870 					final JScrollBar hs = graphComponent
871 							.getHorizontalScrollBar();
872 					final double sx;
873 
874 					if (hs != null)
875 					{
876 						sx = (double) hs.getValue() / hs.getMaximum();
877 					}
878 					else
879 					{
880 						sx = 0;
881 					}
882 
883 					final JScrollBar vs = graphComponent.getVerticalScrollBar();
884 					final double sy;
885 
886 					if (vs != null)
887 					{
888 						sy = (double) vs.getValue() / vs.getMaximum();
889 					}
890 					else
891 					{
892 						sy = 0;
893 					}
894 
895 					mxGraphView view = graphComponent.getGraph().getView();
896 					double scale = view.getScale();
897 					double newScale = scale - (dx * scale) / w;
898 					double factor = newScale / scale;
899 					view.setScale(newScale);
900 
901 					if (hs != null)
902 					{
903 						hs.setValue((int) (sx * hs.getMaximum() * factor));
904 					}
905 
906 					if (vs != null)
907 					{
908 						vs.setValue((int) (sy * vs.getMaximum() * factor));
909 					}
910 				}
911 
912 				zoomGesture = false;
913 				start = null;
914 			}
915 		}
916 
917 		/**
918 		 *
919 		 */
hitZoomHandle(int x, int y)920 		public boolean hitZoomHandle(int x, int y)
921 		{
922 			return new Rectangle(finderBounds.x + finderBounds.width - 6,
923 					finderBounds.y + finderBounds.height - 6, 8, 8).contains(x,
924 					y);
925 		}
926 
927 		/*
928 		 * (non-Javadoc)
929 		 * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
930 		 */
mouseMoved(MouseEvent e)931 		public void mouseMoved(MouseEvent e)
932 		{
933 			if (hitZoomHandle(e.getX(), e.getY()))
934 			{
935 				setCursor(new Cursor(Cursor.HAND_CURSOR));
936 			}
937 			else if (finderBounds.contains(e.getPoint()))
938 			{
939 				setCursor(new Cursor(Cursor.MOVE_CURSOR));
940 			}
941 			else
942 			{
943 				setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
944 			}
945 		}
946 
947 		/*
948 		 * (non-Javadoc)
949 		 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
950 		 */
mouseClicked(MouseEvent e)951 		public void mouseClicked(MouseEvent e)
952 		{
953 			// ignore
954 		}
955 
956 		/*
957 		 * (non-Javadoc)
958 		 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
959 		 */
mouseEntered(MouseEvent e)960 		public void mouseEntered(MouseEvent e)
961 		{
962 			// ignore
963 		}
964 
965 		/*
966 		 * (non-Javadoc)
967 		 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
968 		 */
mouseExited(MouseEvent e)969 		public void mouseExited(MouseEvent e)
970 		{
971 			// ignore
972 		}
973 
974 	}
975 
976 }
977