Merge commit '8b2627ce767579eb616e262b3f45f810a88ec200'
[fanfix.git] / src / be / nikiroo / utils / ui / ListModel.java
index 06e7914c1a80a5dbcc6287d8d636f96c8de000e2..cf16d5f0a111e660171102b3efad3bdefe1d6d48 100644 (file)
@@ -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,6 +12,7 @@ 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;
@@ -78,32 +80,66 @@ public class ListModel<T> extends DefaultListModel6<T> {
                public void setHovered(boolean hovered);
        }
 
+       /**
+        * An interface required to support tooltips on this {@link ListModel}.
+        * 
+        * @author niki
+        *
+        * @param <T>
+        *            the type of elements and items (the same type)
+        */
+       public interface TooltipCreator<T> {
+               /**
+                * Generate a tooltip {@link Window} for this element.
+                * <p>
+                * 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);
+       }
+
        private int hoveredIndex;
        private List<T> items = new ArrayList<T>();
-       private JList6<T> list;
+       private boolean keepSelection = true;
+
+       private TooltipCreator<T> tooltipCreator;
+       private Window tooltip;
+
+       @SuppressWarnings("rawtypes") // JList<?> not compatible Java 1.6
+       private JList list;
 
        /**
         * Create a new {@link ListModel}.
         * 
         * @param list
-        *            the {@link JList} we will handle the data of (cannot be NULL)
+        *            the {@link JList6} we will handle the data of (cannot be NULL)
         */
-       @SuppressWarnings({ "unchecked", "rawtypes" }) // not compatible Java 1.6
-       public ListModel(JList list) {
-               this((JList6<T>) list);
+       @SuppressWarnings("rawtypes") // JList<?> not compatible Java 1.6
+       public ListModel(JList6<T> list) {
+               this((JList) list);
        }
 
        /**
         * Create a new {@link ListModel}.
         * 
         * @param list
-        *            the {@link JList} we will handle the data of (cannot be NULL)
+        *            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)
         */
-       @SuppressWarnings({ "unchecked", "rawtypes" }) // not compatible Java 1.6
-       public ListModel(final JList list, final JPopupMenu popup) {
-               this((JList6<T>) list, popup);
+       @SuppressWarnings("rawtypes") // JList<?> not compatible Java 1.6
+       public ListModel(JList6<T> list, JPopupMenu popup) {
+               this((JList) list, popup);
        }
 
        /**
@@ -111,9 +147,13 @@ public class ListModel<T> extends DefaultListModel6<T> {
         * 
         * @param list
         *            the {@link JList6} we will handle the data of (cannot be NULL)
+        * @param tooltipCreator
+        *            use this if you want the list to display tooltips on hover
+        *            (can be NULL)
         */
-       public ListModel(JList6<T> list) {
-               this(list, null);
+       @SuppressWarnings("rawtypes") // JList<?> not compatible Java 1.6
+       public ListModel(JList6<T> list, TooltipCreator<T> tooltipCreator) {
+               this((JList) list, null, tooltipCreator);
        }
 
        /**
@@ -123,24 +163,142 @@ public class ListModel<T> extends DefaultListModel6<T> {
         *            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)
+        * @param tooltipCreator
+        *            use this if you want the list to display tooltips on hover
+        *            (can be NULL)
+        */
+       @SuppressWarnings("rawtypes") // JList<?> not compatible Java 1.6
+       public ListModel(JList6<T> list, JPopupMenu popup,
+                       TooltipCreator<T> tooltipCreator) {
+               this((JList) list, popup, tooltipCreator);
+       }
+
+       /**
+        * Create a new {@link ListModel}.
+        * <p>
+        * 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 JList} we will handle the data of (cannot be NULL,
+        *            must only contain elements of the type of this
+        *            {@link ListModel})
+        */
+       @SuppressWarnings("rawtypes") // JList<?> not compatible Java 1.6
+       public ListModel(JList list) {
+               this(list, null, null);
+       }
+
+       /**
+        * Create a new {@link ListModel}.
+        * <p>
+        * 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, JPopupMenu)} instead.
+        * 
+        * @param list
+        *            the {@link JList} we will handle the data of (cannot be NULL,
+        *            must only contain elements of the type of this
+        *            {@link ListModel})
+        * @param popup
+        *            the popup to use and keep track of (can be NULL)
         */
-       public ListModel(final JList6<T> list, final JPopupMenu popup) {
+       @SuppressWarnings("rawtypes") // JList<?> not in Java 1.6
+       public ListModel(JList list, JPopupMenu popup) {
+               this(list, popup, null);
+       }
+
+       /**
+        * Create a new {@link ListModel}.
+        * <p>
+        * 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, JPopupMenu)} instead.
+        * 
+        * @param list
+        *            the {@link JList} we will handle the data of (cannot be NULL,
+        *            must only contain elements of the type of this
+        *            {@link ListModel})
+        * @param tooltipCreator
+        *            use this if you want the list to display tooltips on hover
+        *            (can be NULL)
+        */
+       @SuppressWarnings("rawtypes") // JList<?> not in Java 1.6
+       public ListModel(JList list, TooltipCreator<T> tooltipCreator) {
+               this(list, null, tooltipCreator);
+       }
+
+       /**
+        * Create a new {@link ListModel}.
+        * <p>
+        * 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, JPopupMenu)} instead.
+        * 
+        * @param list
+        *            the {@link JList} we will handle the data of (cannot be NULL,
+        *            must only contain elements of the type of this
+        *            {@link ListModel})
+        * @param popup
+        *            the popup to use and keep track of (can be NULL)
+        * @param tooltipCreator
+        *            use this if you want the list to display tooltips on hover
+        *            (can be NULL)
+        */
+       @SuppressWarnings({ "unchecked", "rawtypes" }) // JList<?> not in Java 1.6
+       public ListModel(final JList list, final JPopupMenu popup,
+                       TooltipCreator<T> tooltipCreator) {
                this.list = list;
+               this.tooltipCreator = tooltipCreator;
+
                list.setModel(this);
 
+               final DelayWorker tooltipWatcher = new DelayWorker(500);
+               if (tooltipCreator != null) {
+                       tooltipWatcher.start();
+               }
+
                list.addMouseMotionListener(new MouseAdapter() {
                        @Override
-                       public void mouseMoved(MouseEvent me) {
+                       public void mouseMoved(final MouseEvent me) {
                                if (popup != null && 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) {
+                                               tooltipWatcher.delay("tooltip",
+                                                               new SwingWorker<Void, Void>() {
+                                                                       @Override
+                                                                       protected Void doInBackground()
+                                                                                       throws Exception {
+                                                                               return null;
+                                                                       }
+
+                                                                       @Override
+                                                                       protected void done() {
+                                                                               Window oldTooltip = tooltip;
+                                                                               tooltip = null;
+                                                                               if (oldTooltip != null) {
+                                                                                       oldTooltip.setVisible(false);
+                                                                               }
+
+                                                                               if (index < 0
+                                                                                               || index != hoveredIndex) {
+                                                                                       return;
+                                                                               }
+
+                                                                               tooltip = newTooltip(index, me);
+                                                                       }
+                                                               });
+                                       }
                                }
                        }
                });
@@ -182,9 +340,35 @@ public class ListModel<T> extends DefaultListModel6<T> {
                                        popup.show(list, e.getX(), e.getY());
                                }
                        }
+
                });
        }
 
+       /**
+        * (Try and) keep the elements that were selected when filtering.
+        * <p>
+        * 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.
+        * <p>
+        * 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;
+       }
+
        /**
         * Check if this element is currently under the mouse.
         * 
@@ -287,8 +471,13 @@ public class ListModel<T> extends DefaultListModel6<T> {
         *            the filter will be copied as an element (can be NULL, in that
         *            case all items will be copied as elements)
         */
-       @SuppressWarnings("unchecked") // ListModel<T> and JList<T> are not java 1.6
+       @SuppressWarnings("unchecked") // JList<?> not compatible Java 1.6
        public void filter(Predicate<T> filter) {
+               ListSnapshot snapshot = null;
+
+               if (keepSelection)
+                       snapshot = new ListSnapshot(list);
+
                clear();
                for (T item : items) {
                        if (filter == null || filter.test(item)) {
@@ -296,6 +485,9 @@ public class ListModel<T> extends DefaultListModel6<T> {
                        }
                }
 
+               if (keepSelection)
+                       snapshot.apply();
+
                list.repaint();
        }
 
@@ -354,12 +546,37 @@ public class ListModel<T> extends DefaultListModel6<T> {
                }
        }
 
-       @SuppressWarnings("unchecked") // ListModel<T> and JList<T> 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);
+                                       promotedTooltip.setLocation(newTooltip.getLocation());
+                                       newTooltip.setVisible(false);
+                                       promotedTooltip.setVisible(true);
+                               }
+                       });
+                       newTooltip.setLocation(me.getXOnScreen(), me.getYOnScreen());
+
+                       newTooltip.setVisible(true);
+               }
+
+               return newTooltip;
+       }
+
        /**
         * Generate a {@link ListCellRenderer} that supports {@link Hoverable}
         * elements.