View Javadoc

1   package net.sourceforge.jpotpourri.gui.flexi;
2   
3   /* 
4    * The contents of this file are subject to the terms of the Common Development and Distribution License (the License).
5    * You may not use this file except in compliance with the License.
6    * You can obtain a copy of the License at http://www.netbeans.org/cddl.html or http://www.netbeans.org/cddl.txt.
7    * When distributing Covered Code, include this CDDL Header Notice in each file and include the License file at
8    * http://www.netbeans.org/cddl.txt.
9    * If applicable, add the following below the CDDL Header,
10   * with the fields enclosed by brackets [] replaced by your own identifying information:
11   * 
12   * "Portions Copyrighted [year] [name of copyright owner]"
13   * 
14   * Copyright 2006 Sun Microsystems, all rights reserved.
15   */
16  
17  import java.awt.Color;
18  
19  import java.awt.Component;
20  
21  import java.awt.Dimension;
22  
23  import java.awt.Graphics;
24  
25  import java.awt.Graphics2D;
26  
27  import java.awt.Insets;
28  
29  import java.awt.KeyboardFocusManager;
30  
31  import java.awt.Point;
32  
33  import java.awt.Rectangle;
34  
35  import java.awt.event.ComponentEvent;
36  
37  import java.awt.event.ComponentListener;
38  
39  import java.awt.event.HierarchyBoundsListener;
40  
41  import java.awt.event.HierarchyEvent;
42  
43  import java.awt.event.HierarchyListener;
44  
45  import java.awt.event.MouseAdapter;
46  
47  import java.awt.event.MouseEvent;
48  
49  import java.awt.event.MouseMotionListener;
50  
51  import java.awt.geom.AffineTransform;
52  
53  import java.awt.image.BufferedImage;
54  
55  import java.beans.PropertyChangeEvent;
56  
57  import java.beans.PropertyChangeListener;
58  
59  import javax.swing.JComponent;
60  
61  import javax.swing.JList;
62  
63  import javax.swing.JScrollPane;
64  
65  import javax.swing.JTree;
66  
67  import javax.swing.ListCellRenderer;
68  
69  import javax.swing.Popup;
70  
71  import javax.swing.PopupFactory;
72  
73  import javax.swing.SwingUtilities;
74  
75  import javax.swing.border.Border;
76  
77  import javax.swing.event.ChangeEvent;
78  
79  import javax.swing.event.ChangeListener;
80  
81  import javax.swing.event.ListDataEvent;
82  
83  import javax.swing.event.ListDataListener;
84  
85  import javax.swing.event.ListSelectionEvent;
86  
87  import javax.swing.event.ListSelectionListener;
88  
89  import javax.swing.event.TreeModelEvent;
90  
91  import javax.swing.event.TreeModelListener;
92  
93  import javax.swing.event.TreeSelectionEvent;
94  
95  import javax.swing.event.TreeSelectionListener;
96  
97  import javax.swing.tree.TreePath;
98  
99  /**
100  * Displays pseudo-tooltips for tree and list views which don't have enough
101  * space.  This class is not NB specific, and can be used with any
102  * JTree or JList.
103  *
104  * @author Tim Boudreau; http://javabyexample.wisdomplug.com/java-concepts/
105  * 				34-core-java/59-tips-and-tricks-for-jtree-jlist-and-jcombobox-part-i.html
106  * @modified Vimal
107  */
108 public final class ViewTooltips extends MouseAdapter implements MouseMotionListener {
109 		
110 	// FIXME use this gui util for all jpot lists/trees!
111 	// ... continous tips could also be usefull for ColumnLimitedTextFields
112 	
113 	// TODO enable ViewTooltips for JTable also
114 
115 	
116 	/** The default instance, reference counted */
117 	private static ViewTooltips instance = null;
118 
119 	/** A reference count for number of comps listened to */
120 	private int refcount = 0;
121 
122 	/** The last known component we were invoked against, nulled on hide() */
123 	private JComponent inner = null;
124 
125 	/** The last row we were invoked against */
126 	private int row = -1;
127 
128 	/** An array of currently visible popups */
129 	private Popup[] popups = new Popup[2];
130 
131 	/** A component we'll reuse to paint into the popups */
132 	private ImgComp painter = new ImgComp();
133 
134 	/** Nobody should instantiate this */
135 	private ViewTooltips() {
136 		// singleton
137 	}
138 
139 	/**
140 	 * Register a child of a JScrollPane (only JList or JTree supported 
141 	 * for now) which should show helper tooltips.  Should be called
142 	 * from the component's addNotify() method.
143 	 */
144 	public static void register(final JList list) {
145 		registerSafeType(list);
146 	}
147 	
148 	public static void register(final JTree tree) {
149 		registerSafeType(tree);
150 	}
151 	
152 	private static void registerSafeType(final JComponent comp) {
153 		if (instance == null) {
154 			instance = new ViewTooltips();
155 		}
156 		instance.attachTo(comp);
157 	}
158 	
159 	/**
160 	 * Unregister a child of a JScrollPane (only JList or JTree supported 
161 	 * for now) which should show helper tooltips. Should be called
162 	 * from the component's removeNotify() method.
163 	 */
164 	public static void unregister(final JComponent comp) {
165 		assert instance != null : "Unregister asymmetrically called";
166 		if (null != instance && instance.detachFrom(comp) == 0) {
167 			instance.hide();
168 			instance = null;
169 		}
170 	}
171 
172 	/** Start listening to mouse motion on the passed component */
173 	private void attachTo(final JComponent comp) {
174 		assert comp instanceof JTree || comp instanceof JList;
175 		comp.addMouseListener(this);
176 		comp.addMouseMotionListener(this);
177 		refcount++;
178 	}
179 
180 	/** Stop listening to mouse motion on the passed component */
181 	private int detachFrom(final JComponent comp) {
182 		assert comp instanceof JTree || comp instanceof JList;
183 		comp.removeMouseMotionListener(this);
184 		comp.removeMouseListener(this);
185 		return refcount--;
186 	}
187 
188 	public void mouseMoved(final MouseEvent e) {
189 		Point p = e.getPoint();
190 		JComponent comp = (JComponent) e.getSource();
191 		JScrollPane jsp = (JScrollPane)
192 		SwingUtilities.getAncestorOfClass(JScrollPane.class, comp);
193 
194 		if (jsp != null) {
195 			p = SwingUtilities.convertPoint(comp, p, jsp);
196 			show(jsp, p);
197 		}
198 	}
199 
200 	public void mouseDragged(final MouseEvent e) {
201 		hide();
202 	}
203 
204 	public void mouseEntered(final MouseEvent e) {
205 		hide();
206 	}
207 
208 	public void mouseExited(final MouseEvent e) {
209 		hide();
210 	}
211 
212 	/** Shows the appropriate popups given the state of the scroll pane and
213 	 * its view. 
214 	 * @param view The scroll pane owning the component the event happened on
215 	 * @param pt The point at which the mouse event happened, in the coordinate
216 	 *  space of the scroll pane.
217 	 */
218 	void show(final JScrollPane view, final Point pt) {
219 		if (view.getViewport().getView() instanceof JTree) {
220 			showJTree(view, pt);
221 		} else if (view.getViewport().getView() instanceof JList) {
222 			showJList(view, pt);
223 		} else {
224 			assert false : "Bad component type registered: "
225 					+ view.getViewport().getView();
226 		}
227 	}
228 
229 	private void showJList(final JScrollPane view, final Point pt) {
230 		JList list = (JList) view.getViewport().getView();
231 		Point p = SwingUtilities.convertPoint(view, pt.x, pt.y, list);
232 		int listRow = list.locationToIndex(p);
233 
234 		if (listRow == -1) {
235 			hide();
236 			return;
237 		}
238 
239 		Rectangle bds = list.getCellBounds(listRow, listRow);
240 		
241 		//GetCellBounds returns a width that is the
242 		//full component width;  we want only what
243 		//the renderer really needs.
244 
245 		ListCellRenderer ren = list.getCellRenderer();
246 		Dimension rendererSize =
247 		ren.getListCellRendererComponent(list,
248 		list.getModel().getElementAt(listRow),
249 		listRow, false, false).getPreferredSize();
250 		bds.width = rendererSize.width;
251 
252 		if (bds == null || !bds.contains(p)) {
253 			hide();
254 			return;
255 		}
256 
257 		if (setCompAndRow(list, listRow)) {
258 			Rectangle visible = getShowingRect(view);
259 			Rectangle[] rects = getRects(bds, visible);
260 
261 			if (rects.length > 0) {
262 				ensureOldPopupsHidden();
263 				painter.configure(list.getModel().getElementAt(listRow),
264 				view, list, listRow);
265 				showPopups(rects, bds, visible, list, view);
266 
267 			} else {
268 				hide();
269 			}
270 		}
271 	}
272 
273 	private void showJTree(final JScrollPane view, final Point pt) {
274 		JTree tree = (JTree) view.getViewport().getView();
275 		Point p = SwingUtilities.convertPoint(view, pt.x, pt.y, tree);
276 		final int closestRow = tree.getClosestRowForLocation(p.x, p.y);
277 		TreePath path = tree.getClosestPathForLocation(p.x, p.y);
278 		Rectangle bds = tree.getPathBounds(path);
279 
280 		if (bds == null || !bds.contains(p)) {
281 			hide();
282 			return;
283 		}
284 
285 		if (setCompAndRow(tree, closestRow)) {
286 			Rectangle visible = getShowingRect(view);
287 			Rectangle[] rects = getRects(bds, visible);
288 
289 			if (rects.length > 0) {
290 				ensureOldPopupsHidden();
291 				painter.configure(path.getLastPathComponent(),
292 				view, tree, path, closestRow);
293 				showPopups(rects, bds, visible, tree, view);
294 			} else {
295 				hide();
296 			}
297 		}
298 	}
299 
300 	/**
301 	 * Set the currently shown component and row, returning true if they are
302 	 * not the same as the last known values.
303 	 */
304 	private boolean setCompAndRow(final JComponent newInner, final int newRow) {
305 		boolean rowChanged = newRow != this.row;
306 		boolean compChanged = newInner != this.inner;
307 		this.inner = newInner;
308 		this.row = newRow;
309 		return (rowChanged || compChanged);
310 	}
311 
312 	/**
313 	 * Hide all popups and discard any references to the components the
314 	 * popups were showing for.
315 	 */
316 	void hide() {
317 		ensureOldPopupsHidden();
318 		if (painter != null) {
319 			painter.clear();
320 		}
321 
322 		setHideComponent(null, null);
323 		inner = null;
324 		row = -1;
325 	}
326 
327 	private void ensureOldPopupsHidden() {
328 		for (int i = 0; i < popups.length; i++) {
329 			if (popups[i] != null) {
330 				popups[i].hide();
331 				popups[i] = null;
332 			}
333 		}
334 	}
335 
336 	/**
337 	 * Gets the sub-rectangle of a JScrollPane's area that
338 	 * is actually showing the view
339 	 */
340 	private Rectangle getShowingRect(final JScrollPane pane) {
341 		final Insets ins1 = pane.getViewport().getInsets();
342 		final Border border = pane.getViewportBorder();
343 		
344 		final Insets ins2;
345 		if(border != null) {
346 			ins2 = border.getBorderInsets(pane);
347 		} else {
348 			ins2 = new Insets(0, 0, 0, 0);
349 		}
350 
351 		Insets ins3 = new Insets(0, 0, 0, 0);
352 		if(pane.getBorder() != null) {
353 			ins3 = pane.getBorder().getBorderInsets(pane);
354 		}
355 
356 		final Rectangle r = pane.getViewportBorderBounds();
357 		r.translate(-r.x, -r.y);
358 		r.width -= ins1.left + ins1.right;
359 		r.width -= ins2.left + ins2.right;
360 		r.height -= ins1.top + ins1.bottom;
361 		r.height -= ins2.top + ins2.bottom;
362 		r.x -= ins2.left;
363 		r.x -= ins3.left;
364 		
365 		final Point p = pane.getViewport().getViewPosition();
366 		r.translate(p.x, p.y);
367 
368 		return SwingUtilities.convertRectangle(pane.getViewport(), r, pane);
369 	}
370 
371 	/**
372 	 * Fetches an array or rectangles representing the non-overlapping
373 	 * portions of a cell rect against the visible portion of the component.
374 	 * @bds The cell's bounds, in the coordinate space of the tree or list
375 	 * @vis The visible area of the tree or list, in the tree or list's coordinate space
376 	 */
377 	private static Rectangle[] getRects(final Rectangle bds, final Rectangle vis) {
378 		Rectangle[] result;
379 
380 		if (vis.contains(bds)) {
381 			result = new Rectangle[0];
382 		} else {
383 
384 			if (bds.x < vis.x && bds.x + bds.width > vis.x + vis.width) {
385 				Rectangle a = new Rectangle(bds.x, bds.y, vis.x - bds.x, bds.height);
386 				Rectangle b = new Rectangle(vis.x + vis.width, bds.y,
387 						(bds.x + bds.width) - (vis.x + vis.width), bds.height);
388 				result = new Rectangle[] { a, b };
389 
390 			} else if (bds.x < vis.x) {
391 				result = new Rectangle[] {
392 						new Rectangle(bds.x, bds.y, vis.x - bds.x, bds.height)
393 				};
394 
395 			} else if (bds.x + bds.width > vis.x + vis.width) {
396 				result = new Rectangle[] {
397 				new Rectangle(vis.x + vis.width, bds.y, (bds.x + bds.width) - (vis.x + vis.width), bds.height)
398 				};
399 			} else {
400 				result = new Rectangle[0];
401 			}
402 		}
403 
404 //		for (int i = 0; i < result.length; i++) { // MINOR was war denn das hier?!?!
405 //
406 //		}
407 		return result;
408 	}
409 
410 	/**
411 	 * Show popups for each rectangle, using the now configured painter.
412 	 */
413 	private void showPopups(
414 			final Rectangle[] rects,
415 			final Rectangle bds,
416 			final Rectangle visible,
417 			final JComponent comp,
418 			final JScrollPane view
419 			) {
420 		boolean shown = false;
421 
422 		for (int i = 0; i < rects.length; i++) {
423 			Rectangle sect = rects[i];
424 			sect.translate(-bds.x, -bds.y);
425 			ImgComp part = painter.getPartial(sect, bds.x + rects[i].x < visible.x);
426 			Point pos = new Point(bds.x + rects[i].x, bds.y + rects[i].y);
427 			SwingUtilities.convertPointToScreen(pos, comp);
428 
429 			if (comp instanceof JList) {
430 				//XXX off by one somewhere, only with JLists - where?
431 				pos.y--;
432 			}
433 
434 			if (pos.x > 0) { //Mac OS will reposition off-screen popups to x=0,
435 				//so don't try to show them
436 				popups[i] = getPopupFactory().getPopup(view,
437 				part, pos.x, pos.y);
438 				popups[i].show();
439 				shown = true;
440 			}
441 		}
442 
443 		if (shown) {
444 			setHideComponent(comp, view);
445 		} else {
446 			setHideComponent(null, null); //clear references
447 		}
448 	}
449 	
450 	private static PopupFactory getPopupFactory() {
451 		//        if ((Utilities.getOperatingSystem() & Utilities.OS_MAC) != 0 ) {
452 		//            
453 		//            // See ide/applemenu/src/org/netbeans/modules/applemenu/ApplePopupFactory
454 		//            // We have a custom PopupFactory that will consistently use 
455 		//            // lightweight popups on Mac OS, since HW popups get a drop
456 		//            // shadow.  By default, popups returned when a heavyweight popup
457 		//            // is needed (SDI mode) are no-op popups, since some hacks
458 		//            // are necessary to make it really work.
459 		//            
460 		//            // To enable heavyweight popups which have no drop shadow
461 		//            // *most* of the time on mac os, run with
462 		//            // -J-Dnb.explorer.hw.completions=true
463 		//            
464 		//            // To enable heavyweight popups which have no drop shadow 
465 		//            // *ever* on mac os, you need to put the cocoa classes on the
466 		//            // classpath - modify netbeans.conf to add 
467 		//            // System/Library/Java on the bootclasspath.  *Then*
468 		//            // run with the above line switch and 
469 		//            // -J-Dnb.explorer.hw.cocoahack=true
470 		//            
471 		//            PopupFactory result = (PopupFactory) Lookup.getDefault().lookup (
472 		//                    PopupFactory.class);
473 		//            return result == null ? PopupFactory.getSharedInstance() : result;
474 		//        } else {
475 		return PopupFactory.getSharedInstance();
476 		//        }
477 	}
478 
479 	private Hider hider = null;
480 	
481 	/**
482 	 * Set a component (JList or JTree) which should be listened to, such that if
483 	 * a model, selection or scroll event occurs, all currently open popups
484 	 * should be hidden.
485 	 */
486 	private void setHideComponent(final JComponent comp, final JScrollPane parent) {
487 		if (hider != null) {
488 			if (hider.isListeningTo(comp)) {
489 				return;
490 			}
491 		}
492 		
493 		if (hider != null) {
494 			hider.detach();
495 		}
496 		
497 		if (comp != null) {
498 			hider = new Hider(comp, parent);
499 		} else {
500 			hider = null;
501 		}
502 	}
503 
504 	/**
505 	 * A JComponent which creates a BufferedImage of a cell renderer and can
506 	 * produce clones of itself that display subrectangles of that cell
507 	 * renderer.
508 	 */
509 	private static final class ImgComp extends JComponent {
510 
511 		private static final long serialVersionUID = -3812447265015213647L;
512 
513 		private BufferedImage img;
514 
515 		private Dimension d = null;
516 
517 		private Color bg = Color.WHITE;
518 
519 //		private JScrollPane comp = null;
520 
521 //		private Object node = null;
522 
523 		private AffineTransform at = AffineTransform.getTranslateInstance(0d, 0d);
524 
525 		private boolean isRight = false;
526 
527 		ImgComp() {
528 			// nothing to do
529 		}
530 
531 		/**
532 		 * Create a clone with a specified backing image
533 		 */
534 		ImgComp(final BufferedImage img, final Rectangle off, final boolean right) {
535 			this.img = img;
536 			at = AffineTransform.getTranslateInstance(-off.x, 0);
537 			d = new Dimension(off.width, off.height);
538 			isRight = right;
539 		}
540 
541 		public ImgComp getPartial(final Rectangle bds, final boolean right) {
542 			assert img != null;
543 			return new ImgComp(img, bds, right);
544 		}
545 
546 		/** Configures a tree cell renderer and sets up sizing and the 
547 		 * backing image from it */
548 		public boolean configure(
549 				final Object nd,
550 				@SuppressWarnings("unused") final JScrollPane tv,
551 				final JTree tree,
552 				final TreePath path,
553 				final int row
554 				) {
555 //			boolean sameVn = setLastRendereredObject(nd);
556 //			boolean sameComp = setLastRenderedScrollPane(tv);
557 			Component renderer = null;
558 			
559 			bg = tree.getBackground();
560 			boolean sel = tree.isSelectionEmpty() ? false :
561 			tree.getSelectionModel().isPathSelected(path);
562 
563 			boolean exp = tree.isExpanded(path);
564 			boolean leaf = !exp && tree.getModel().isLeaf(nd);
565 			boolean lead = path.equals(tree.getSelectionModel().getLeadSelectionPath());
566 
567 			renderer = tree.getCellRenderer().getTreeCellRendererComponent(tree, nd, sel, exp, leaf, row, lead);
568 			if (renderer != null) {
569 				setComponent(renderer);
570 			}
571 			return true;
572 		}
573 
574 		/** Configures a list cell renderer and sets up sizing and the 
575 		 * backing image from it */
576 		public boolean configure(
577 				final Object nd,
578 				@SuppressWarnings("unused") final JScrollPane tv,
579 				final JList list,
580 				final int row
581 				) {
582 //			boolean sameVn = setLastRendereredObject(nd);
583 //			boolean sameComp = setLastRenderedScrollPane(tv);
584 			Component renderer = null;
585 			bg = list.getBackground();
586 
587 			boolean sel = list.isSelectionEmpty() ? false :
588 			list.getSelectionModel().isSelectedIndex(row);
589 			renderer = list.getCellRenderer().getListCellRendererComponent(list, nd, row, sel, false);
590 			if (renderer != null) {
591 				setComponent(renderer);
592 			}
593 			return true;
594 		}
595 
596 //		private boolean setLastRenderedScrollPane(final JScrollPane comp) {
597 //			boolean result = comp != this.comp;
598 //			this.comp = comp;
599 //			return result;
600 //		}
601 //
602 //		private boolean setLastRendereredObject(final Object nd) {
603 //			boolean result = node != nd;
604 //			if (result) {
605 //				node = nd;
606 //			}
607 //			return result;
608 //		}
609 
610 		void clear() {
611 //			comp = null;
612 //			node = null;
613 		}
614 
615 		/**
616 		 * Set the cell renderer we will proxy.
617 		 */
618 		public void setComponent(final Component jc) {
619 			final Dimension prefDim = jc.getPreferredSize();
620 			BufferedImage nue = new BufferedImage(prefDim.width, prefDim.height + 2,
621 			BufferedImage.TYPE_INT_ARGB_PRE);
622 			SwingUtilities.paintComponent(nue.getGraphics(), jc, this, 0, 0, prefDim.width, prefDim.height + 2);
623 			setImg(nue);
624 		}
625 
626 		public Rectangle getBounds() {
627 			Dimension dd = getPreferredSize();
628 			return new Rectangle(0, 0, dd.width, dd.height);
629 		}
630 
631 		private void setImg(final BufferedImage img) {
632 			this.img = img;
633 			d = null;
634 		}
635 
636 		public Dimension getPreferredSize() {
637 			if (d == null) {
638 				d = new Dimension(img.getWidth(), img.getHeight());
639 			}
640 			return d;
641 		}
642 
643 		public Dimension getSize() {
644 			return getPreferredSize();
645 		}
646 
647 		public void paint(final Graphics g) {
648 			g.setColor(bg);
649 			g.fillRect(0, 0, d.width, d.height);
650 			Graphics2D g2d = (Graphics2D) g;
651 			g2d.drawRenderedImage(img, at);
652 			g.setColor(Color.GRAY);
653 			g.drawLine(0, 0, d.width, 0);
654 			g.drawLine(0, d.height - 1, d.width, d.height - 1);
655 
656 			if (isRight) {
657 				g.drawLine(0, 0, 0, d.height - 1);
658 			} else {
659 				g.drawLine(d.width - 1, 0, d.width - 1, d.height - 1);
660 			}
661 		}
662 
663 		public void firePropertyChange(final String s, final Object a, final Object b) {
664 			// nothing to do
665 		}
666 
667 		public void invalidate() {
668 			// nothing to do
669 		}
670 
671 		public void validate() {
672 			// nothing to do
673 		}
674 
675 		public void revalidate() {
676 			// nothing to do
677 		}
678 	}
679 
680 	/**
681 	 * A listener that listens to just about everything in the known universe
682 	 * and hides all currently displayed popups if anything happens.
683 	 */
684 	private static final class Hider implements ChangeListener,
685 			PropertyChangeListener, TreeModelListener, TreeSelectionListener,
686 			HierarchyListener, HierarchyBoundsListener, ListSelectionListener,
687 			ListDataListener, ComponentListener {
688 
689 		private final JTree tree;
690 
691 		private JScrollPane pane;
692 
693 		private final JList list;
694 
695 		private boolean detached = false;
696 		
697 		public Hider(final JComponent comp, final JScrollPane pane) {
698 			if (comp instanceof JTree) {
699 				this.tree = (JTree) comp;
700 				this.list = null;
701 			} else {
702 				this.list = (JList) comp;
703 				this.tree = null;
704 			}
705 
706 			assert tree != null || list != null;
707 			this.pane = pane;
708 			attach();
709 		}
710 
711 		private boolean isListeningTo(final JComponent comp) {
712 			return !detached && (comp == list || comp == tree);
713 		}
714 
715 		private void attach() {
716 			if (tree != null) {
717 				tree.getModel().addTreeModelListener(this);
718 				tree.getSelectionModel().addTreeSelectionListener(this);
719 				tree.addHierarchyBoundsListener(this);
720 				tree.addHierarchyListener(this);
721 				tree.addComponentListener(this);
722 			} else {
723 				list.getSelectionModel().addListSelectionListener(this);
724 				list.getModel().addListDataListener(this);
725 				list.addHierarchyBoundsListener(this);
726 				list.addHierarchyListener(this);
727 				list.addComponentListener(this);
728 			}
729 
730 			if (null != pane.getHorizontalScrollBar()) {
731 				pane.getHorizontalScrollBar().getModel().addChangeListener(this);
732 			}
733 
734 			if (null != pane.getVerticalScrollBar()) {
735 				pane.getVerticalScrollBar().getModel().addChangeListener(this);
736 			}
737 			KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(this);
738 		}
739 
740 		
741 
742 		private void detach() {
743 			KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener(this);
744 
745 			if (tree != null) {
746 				tree.getSelectionModel().removeTreeSelectionListener(this);
747 				tree.getModel().removeTreeModelListener(this);
748 				tree.removeHierarchyBoundsListener(this);
749 				tree.removeHierarchyListener(this);
750 				tree.removeComponentListener(this);
751 			} else {
752 				list.getSelectionModel().removeListSelectionListener(this);
753 				list.getModel().removeListDataListener(this);
754 				list.removeHierarchyBoundsListener(this);
755 				list.removeHierarchyListener(this);
756 				list.removeComponentListener(this);
757 			}
758 
759 			if (null != pane.getHorizontalScrollBar()) {
760 				pane.getHorizontalScrollBar().getModel().removeChangeListener(this);
761 			}
762 
763 			if (null != pane.getVerticalScrollBar()) {
764 				pane.getVerticalScrollBar().getModel().removeChangeListener(this);
765 			}
766 			detached = true;
767 		}
768 
769 		private void change() {
770 			if (ViewTooltips.instance != null) {
771 				ViewTooltips.instance.hide();
772 			}
773 			detach();
774 		}
775 
776 		public void propertyChange(final PropertyChangeEvent evt) {
777 			change();
778 		}
779 
780 		public void treeNodesChanged(final TreeModelEvent e) {
781 			change();
782 		}
783 
784 		public void treeNodesInserted(final TreeModelEvent e) {
785 			change();
786 		}
787 
788 		public void treeNodesRemoved(final TreeModelEvent e) {
789 			change();
790 		}
791 
792 		public void treeStructureChanged(final TreeModelEvent e) {
793 			change();
794 		}
795 
796 		public void hierarchyChanged(final HierarchyEvent e) {
797 			change();
798 		}
799 
800 		public void valueChanged(final TreeSelectionEvent e) {
801 			change();
802 		}
803 
804 		public void ancestorMoved(final HierarchyEvent e) {
805 			change();
806 		}
807 
808 		public void ancestorResized(final HierarchyEvent e) {
809 			change();
810 		}
811 
812 		public void stateChanged(final ChangeEvent e) {
813 			change();
814 		}
815 
816 		public void valueChanged(final ListSelectionEvent e) {
817 			change();
818 		}
819 
820 		public void intervalAdded(final ListDataEvent e) {
821 			change();
822 		}
823 
824 		public void intervalRemoved(final ListDataEvent e) {
825 			change();
826 		}
827 
828 		public void contentsChanged(final ListDataEvent e) {
829 			change();
830 		}
831 
832 		public void componentResized(final ComponentEvent e) {
833 			change();
834 		}
835 
836 		public void componentMoved(final ComponentEvent e) {
837 			change();
838 		}
839 		
840 		public void componentShown(final ComponentEvent e) {
841 			change();
842 		}
843 		
844 		public void componentHidden(final ComponentEvent e) {
845 			change();
846 		}
847 	}
848 }