/** * Copyright (C) 2001-2017 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.com * * This program is free software: you can redistribute it and/or modify it under the terms of the * GNU Affero General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.gui.tools; import java.awt.EventQueue; import java.awt.Point; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.HierarchyBoundsListener; import java.awt.event.HierarchyEvent; import java.awt.event.HierarchyListener; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.JList; import javax.swing.ListModel; import javax.swing.SwingUtilities; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.event.MouseInputAdapter; /** * Utility class to get information about hovering index in a JList ListCellEditor. * * * @author Nils Woehler * */ public class ListHoverHelper { private static class HoverListener extends MouseInputAdapter implements ListDataListener, PropertyChangeListener, ComponentListener, HierarchyListener, HierarchyBoundsListener, Runnable { private JList<?> list; private int hoverIndex; private boolean enabled, running; private Point lastLocation; public HoverListener(JList<?> l) { list = l; hoverIndex = -1; } public void setEnabled(boolean value) { if (enabled == value) { return; } enabled = value; if (enabled) { list.addMouseListener(this); list.addMouseMotionListener(this); list.addPropertyChangeListener(this); list.getModel().addListDataListener(this); list.addComponentListener(this); list.addHierarchyListener(this); list.addHierarchyBoundsListener(this); list.putClientProperty(HOVER, this); } else { repaint(hoverIndex()); list.removeMouseListener(this); list.removeMouseMotionListener(this); list.removePropertyChangeListener(this); list.getModel().removeListDataListener(this); list.removeHierarchyBoundsListener(this); list.removeHierarchyListener(this); list.removeComponentListener(this); list.putClientProperty(HOVER, null); } } public int hoverIndex() { return hoverIndex; } private void setHoverIndex(int value) { hoverIndex = value; } private void repaint(int index) { if (index != -1) { list.repaint(list.getCellBounds(index, index)); } } private Point toScreen(Point p) { p = new Point(p); SwingUtilities.convertPointToScreen(p, list); return p; } private Point toList(Point p) { if (!list.isShowing()) { return null; } Point s = list.getLocationOnScreen(); s.x = p.x - s.x; s.y = p.y - s.y; return s; } // p is in screen coordinate system (or zero, denoting not known or outside) private void updateHover(Point p) { int h = hoverIndex(); Point q = p == null ? null : toList(p); int newHoverIndex = p == null ? -1 : list.locationToIndex(q); if (h != newHoverIndex) { repaint(h); repaint(newHoverIndex); setHoverIndex(newHoverIndex); } lastLocation = p; } // updateLater is used to make sure that the cell bounds are already updated. // this may have problems if called initially not from the event-dispatch thread private void updateLater() { if (!running) { running = true; EventQueue.invokeLater(this); } } @Override public void run() { running = false; updateHover(lastLocation); } @Override public void propertyChange(PropertyChangeEvent e) { String name = e.getPropertyName(); if (name.equals("model")) { ((ListModel<?>) e.getOldValue()).removeListDataListener(this); ((ListModel<?>) e.getNewValue()).addListDataListener(this); setHoverIndex(-1); updateLater(); } else if (name.equals("font") || name.equals("cellRenderer") || name.equals("fixedRowWidth") || name.equals("fixedRowHeight") || name.equals("prototypeCellValue") || name.equals("layoutOrientation")) { // very unspecific list.repaint(); updateLater(); } } @Override public void hierarchyChanged(HierarchyEvent e) { if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) { if (!list.isShowing()) { lastLocation = null; setHoverIndex(-1); } // else: nothing to be done as mouse location is not known (only 1.5) } } @Override public void componentShown(ComponentEvent e) {} @Override public void componentHidden(ComponentEvent e) { // handled by hierarchyChanged } @Override public void componentMoved(ComponentEvent e) { updateHover(lastLocation); } @Override public void ancestorMoved(HierarchyEvent e) { updateHover(lastLocation); } @Override public void componentResized(ComponentEvent e) { updateLater(); } @Override public void ancestorResized(HierarchyEvent e) { updateLater(); } @Override public void mouseMoved(MouseEvent e) { updateHover(toScreen(e.getPoint())); } @Override public void mouseDragged(MouseEvent e) { updateHover(toScreen(e.getPoint())); } @Override public void mouseEntered(MouseEvent e) { updateHover(toScreen(e.getPoint())); } @Override public void mouseExited(MouseEvent e) { updateHover(null); } // These implementations are quite unspecific. @Override public void intervalAdded(ListDataEvent e) { if (hoverIndex() >= e.getIndex0()) { setHoverIndex(-1); } updateLater(); } @Override public void intervalRemoved(ListDataEvent e) { if (hoverIndex() >= e.getIndex0()) { setHoverIndex(-1); } updateLater(); } @Override public void contentsChanged(ListDataEvent e) { updateLater(); } } private static Object HOVER = "xxx.Hover"; public static int index(JList<?> l) { HoverListener h = (HoverListener) l.getClientProperty(HOVER); return h == null ? -1 : h.hoverIndex(); } public static void install(JList<?> l) { if (l.getClientProperty(HOVER) == null) { HoverListener h = new HoverListener(l); h.setEnabled(true); } } public static void uninstall(JList<?> l) { if (l != null) { HoverListener h = (HoverListener) l.getClientProperty(HOVER); h.setEnabled(false); } } }