X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;ds=sidebyside;f=src%2Fbe%2Fnikiroo%2Futils%2Fui%2FListModel.java;fp=src%2Fbe%2Fnikiroo%2Futils%2Fui%2FListModel.java;h=06e7914c1a80a5dbcc6287d8d636f96c8de000e2;hb=f19b48e27a56ebab18687debd9ef52581a03f06d;hp=0000000000000000000000000000000000000000;hpb=dfa4091ccd9f46687c7326aa6bf2eaf588a7cb83;p=fanfix.git
diff --git a/src/be/nikiroo/utils/ui/ListModel.java b/src/be/nikiroo/utils/ui/ListModel.java
new file mode 100644
index 0000000..06e7914
--- /dev/null
+++ b/src/be/nikiroo/utils/ui/ListModel.java
@@ -0,0 +1,393 @@
+package be.nikiroo.utils.ui;
+
+import java.awt.Component;
+import java.awt.Point;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.JList;
+import javax.swing.JPopupMenu;
+import javax.swing.ListCellRenderer;
+
+import be.nikiroo.utils.compat.DefaultListModel6;
+import be.nikiroo.utils.compat.JList6;
+import be.nikiroo.utils.compat.ListCellRenderer6;
+
+/**
+ * A {@link javax.swing.ListModel} that can maintain 2 lists; one with the
+ * actual data (the elements), and a second one with the items that are
+ * currently displayed (the items).
+ *
+ * It also offers filter options, supports hovered changes and some more utility
+ * functions.
+ *
+ * @author niki
+ *
+ * @param
+ * the type of elements and items (the same type)
+ */
+public class ListModel extends DefaultListModel6 {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 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
+ * change the signatures -- but I support java 1.6+).
+ *
+ * @author niki
+ *
+ * @param
+ * the type of elements and items (the same type)
+ */
+ public interface Predicate {
+ /**
+ * Check if an item or an element pass a filter.
+ *
+ * @param item
+ * the item to test
+ *
+ * @return TRUE if the test passed, FALSE if not
+ */
+ public boolean test(T item);
+ }
+
+ /**
+ * A simple interface your elements must implement if you want to use
+ * {@link ListModel#generateRenderer(ListModel)}.
+ *
+ * @author niki
+ */
+ public interface Hoverable {
+ /**
+ * The element is currently selected.
+ *
+ * @param selected
+ * TRUE for selected, FALSE for unselected
+ */
+ public void setSelected(boolean selected);
+
+ /**
+ * The element is currently under the mouse cursor.
+ *
+ * @param hovered
+ * TRUE if it is, FALSE if not
+ */
+ public void setHovered(boolean hovered);
+ }
+
+ private int hoveredIndex;
+ private List items = new ArrayList();
+ private JList6 list;
+
+ /**
+ * Create a new {@link ListModel}.
+ *
+ * @param list
+ * the {@link JList} we will handle the data of (cannot be NULL)
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" }) // not compatible Java 1.6
+ public ListModel(JList list) {
+ this((JList6) list);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Create a new {@link ListModel}.
+ *
+ * @param list
+ * the {@link JList6} we will handle the data of (cannot be NULL)
+ */
+ public ListModel(JList6 list) {
+ this(list, null);
+ }
+
+ /**
+ * Create a new {@link ListModel}.
+ *
+ * @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)
+ */
+ public ListModel(final JList6 list, final JPopupMenu popup) {
+ this.list = list;
+ list.setModel(this);
+
+ list.addMouseMotionListener(new MouseAdapter() {
+ @Override
+ public void mouseMoved(MouseEvent me) {
+ if (popup != null && popup.isShowing())
+ return;
+
+ Point p = new Point(me.getX(), me.getY());
+ int index = list.locationToIndex(p);
+ if (index != hoveredIndex) {
+ int oldIndex = hoveredIndex;
+ hoveredIndex = index;
+ fireElementChanged(oldIndex);
+ fireElementChanged(index);
+ }
+ }
+ });
+
+ list.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ check(e);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ check(e);
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (popup != null && popup.isShowing())
+ return;
+
+ if (hoveredIndex > -1) {
+ int oldIndex = hoveredIndex;
+ hoveredIndex = -1;
+ fireElementChanged(oldIndex);
+ }
+ }
+
+ private void check(MouseEvent e) {
+ if (popup == null) {
+ return;
+ }
+
+ if (e.isPopupTrigger()) {
+ if (list.getSelectedIndices().length <= 1) {
+ list.setSelectedIndex(
+ list.locationToIndex(e.getPoint()));
+ }
+
+ popup.show(list, e.getX(), e.getY());
+ }
+ }
+ });
+ }
+
+ /**
+ * Check if this element is currently under the mouse.
+ *
+ * @param element
+ * the element to check
+ *
+ * @return TRUE if it is
+ */
+ public boolean isHovered(T element) {
+ return indexOf(element) == hoveredIndex;
+ }
+
+ /**
+ * Check if this element is currently under the mouse.
+ *
+ * @param index
+ * the index of the element to check
+ *
+ * @return TRUE if it is
+ */
+ public boolean isHovered(int index) {
+ return index == hoveredIndex;
+ }
+
+ /**
+ * Add an item to the model.
+ *
+ * @param item
+ * the new item to add
+ */
+ public void addItem(T item) {
+ items.add(item);
+ }
+
+ /**
+ * Add items to the model.
+ *
+ * @param items
+ * the new items to add
+ */
+ public void addAllItems(Collection items) {
+ this.items.addAll(items);
+ }
+
+ /**
+ * Removes the first occurrence of the specified element from this list, if
+ * it is present (optional operation).
+ *
+ * @param item
+ * the item to remove if possible (can be NULL)
+ *
+ * @return TRUE if one element was removed, FALSE if not found
+ */
+ public boolean removeItem(T item) {
+ return items.remove(item);
+ }
+
+ /**
+ * Remove the items that pass the given filter (or all items if the filter
+ * is NULL).
+ *
+ * @param filter
+ * the filter (if the filter returns TRUE, the item will be
+ * removed)
+ *
+ * @return TRUE if at least one item was removed
+ */
+ public boolean removeItemIf(Predicate filter) {
+ boolean changed = false;
+ if (filter == null) {
+ changed = !items.isEmpty();
+ clearItems();
+ } else {
+ for (int i = 0; i < items.size(); i++) {
+ if (filter.test(items.get(i))) {
+ items.remove(i--);
+ changed = true;
+ }
+ }
+ }
+
+ return changed;
+ }
+
+ /**
+ * Removes all the items from this model.
+ */
+ public void clearItems() {
+ items.clear();
+ }
+
+ /**
+ * Filter the current elements.
+ *
+ * This method will clear all the elements then look into all the items:
+ * those that pass the given filter will be copied as elements.
+ *
+ * @param filter
+ * the filter to select which elements to keep; an item that pass
+ * 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
+ public void filter(Predicate filter) {
+ clear();
+ for (T item : items) {
+ if (filter == null || filter.test(item)) {
+ addElement(item);
+ }
+ }
+
+ list.repaint();
+ }
+
+ /**
+ * Return the currently selected elements.
+ *
+ * @return the selected elements
+ */
+ public List getSelectedElements() {
+ List selected = new ArrayList();
+ for (int index : list.getSelectedIndices()) {
+ selected.add(get(index));
+ }
+
+ return selected;
+ }
+
+ /**
+ * Return the selected element if one and only one element is
+ * selected. I.E., if zero, two or more elements are selected, NULL will be
+ * returned.
+ *
+ * @return the element if it is the only selected element, NULL otherwise
+ */
+ public T getUniqueSelectedElement() {
+ List selected = getSelectedElements();
+ if (selected.size() == 1) {
+ return selected.get(0);
+ }
+
+ return null;
+ }
+
+ /**
+ * Notify that this element has been changed.
+ *
+ * @param index
+ * the index of the element
+ */
+ public void fireElementChanged(int index) {
+ if (index >= 0) {
+ fireContentsChanged(this, index, index);
+ }
+ }
+
+ /**
+ * Notify that this element has been changed.
+ *
+ * @param element
+ * the element
+ */
+ public void fireElementChanged(T element) {
+ int index = indexOf(element);
+ if (index >= 0) {
+ fireContentsChanged(this, index, index);
+ }
+ }
+
+ @SuppressWarnings("unchecked") // ListModel and JList are not java 1.6
+ @Override
+ public T get(int index) {
+ return (T) super.get(index);
+ }
+
+ /**
+ * Generate a {@link ListCellRenderer} that supports {@link Hoverable}
+ * elements.
+ *
+ * @param
+ * the type of elements and items (the same type), which should
+ * implement {@link Hoverable} (it will not cause issues if not,
+ * but then, it will be a default renderer)
+ * @param model
+ * the model to use
+ *
+ * @return a suitable, {@link Hoverable} compatible renderer
+ */
+ static public ListCellRenderer6 generateRenderer(
+ final ListModel model) {
+ return new ListCellRenderer6() {
+ @Override
+ public Component getListCellRendererComponent(JList6 list,
+ T item, int index, boolean isSelected,
+ boolean cellHasFocus) {
+ if (item instanceof Hoverable) {
+ Hoverable hoverable = (Hoverable) item;
+ hoverable.setSelected(isSelected);
+ hoverable.setHovered(model.isHovered(index));
+ }
+
+ return item;
+ }
+ };
+ }
+}