X-Git-Url: http://git.nikiroo.be/?p=nikiroo-utils.git;a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2Fui%2FListModel.java;h=7cc23b8c34b78262b33b5dc065e525d6e2a1ce90;hp=06e7914c1a80a5dbcc6287d8d636f96c8de000e2;hb=844d50dbf3ceb3480b0effc9085752de503856aa;hpb=fa9ed2183664589496a443157f195a03862f0c66 diff --git a/src/be/nikiroo/utils/ui/ListModel.java b/src/be/nikiroo/utils/ui/ListModel.java index 06e7914..7cc23b8 100644 --- a/src/be/nikiroo/utils/ui/ListModel.java +++ b/src/be/nikiroo/utils/ui/ListModel.java @@ -2,6 +2,7 @@ package be.nikiroo.utils.ui; import java.awt.Component; import java.awt.Point; +import java.awt.Window; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; @@ -11,10 +12,11 @@ import java.util.List; import javax.swing.JList; import javax.swing.JPopupMenu; import javax.swing.ListCellRenderer; +import javax.swing.SwingWorker; -import be.nikiroo.utils.compat.DefaultListModel6; -import be.nikiroo.utils.compat.JList6; -import be.nikiroo.utils.compat.ListCellRenderer6; +import be.nikiroo.utils.ui.compat.DefaultListModel6; +import be.nikiroo.utils.ui.compat.JList6; +import be.nikiroo.utils.ui.compat.ListCellRenderer6; /** * A {@link javax.swing.ListModel} that can maintain 2 lists; one with the @@ -32,6 +34,9 @@ import be.nikiroo.utils.compat.ListCellRenderer6; public class ListModel extends DefaultListModel6 { private static final long serialVersionUID = 1L; + /** How long to wait before displaying a tooltip, in milliseconds. */ + private static final int DELAY_TOOLTIP_MS = 1000; + /** * A filter interface, to check for a condition (note that a Predicate class * already exists in Java 1.8+, and is compatible with this one if you @@ -78,33 +83,45 @@ public class ListModel extends DefaultListModel6 { public void setHovered(boolean hovered); } - private int hoveredIndex; - private List items = new ArrayList(); - private JList6 list; - /** - * Create a new {@link ListModel}. + * An interface required to support tooltips on this {@link ListModel}. * - * @param list - * the {@link JList} we will handle the data of (cannot be NULL) + * @author niki + * + * @param + * the type of elements and items (the same type) */ - @SuppressWarnings({ "unchecked", "rawtypes" }) // not compatible Java 1.6 - public ListModel(JList list) { - this((JList6) list); + public interface TooltipCreator { + /** + * Generate a tooltip {@link Window} for this element. + *

+ * Note that the tooltip can be of two modes: undecorated or standalone. + * An undecorated tooltip will be taken care of by this + * {@link ListModel}, but a standalone one is supposed to be its own + * Dialog or Frame (it won't be automatically closed). + * + * @param t + * the element to generate a tooltip for + * @param undecorated + * TRUE for undecorated tooltip, FALSE for standalone + * tooltips + * + * @return the generated tooltip or NULL for none + */ + public Window generateTooltip(T t, boolean undecorated); } - /** - * Create a new {@link ListModel}. - * - * @param list - * the {@link JList} we will handle the data of (cannot be NULL) - * @param popup - * the popup to use and keep track of (can be NULL) - */ - @SuppressWarnings({ "unchecked", "rawtypes" }) // not compatible Java 1.6 - public ListModel(final JList list, final JPopupMenu popup) { - this((JList6) list, popup); - } + private int hoveredIndex; + private List items = new ArrayList(); + private boolean keepSelection = true; + + private DelayWorker tooltipWatcher; + private JPopupMenu popup; + private TooltipCreator tooltipCreator; + private Window tooltip; + + @SuppressWarnings("rawtypes") // JList not compatible Java 1.6 + private JList list; /** * Create a new {@link ListModel}. @@ -112,35 +129,78 @@ public class ListModel extends DefaultListModel6 { * @param list * the {@link JList6} we will handle the data of (cannot be NULL) */ + @SuppressWarnings("rawtypes") // JList not compatible Java 1.6 public ListModel(JList6 list) { - this(list, null); + this((JList) list); } /** * Create a new {@link ListModel}. + *

+ * Note that you must take care of passing a {@link JList} that only handles + * elements of the type of this {@link ListModel} -- you can also use + * {@link ListModel#ListModel(JList6)} instead. * * @param list - * the {@link JList6} we will handle the data of (cannot be NULL) - * @param popup - * the popup to use and keep track of (can be NULL) + * the {@link JList} we will handle the data of (cannot be NULL, + * must only contain elements of the type of this + * {@link ListModel}) */ - public ListModel(final JList6 list, final JPopupMenu popup) { + @SuppressWarnings({ "unchecked", "rawtypes" }) // JList not in Java 1.6 + public ListModel(final JList list) { this.list = list; + list.setModel(this); + // We always have it ready + tooltipWatcher = new DelayWorker(DELAY_TOOLTIP_MS); + tooltipWatcher.start(); + list.addMouseMotionListener(new MouseAdapter() { @Override - public void mouseMoved(MouseEvent me) { - if (popup != null && popup.isShowing()) + public void mouseMoved(final MouseEvent me) { + if (ListModel.this.popup != null + && ListModel.this.popup.isShowing()) return; Point p = new Point(me.getX(), me.getY()); - int index = list.locationToIndex(p); + final int index = list.locationToIndex(p); if (index != hoveredIndex) { int oldIndex = hoveredIndex; hoveredIndex = index; fireElementChanged(oldIndex); fireElementChanged(index); + + if (ListModel.this.tooltipCreator != null) { + showTooltip(null); + + tooltipWatcher.delay("tooltip", + new SwingWorker() { + @Override + protected Void doInBackground() + throws Exception { + return null; + } + + @Override + protected void done() { + showTooltip(null); + + if (index < 0 + || index != hoveredIndex) { + return; + } + + if (ListModel.this.popup != null + && ListModel.this.popup + .isShowing()) { + return; + } + + showTooltip(newTooltip(index, me)); + } + }); + } } } }); @@ -158,7 +218,8 @@ public class ListModel extends DefaultListModel6 { @Override public void mouseExited(MouseEvent e) { - if (popup != null && popup.isShowing()) + if (ListModel.this.popup != null + && ListModel.this.popup.isShowing()) return; if (hoveredIndex > -1) { @@ -169,7 +230,7 @@ public class ListModel extends DefaultListModel6 { } private void check(MouseEvent e) { - if (popup == null) { + if (ListModel.this.popup == null) { return; } @@ -179,12 +240,79 @@ public class ListModel extends DefaultListModel6 { list.locationToIndex(e.getPoint())); } - popup.show(list, e.getX(), e.getY()); + showTooltip(null); + ListModel.this.popup.show(list, e.getX(), e.getY()); } } + }); } + /** + * (Try and) keep the elements that were selected when filtering. + *

+ * This will use toString on the elements to identify them, and can be a bit + * resource intensive. + * + * @return TRUE if we do + */ + public boolean isKeepSelection() { + return keepSelection; + } + + /** + * (Try and) keep the elements that were selected when filtering. + *

+ * This will use toString on the elements to identify them, and can be a bit + * resource intensive. + * + * @param keepSelection + * TRUE to try and keep them selected + */ + public void setKeepSelection(boolean keepSelection) { + this.keepSelection = keepSelection; + } + + /** + * The popup to use and keep track of (can be NULL). + * + * @return the current popup + */ + public JPopupMenu getPopup() { + return popup; + } + + /** + * The popup to use and keep track of (can be NULL). + * + * @param popup + * the new popup + */ + public void setPopup(JPopupMenu popup) { + this.popup = popup; + } + + /** + * You can use a {@link TooltipCreator} if you want the list to display + * tooltips on mouse hover (can be NULL). + * + * @return the current {@link TooltipCreator} + */ + public TooltipCreator getTooltipCreator() { + return tooltipCreator; + } + + /** + * You can use a {@link TooltipCreator} if you want the list to display + * tooltips on mouse hover (can be NULL). + * + * @param tooltipCreator + * the new {@link TooltipCreator} + */ + public void setTooltipCreator(TooltipCreator tooltipCreator) { + this.tooltipCreator = tooltipCreator; + } + /** * Check if this element is currently under the mouse. * @@ -287,8 +415,13 @@ public class ListModel extends DefaultListModel6 { * the filter will be copied as an element (can be NULL, in that * case all items will be copied as elements) */ - @SuppressWarnings("unchecked") // ListModel and JList are not java 1.6 + @SuppressWarnings("unchecked") // JList not compatible Java 1.6 public void filter(Predicate filter) { + ListSnapshot snapshot = null; + + if (keepSelection) + snapshot = new ListSnapshot(list); + clear(); for (T item : items) { if (filter == null || filter.test(item)) { @@ -296,6 +429,9 @@ public class ListModel extends DefaultListModel6 { } } + if (keepSelection) + snapshot.apply(); + list.repaint(); } @@ -354,12 +490,53 @@ public class ListModel extends DefaultListModel6 { } } - @SuppressWarnings("unchecked") // ListModel and JList are not java 1.6 + @SuppressWarnings("unchecked") // JList not compatible Java 1.6 @Override public T get(int index) { return (T) super.get(index); } + private Window newTooltip(final int index, final MouseEvent me) { + final T value = ListModel.this.get(index); + final Window newTooltip = tooltipCreator.generateTooltip(value, true); + if (newTooltip != null) { + newTooltip.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + Window promotedTooltip = tooltipCreator + .generateTooltip(value, false); + if (promotedTooltip != null) { + promotedTooltip.setLocation(me.getXOnScreen(), + me.getYOnScreen()); + promotedTooltip.setVisible(true); + } + + newTooltip.setVisible(false); + } + }); + + newTooltip.setLocation(me.getXOnScreen(), me.getYOnScreen()); + showTooltip(newTooltip); + } + + return newTooltip; + } + + private void showTooltip(Window tooltip) { + synchronized (tooltipWatcher) { + if (this.tooltip != null) { + this.tooltip.setVisible(false); + this.tooltip.dispose(); + } + + this.tooltip = tooltip; + + if (tooltip != null) { + tooltip.setVisible(true); + } + } + } + /** * Generate a {@link ListCellRenderer} that supports {@link Hoverable} * elements.