diff -r 077ebe3d0d24 -r f1d133b09a8c components/visual-panels/core/src/java/util/com/oracle/solaris/vp/util/swing/ListCellOverlay.java --- a/components/visual-panels/core/src/java/util/com/oracle/solaris/vp/util/swing/ListCellOverlay.java Tue Dec 16 05:53:51 2014 -0800 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,651 +0,0 @@ -/* - * CDDL HEADER START - * - * The contents of this file are subject to the terms of the - * Common Development and Distribution License (the "License"). - * You may not use this file except in compliance with the License. - * - * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE - * or http://www.opensolaris.org/os/licensing. - * See the License for the specific language governing permissions - * and limitations under the License. - * - * When distributing Covered Code, include this CDDL HEADER in each - * file and include the License file at usr/src/OPENSOLARIS.LICENSE. - * If applicable, add the following below this CDDL HEADER, with the - * fields enclosed by brackets "[]" replaced with your own identifying - * information: Portions Copyright [yyyy] [name of copyright owner] - * - * CDDL HEADER END - */ - -/* - * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. - */ - -package com.oracle.solaris.vp.util.swing; - -import java.awt.*; -import java.awt.event.*; -import java.beans.*; -import java.util.*; -import java.util.List; -import javax.swing.*; -import javax.swing.Timer; -import javax.swing.event.*; -import com.oracle.solaris.vp.util.misc.CollectionUtil; - -/** - * The {@code ListCellOverlay} class provides an overlay over each cell in a - * {@code JList}. This overlay extends the cell to its preferred size so that - * the user can see the entire contents of a cell that is partially hidden - * within a scroll pane or is sized smaller than its preferred size. - *
- * This class assumes that the {@code JList} is contained within a {@code - * JScrollPane}; the overlay is styled to complement that scroll pane. - *
- * The {@link #getOverlayComponent overlay component} or the {@link - * #getOverlayScrollPane scroll pane} that contains it may be changed visually - * (custom borders, background color, etc.) to affect the look of the overlay. - */ -public class ListCellOverlay { - // - // Enums - // - - public enum Style { - SINGLE_POPUP, MULTI_POPUP - } - - // - // Inner classes - // - - private static abstract class CellPanel extends JPanel { - // - // Instance data - // - - private CellRendererPane rPane = new CellRendererPane(); - - // - // Constructors - // - - public CellPanel() { - setLayout(null); - add(rPane); - } - - // - // Component methods - // - - @Override - public Dimension getPreferredSize() { - Dimension d = getRendererComponentPreferredSize(); - Insets insets = getInsets(); - d.width += insets.left + insets.right; - d.height += insets.top + insets.bottom; - - return d; - } - - // - // JComponent methods - // - - @Override - public void paintComponent(Graphics g) { - super.paintComponent(g); - - Component comp = getRendererComponent(); - - Insets insets = getInsets(); - int x = insets.left; - int y = insets.top; - int width = getWidth() - insets.left - insets.right; - int height = getHeight() - insets.top - insets.bottom; - - rPane.paintComponent(g, comp, this, x, y, width, height, true); - } - - // - // CellPanel methods - // - - public abstract Component getRendererComponent(); - - public Dimension getRendererComponentPreferredSize() { - return getRendererComponent().getPreferredSize(); - } - } - - private class SimpleCellPanel extends CellPanel { - // - // Instance data - // - - private Component comp; - - // - // Constructors - // - - public SimpleCellPanel(Component comp) { - this.comp = comp; - - MouseAdapter listener = - new MouseAdapter() { - // - // MouseListener methods - // - - @Override - public void mouseClicked(MouseEvent e) { - GUIUtil.propagate(e, list); - } - - @Override - public void mouseExited(MouseEvent e) { - configurePopups(e); - } - - @Override - public void mousePressed(MouseEvent e) { - GUIUtil.propagate(e, list); - } - - @Override - public void mouseReleased(MouseEvent e) { - GUIUtil.propagate(e, list); - } - - // - // MouseMotionListener methods - // - - @Override - public void mouseDragged(MouseEvent e) { - GUIUtil.propagate(e, list); - } - - @Override - public void mouseMoved(MouseEvent e) { - GUIUtil.propagate(e, list); - } - - // - // MouseWheelListener methods - // - - @Override - public void mouseWheelMoved(MouseWheelEvent e) { - if (listScroll != null) { - GUIUtil.propagate(e, listScroll); - } - } - }; - - addMouseListener(listener); - addMouseMotionListener(listener); - addMouseWheelListener(listener); - } - - // - // CellPanel methods - // - - @Override - public Component getRendererComponent() { - return comp; - } - } - - // - // Static data - // - - /** - * The initial delay after hovering over the list before an overlay appears. - */ - public static final int DELAY = 0; - - // - // Instance data - // - - // Set to true to paint each overlay rectangle with a solid color - private boolean debug = false; - - private JList list; - private JScrollPane listScroll; - - private CellPanel panel; - private JScrollPane panelScroll; - private Map popups = new HashMap(); - private Timer timer; - - private int index = -1; - private boolean enabled = true; - private Style style = Style.MULTI_POPUP; - - private ListDataListener listModelListener = - new ListDataListener() { - @Override - public void contentsChanged(ListDataEvent e) { - repaintPopups(); - } - - @Override - public void intervalAdded(ListDataEvent e) { - repaintPopups(); - } - - @Override - public void intervalRemoved(ListDataEvent e) { - hidePopups(); - } - }; - - // - // Constructors - // - - public ListCellOverlay(final JList list) { - this.list = list; - - panel = new CellPanel() { - @Override - public Component getRendererComponent() { - Object value = list.getModel().getElementAt(index); - boolean isSelected = list.isSelectedIndex(index); - boolean hasFocus = list.hasFocus() && - (index == list.getLeadSelectionIndex()); - - return list.getCellRenderer().getListCellRendererComponent( - list, value, index, isSelected, hasFocus); - } - - @Override - public Dimension getRendererComponentPreferredSize() { - Dimension d = super.getRendererComponentPreferredSize(); - - Rectangle r = list.getCellBounds(index, index); - d.width = Math.max(d.width, r.width); - d.height = Math.max(d.height, r.height); - - return d; - } - }; - - panel.setOpaque(true); - panel.setBackground(list.getBackground()); - - panelScroll = new JScrollPane(); - - timer = new Timer(DELAY, - new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - for (Popup popup : popups.keySet()) { - popup.show(); - } - } - }); - timer.setRepeats(false); - - HierarchyListener listener = - new HierarchyListener() { - @Override - public void hierarchyChanged(HierarchyEvent e) { - listScroll = (JScrollPane)SwingUtilities.getAncestorOfClass( - JScrollPane.class, list); - } - }; - list.addHierarchyListener(listener); - - // Initialize - listener.hierarchyChanged(null); - - list.addHierarchyBoundsListener( - new HierarchyBoundsListener() { - @Override - public void ancestorMoved(HierarchyEvent e) { - hidePopups(); - } - - @Override - public void ancestorResized(HierarchyEvent e) { - hidePopups(); - } - }); - - list.addComponentListener( - new ComponentAdapter() { - @Override - public void componentMoved(ComponentEvent e) { - hidePopups(); - } - - @Override - public void componentResized(ComponentEvent e) { - hidePopups(); - } - }); - - list.addMouseMotionListener( - new MouseMotionAdapter() { - @Override - public void mouseMoved(MouseEvent e) { - configurePopups(e); - } - }); - - list.addMouseListener( - new MouseAdapter() { - @Override - public void mouseExited(MouseEvent e) { - configurePopups(e); - } - }); - - list.addListSelectionListener( - new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e) { - repaintPopups(); - } - }); - - list.addPropertyChangeListener("model", - new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent e) { - modelChanged((ListModel)e.getOldValue(), - (ListModel)e.getNewValue()); - } - }); - - // Initialize - modelChanged(null, list.getModel()); - } - - // - // ListCellOverlay methods - // - - /** - * Gets the initial delay after hovering over the list before an overlay of - * a cell appears. - */ - public int getDelay() { - return timer.getInitialDelay(); - } - - /** - * Gets whether this {@code ListCellOverlay} is enabled. - */ - public boolean getEnabled() { - return enabled; - } - - public JList getList() { - return list; - } - - /** - * Gets the {@code JComponent} that is displays the cell renderer in the - * overlay. This {@code JComponent} can be customized with a border, a - * change in background color, etc. - */ - public JComponent getOverlayComponent() { - return panel; - } - - /** - * Gets the {@code JScrollPane} containing the {@link #getOverlayComponent - * overlay component}. This provides a border that (presumably) matches the - * one surrounding the list. - */ - public JScrollPane getOverlayScrollPane() { - return panelScroll; - } - - /** - * Gets the style to use when displaying the overlay. - */ - public Style getStyle() { - return style; - } - - /** - * Sets the style to use when displaying the overlay. {@link - * Style#SINGLE_POPUP} uses a single, tooltip-like popup, complete with - * borders, as the overlay. {@link Style#MULTI_POPUP} uses multiple popups - * to make it appear as though the scroll pane that encloses the list - * changes shape to accommodate the overlay. The latter style may appear - * more polished, but the former has a lesser performance hit. The default - * value is {@link Style#MULTI_POPUP}. - */ - public void setStyle(Style style) { - this.style = style; - } - - /** - * Sets the initial delay after hovering over the list before an overlay of - * a cell appears. - */ - public void setDelay(int delay) { - timer.setInitialDelay(delay); - } - - /** - * Gets whether this {@code ListCellOverlay} is enabled. - */ - public void setEnabled(boolean enabled) { - if (this.enabled != enabled) { - if (!enabled) { - hidePopups(); - } - this.enabled = enabled; - } - } - - // - // Private methods - // - - private void configurePopups(MouseEvent e) { - if (!enabled || listScroll == null) { - return; - } - - Rectangle cellRect = null; - int index; - - Point listScrollPoint = SwingUtilities.convertPoint( - e.getComponent(), e.getPoint(), listScroll); - - // Is the point visible? - if (!listScroll.contains(listScrollPoint)) { - index = -1; - } else { - Point listPoint = SwingUtilities.convertPoint( - listScroll, listScrollPoint, list); - - index = list.locationToIndex(listPoint); - if (index != -1) { - cellRect = list.getCellBounds(index, index); - if (!cellRect.contains(listPoint)) { - // The point is not actually in a list cell - index = -1; - } - } - } - - // Has the index of the overlaid cell changed? - if (this.index != index) { - hidePopups(); - - if (index != -1) { - this.index = index; - - if (isCellObscured()) { - // We now need to figure out where to place popup windows - // (containing scroll, which contains panel, which paints - // the cell renderer) so that the cell renderer within the - // popups aligns with the cell renderer in the list. - - // The size of the panel scroll pane border - // (panelScroll.getInsets() doesn't work under some L&Fs - // (like synth), so we are forced to do a layout and compare - // points) - panelScroll.setViewportView(panel); - panelScroll.doLayout(); - Point panelScrollInsets = SwingUtilities.convertPoint( - panel, 0, 0, panelScroll); - - // The size of the panel border - Insets panelInsets = panel.getInsets(); - - if (cellRect == null) { - cellRect = list.getCellBounds(index, index); - } - - Dimension panelScrollPrefSize = - panelScroll.getPreferredSize(); - - // The bounds of the panel scroll pane in list coordinates - Rectangle panelScrollBounds = new Rectangle( - cellRect.x - panelInsets.left - panelScrollInsets.x, - cellRect.y - panelInsets.top - panelScrollInsets.y, - panelScrollPrefSize.width, panelScrollPrefSize.height); - - // Convert to the list scroll pane's coordinates - panelScrollBounds = SwingUtilities.convertRectangle( - list, panelScrollBounds, listScroll); - - List rectList = new ArrayList(); - - switch (style) { - case SINGLE_POPUP: - rectList.add(panelScrollBounds); - break; - - default: - case MULTI_POPUP: - // The bounds of the list viewport in the list - // scroll pane's coordinates - Rectangle listViewBounds = - listScroll.getViewport().getBounds(); - - Rectangle[] rects = - SwingUtilities.computeDifference( - panelScrollBounds, listViewBounds); - - CollectionUtil.addAll(rectList, rects); - - // Convert to the list scroll pane's coordinates - cellRect = SwingUtilities.convertRectangle( - list, cellRect, listScroll); - - // Now add one more rect to cover the visible area - // of the cell entirely. This is necessary because - // the renderer may be drawing its content truncated - // (ie. "Foo bar..."), and we need it to be drawn at - // full size. - rectList.add(cellRect); - } - - Point listScrollScreenPoint = - listScroll.getLocationOnScreen(); - - for (Rectangle rect : rectList) { - Point point = rect.getLocation(); - point.translate(-panelScrollBounds.x, - -panelScrollBounds.y); - - JViewport viewport = new JViewport(); - viewport.setPreferredSize(new Dimension( - rect.width, rect.height)); - - CellPanel cell = new SimpleCellPanel(panelScroll); - - if (debug) { - JLabel label = new JLabel(); - label.setOpaque(true); - label.setBackground(ColorUtil.getRandomColor()); - cell = new SimpleCellPanel(label); - } - - viewport.setView(cell); - viewport.setViewPosition(point); - - int x = rect.x + listScrollScreenPoint.x; - int y = rect.y + listScrollScreenPoint.y; - - Popup popup = PopupFactory.getSharedInstance().getPopup( - list, viewport, x, y); - - popups.put(popup, cell); - } - - timer.restart(); - } - } - } - } - - private boolean getPopupsShowing() { - return !popups.isEmpty(); - } - - private void hidePopups() { - timer.stop(); - index = -1; - for (Iterator i = popups.keySet().iterator(); i.hasNext();) { - Popup popup = i.next(); - popup.hide(); - i.remove(); - } - list.repaint(); - } - - private boolean isCellObscured() { - Rectangle cellRect = list.getCellBounds(index, index); - Dimension panelSize = panel.getPreferredSize(); - - // Is the cell rect smaller than the preferred size? - if (cellRect.width < panelSize.width || - cellRect.height < panelSize.height) { - return true; - } - - // The bounds of panel at its preferred size, relative to listScroll - Rectangle panelBounds = SwingUtilities.convertRectangle(list, - new Rectangle(cellRect), listScroll); - - Rectangle scrollBounds = new Rectangle(listScroll.getSize()); - - return !scrollBounds.contains(panelBounds); - } - - private void modelChanged(ListModel oldModel, ListModel newModel) { - if (oldModel != null) { - oldModel.removeListDataListener(listModelListener); - } - - if (newModel != null) { - newModel.addListDataListener(listModelListener); - } - } - - private void repaintPopups() { - if (getPopupsShowing()) { - for (CellPanel cell : popups.values()) { - cell.repaint(); - } - } - } -}