1 19 20 package org.openide.explorer.view; 21 22 import java.awt.Color ; 23 import java.awt.Component ; 24 import java.awt.Dimension ; 25 import java.awt.Graphics ; 26 import java.awt.Graphics2D ; 27 import java.awt.Insets ; 28 import java.awt.KeyboardFocusManager ; 29 import java.awt.Point ; 30 import java.awt.Rectangle ; 31 import java.awt.event.ComponentEvent ; 32 import java.awt.event.ComponentListener ; 33 import java.awt.event.HierarchyBoundsListener ; 34 import java.awt.event.HierarchyEvent ; 35 import java.awt.event.HierarchyListener ; 36 import java.awt.event.MouseAdapter ; 37 import java.awt.event.MouseEvent ; 38 import java.awt.event.MouseMotionListener ; 39 import java.awt.geom.AffineTransform ; 40 import java.awt.image.BufferedImage ; 41 import java.beans.PropertyChangeEvent ; 42 import java.beans.PropertyChangeListener ; 43 import javax.swing.JComponent ; 44 import javax.swing.JList ; 45 import javax.swing.JScrollPane ; 46 import javax.swing.JTree ; 47 import javax.swing.ListCellRenderer ; 48 import javax.swing.Popup ; 49 import javax.swing.PopupFactory ; 50 import javax.swing.SwingUtilities ; 51 import javax.swing.border.Border ; 52 import javax.swing.event.ChangeEvent ; 53 import javax.swing.event.ChangeListener ; 54 import javax.swing.event.ListDataEvent ; 55 import javax.swing.event.ListDataListener ; 56 import javax.swing.event.ListSelectionEvent ; 57 import javax.swing.event.ListSelectionListener ; 58 import javax.swing.event.TreeModelEvent ; 59 import javax.swing.event.TreeModelListener ; 60 import javax.swing.event.TreeSelectionEvent ; 61 import javax.swing.event.TreeSelectionListener ; 62 import javax.swing.tree.TreePath ; 63 import org.openide.util.Lookup; 64 import org.openide.util.Utilities; 65 66 73 final class ViewTooltips extends MouseAdapter implements MouseMotionListener { 74 75 private static ViewTooltips INSTANCE = null; 76 77 private int refcount = 0; 78 79 private JComponent inner = null; 80 81 private int row = -1; 82 83 private Popup [] popups = new Popup [2]; 84 85 private ImgComp painter = new ImgComp(); 86 87 private ViewTooltips() { 88 } 89 90 95 static void register (JComponent comp) { 96 if (INSTANCE == null) { 97 INSTANCE = new ViewTooltips(); 98 } 99 INSTANCE.attachTo (comp); 100 } 101 102 107 static void unregister (JComponent comp) { 108 assert INSTANCE != null : "Unregister asymmetrically called"; 109 if (INSTANCE.detachFrom (comp) == 0) { 110 INSTANCE.hide(); 111 INSTANCE = null; 112 } 113 } 114 115 116 private void attachTo (JComponent comp) { 117 assert comp instanceof JTree || comp instanceof JList ; 118 comp.addMouseListener (this); 119 comp.addMouseMotionListener (this); 120 refcount++; 121 } 122 123 124 private int detachFrom (JComponent comp) { 125 assert comp instanceof JTree || comp instanceof JList ; 126 comp.removeMouseMotionListener (this); 127 comp.removeMouseListener (this); 128 return refcount--; 129 } 130 131 public void mouseMoved(MouseEvent e) { 132 Point p = e.getPoint(); 133 JComponent comp = (JComponent ) e.getSource(); 134 JScrollPane jsp = (JScrollPane ) 135 SwingUtilities.getAncestorOfClass(JScrollPane .class, comp); 136 if (jsp != null) { 137 p = SwingUtilities.convertPoint (comp, p, jsp); 138 show(jsp, p); 139 } 140 } 141 142 public void mouseDragged(MouseEvent e) { 143 hide(); 144 } 145 146 public void mouseEntered(MouseEvent e) { 147 hide(); 148 } 149 150 public void mouseExited(MouseEvent e) { 151 hide(); 152 } 153 154 160 void show (JScrollPane view, Point pt) { 161 if (view.getViewport().getView() instanceof JTree ) { 162 showJTree (view, pt); 163 } else if (view.getViewport().getView() instanceof JList ) { 164 showJList (view, pt); 165 } else { 166 assert false : "Bad component type registered: " + view.getViewport().getView(); 167 } 168 } 169 170 private void showJList (JScrollPane view, Point pt) { 171 JList list = (JList ) view.getViewport().getView(); 172 Point p = SwingUtilities.convertPoint(view, pt.x, pt.y, list); 173 int row = list.locationToIndex(p); 174 if (row == -1) { 175 hide(); 176 return; 177 } 178 Rectangle bds = list.getCellBounds(row, 179 row); 180 ListCellRenderer ren = list.getCellRenderer(); 184 Dimension rendererSize = 185 ren.getListCellRendererComponent(list, 186 list.getModel().getElementAt(row), 187 row, false, false).getPreferredSize(); 188 189 bds.width = rendererSize.width; 190 if (bds == null || !bds.contains(p)) { 191 hide(); 192 return; 193 } 194 if (setCompAndRow (list, row)) { 195 Rectangle visible = getShowingRect (view); 196 Rectangle [] rects = getRects (bds, visible); 197 if (rects.length > 0) { 198 ensureOldPopupsHidden(); 199 painter.configure( 200 list.getModel().getElementAt(row), 201 view, list, row); 202 showPopups (rects, bds, visible, list, view); 203 } else { 204 hide(); 205 } 206 } 207 } 208 209 private void showJTree (JScrollPane view, Point pt) { 210 JTree tree = (JTree ) view.getViewport().getView(); 211 Point p = SwingUtilities.convertPoint(view, 212 pt.x, pt.y, tree); 213 214 int row = tree.getClosestRowForLocation( 215 p.x, p.y); 216 217 TreePath path = 218 tree.getClosestPathForLocation(p.x, 219 p.y); 220 221 Rectangle bds = tree.getPathBounds(path); 222 if (bds == null || !bds.contains(p)) { 223 hide(); 224 return; 225 } 226 if (setCompAndRow (tree, row)) { 227 Rectangle visible = getShowingRect (view); 228 Rectangle [] rects = getRects (bds, visible); 229 if (rects.length > 0) { 230 ensureOldPopupsHidden(); 231 painter.configure( 232 path.getLastPathComponent(), 233 view, tree, path, row); 234 showPopups (rects, bds, visible, tree, view); 235 } else { 236 hide(); 237 } 238 } 239 } 240 241 245 private boolean setCompAndRow (JComponent inner, int row) { 246 boolean rowChanged = row != this.row; 247 boolean compChanged = inner != this.inner; 248 this.inner = inner; 249 this.row = row; 250 return (rowChanged || compChanged); 251 } 252 253 257 void hide() { 258 ensureOldPopupsHidden(); 259 if (painter != null) { 260 painter.clear(); 261 } 262 setHideComponent (null, null); 263 inner = null; 264 row = -1; 265 } 266 267 private void ensureOldPopupsHidden() { 268 for (int i=0; i < popups.length; i++) { 269 if (popups[i] != null) { 270 popups[i].hide(); 271 popups[i] = null; 272 } 273 } 274 } 275 276 280 private Rectangle getShowingRect (JScrollPane pane) { 281 Insets ins1 = pane.getViewport().getInsets(); 282 Border inner = pane.getViewportBorder(); 283 Insets ins2; 284 if (inner != null) { 285 ins2 = inner.getBorderInsets(pane); 286 } else { 287 ins2 = new Insets (0,0,0,0); 288 } 289 Insets ins3 = new Insets (0,0,0,0); 290 if (pane.getBorder() != null) { 291 ins3 = pane.getBorder().getBorderInsets(pane); 292 } 293 294 Rectangle r = pane.getViewportBorderBounds(); 295 r.translate(-r.x, -r.y); 296 r.width -= ins1.left + ins1.right; 297 r.width -= ins2.left + ins2.right; 298 r.height -= ins1.top + ins1.bottom; 299 r.height -= ins2.top + ins2.bottom; 300 r.x -= ins2.left; 301 r.x -= ins3.left; 302 Point p = pane.getViewport().getViewPosition(); 303 r.translate (p.x, p.y); 304 r = SwingUtilities.convertRectangle(pane.getViewport(), r, pane); 305 return r; 306 } 307 308 314 private static final Rectangle [] getRects(final Rectangle bds, final Rectangle vis) { 315 Rectangle [] result; 316 if (vis.contains(bds)) { 317 result = new Rectangle [0]; 318 } else { 319 if (bds.x < vis.x && bds.x + bds.width > vis.x + vis.width) { 320 Rectangle a = new Rectangle (bds.x, bds.y, vis.x - bds.x, bds.height); 321 Rectangle b = new Rectangle (vis.x + vis.width, bds.y, (bds.x + bds.width) - (vis.x + vis.width), bds.height); 322 result = new Rectangle [] {a, b}; 323 } else if (bds.x < vis.x) { 324 result = new Rectangle [] { 325 new Rectangle (bds.x, bds.y, vis.x - bds.x, bds.height) 326 }; 327 } else if (bds.x + bds.width > vis.x + vis.width) { 328 result = new Rectangle [] { 329 new Rectangle (vis.x + vis.width, bds.y, (bds.x + bds.width) - (vis.x + vis.width), bds.height) 330 }; 331 } else { 332 result = new Rectangle [0]; 333 } 334 } 335 return result; 336 } 337 338 341 private void showPopups(Rectangle [] rects, Rectangle bds, Rectangle visible, JComponent comp, JScrollPane view) { 342 boolean shown = false; 343 for (int i=0; i < rects.length; i++) { 344 Rectangle sect = rects[i]; 345 sect.translate (-bds.x, -bds.y); 346 ImgComp part = painter.getPartial(sect, bds.x + rects[i].x < visible.x); 347 Point pos = new Point (bds.x + rects[i].x, bds.y + rects[i].y); 348 SwingUtilities.convertPointToScreen(pos, comp); 349 if (comp instanceof JList ) { 350 pos.y--; 352 } 353 if (pos.x > 0) { popups[i] = getPopupFactory().getPopup(view, 356 part, pos.x, pos.y); 357 popups[i].show(); 358 shown = true; 359 } 360 } 361 if (shown) { 362 setHideComponent (comp, view); 363 } else { 364 setHideComponent (null, null); } 366 } 367 368 private static PopupFactory getPopupFactory() { 369 if (Utilities.isMac()) { 370 371 378 382 389 PopupFactory result = (PopupFactory ) Lookup.getDefault().lookup ( 390 PopupFactory .class); 391 return result == null ? PopupFactory.getSharedInstance() : result; 392 } else { 393 return PopupFactory.getSharedInstance(); 394 } 395 } 396 397 private Hider hider = null; 398 403 private void setHideComponent (JComponent comp, JScrollPane parent) { 404 if (hider != null) { 405 if (hider.isListeningTo(comp)) { 406 return; 407 } 408 } 409 if (hider != null) { 410 hider.detach(); 411 } 412 if (comp != null) { 413 hider = new Hider (comp, parent); 414 } else { 415 hider = null; 416 } 417 } 418 419 424 private static final class ImgComp extends JComponent { 425 private BufferedImage img; 426 private Dimension d = null; 427 428 private Color bg = Color.WHITE; 429 private JScrollPane comp = null; 430 431 private Object node = null; 432 433 private AffineTransform at = AffineTransform.getTranslateInstance(0d, 0d); 434 boolean isRight = false; 435 436 ImgComp() {} 437 438 441 ImgComp (BufferedImage img, Rectangle off, boolean right) { 442 this.img = img; 443 at = AffineTransform.getTranslateInstance(-off.x, 0); 444 d = new Dimension (off.width, off.height); 445 isRight = right; 446 } 447 448 public ImgComp getPartial (Rectangle bds, boolean right) { 449 assert img != null; 450 return new ImgComp (img, bds, right); 451 } 452 453 455 public boolean configure (Object nd, JScrollPane tv, JTree tree, TreePath path, int row) { 456 boolean sameVn = setLastRendereredObject(nd); 457 boolean sameComp = setLastRenderedScrollPane (tv); 458 Component renderer = null; 459 bg = tree.getBackground(); 460 boolean sel = tree.isSelectionEmpty() ? false : 461 tree.getSelectionModel().isPathSelected(path); 462 boolean exp = tree.isExpanded(path); 463 boolean leaf = !exp && tree.getModel().isLeaf(nd); 464 boolean lead = path.equals(tree.getSelectionModel().getLeadSelectionPath()); 465 renderer = tree.getCellRenderer().getTreeCellRendererComponent(tree, nd, sel, exp, leaf, row, lead); 466 if (renderer != null) { 467 setComponent (renderer); 468 } 469 return true; 470 } 471 472 474 public boolean configure (Object nd, JScrollPane tv, JList list, int row) { 475 boolean sameVn = setLastRendereredObject(nd); 476 boolean sameComp = setLastRenderedScrollPane (tv); 477 Component renderer = null; 478 bg = list.getBackground(); 479 boolean sel = list.isSelectionEmpty() ? false : 480 list.getSelectionModel().isSelectedIndex(row); 481 renderer = list.getCellRenderer().getListCellRendererComponent(list, nd, row, sel, false); 482 if (renderer != null) { 483 setComponent (renderer); 484 } 485 return true; 486 } 487 488 private boolean setLastRenderedScrollPane (JScrollPane comp) { 489 boolean result = comp != this.comp; 490 this.comp = comp; 491 return result; 492 } 493 494 private boolean setLastRendereredObject (Object nd) { 495 boolean result = node != nd; 496 if (result) { 497 node = nd; 498 } 499 return result; 500 } 501 502 void clear() { 503 comp = null; 504 node = null; 505 } 506 507 510 public void setComponent (Component jc) { 511 Dimension d = jc.getPreferredSize(); 512 BufferedImage nue = new BufferedImage (d.width, d.height + 2, 513 BufferedImage.TYPE_INT_ARGB_PRE); 514 SwingUtilities.paintComponent(nue.getGraphics(), jc, this, 0, 0, d.width, d.height + 2); 515 setImage (nue); 516 } 517 518 public Rectangle getBounds() { 519 Dimension dd = getPreferredSize(); 520 return new Rectangle (0, 0, dd.width, dd.height); 521 } 522 523 private void setImage(BufferedImage img) { 524 this.img = img; 525 d = null; 526 } 527 528 public Dimension getPreferredSize() { 529 if (d == null) { 530 d = new Dimension (img.getWidth(), img.getHeight()); 531 } 532 return d; 533 } 534 535 public Dimension getSize() { 536 return getPreferredSize(); 537 } 538 539 public void paint (Graphics g) { 540 g.setColor (bg); 541 g.fillRect (0, 0, d.width, d.height); 542 Graphics2D g2d = (Graphics2D ) g; 543 g2d.drawRenderedImage (img, at); 544 g.setColor (Color.GRAY); 545 g.drawLine (0, 0, d.width, 0); 546 g.drawLine (0, d.height-1, d.width, d.height-1); 547 if (isRight) { 548 g.drawLine (0, 0, 0, d.height-1); 549 } else { 550 g.drawLine (d.width-1, 0, d.width-1, d.height-1); 551 } 552 } 553 554 public void firePropertyChange (String s, Object a, Object b) {} 555 public void invalidate() {} 556 public void validate() {} 557 public void revalidate() {} 558 } 559 560 564 private static final class Hider implements ChangeListener , PropertyChangeListener , TreeModelListener , TreeSelectionListener , HierarchyListener , HierarchyBoundsListener , ListSelectionListener , ListDataListener , ComponentListener { 565 private final JTree tree; 566 567 private JScrollPane pane; 568 private final JList list; 569 570 public Hider (JComponent comp, JScrollPane pane) { 571 if (comp instanceof JTree ) { 572 this.tree = (JTree ) comp; 573 this.list = null; 574 } else { 575 this.list = (JList ) comp; 576 this.tree = null; 577 } 578 assert tree != null || list != null; 579 this.pane = pane; 580 attach(); 581 } 582 583 private boolean isListeningTo (JComponent comp) { 584 return !detached && (comp == list || comp == tree); 585 } 586 587 private void attach() { 588 if (tree != null) { 589 tree.getModel().addTreeModelListener(this); 590 tree.getSelectionModel().addTreeSelectionListener(this); 591 tree.addHierarchyBoundsListener(this); 592 tree.addHierarchyListener(this); 593 tree.addComponentListener(this); 594 } else { 595 list.getSelectionModel().addListSelectionListener(this); 596 list.getModel().addListDataListener(this); 597 list.addHierarchyBoundsListener(this); 598 list.addHierarchyListener(this); 599 list.addComponentListener(this); 600 } 601 pane.getHorizontalScrollBar().getModel().addChangeListener(this); 602 pane.getVerticalScrollBar().getModel().addChangeListener(this); 603 KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(this); 604 } 605 606 private boolean detached = false; 607 private void detach() { 608 KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener(this); 609 if (tree != null) { 610 tree.getSelectionModel().removeTreeSelectionListener(this); 611 tree.getModel().removeTreeModelListener(this); 612 tree.removeHierarchyBoundsListener(this); 613 tree.removeHierarchyListener(this); 614 tree.removeComponentListener(this); 615 } else { 616 list.getSelectionModel().removeListSelectionListener(this); 617 list.getModel().removeListDataListener(this); 618 list.removeHierarchyBoundsListener(this); 619 list.removeHierarchyListener(this); 620 list.removeComponentListener(this); 621 } 622 pane.getHorizontalScrollBar().getModel().removeChangeListener(this); 623 pane.getVerticalScrollBar().getModel().removeChangeListener(this); 624 detached = true; 625 } 626 627 private void change() { 628 if (ViewTooltips.INSTANCE != null) { 629 ViewTooltips.INSTANCE.hide(); 630 } 631 detach(); 632 } 633 634 public void propertyChange(PropertyChangeEvent evt) { 635 change(); 636 } 637 public void treeNodesChanged(TreeModelEvent e) { 638 change(); 639 } 640 641 public void treeNodesInserted(TreeModelEvent e) { 642 change(); 643 } 644 645 public void treeNodesRemoved(TreeModelEvent e) { 646 change(); 647 } 648 649 public void treeStructureChanged(TreeModelEvent e) { 650 change(); 651 } 652 653 public void hierarchyChanged(HierarchyEvent e) { 654 change(); 655 } 656 657 public void valueChanged(TreeSelectionEvent e) { 658 change(); 659 } 660 661 public void ancestorMoved(HierarchyEvent e) { 662 change(); 663 } 664 665 public void ancestorResized(HierarchyEvent e) { 666 change(); 667 } 668 669 public void stateChanged(ChangeEvent e) { 670 change(); 671 } 672 673 public void valueChanged(ListSelectionEvent e) { 674 change(); 675 } 676 677 public void intervalAdded(ListDataEvent e) { 678 change(); 679 } 680 681 public void intervalRemoved(ListDataEvent e) { 682 change(); 683 } 684 685 public void contentsChanged(ListDataEvent e) { 686 change(); 687 } 688 689 public void componentResized(ComponentEvent e) { 690 change(); 691 } 692 693 public void componentMoved(ComponentEvent e) { 694 change(); 695 } 696 697 public void componentShown(ComponentEvent e) { 698 change(); 699 } 700 701 public void componentHidden(ComponentEvent e) { 702 change(); 703 } 704 } 705 } 706 | Popular Tags |