1 package be
.nikiroo
.utils
.ui
;
3 import java
.awt
.Component
;
5 import java
.awt
.event
.MouseAdapter
;
6 import java
.awt
.event
.MouseEvent
;
7 import java
.util
.ArrayList
;
8 import java
.util
.Collection
;
11 import javax
.swing
.JList
;
12 import javax
.swing
.JPopupMenu
;
13 import javax
.swing
.ListCellRenderer
;
15 import be
.nikiroo
.utils
.compat
.DefaultListModel6
;
16 import be
.nikiroo
.utils
.compat
.JList6
;
17 import be
.nikiroo
.utils
.compat
.ListCellRenderer6
;
20 * A {@link javax.swing.ListModel} that can maintain 2 lists; one with the
21 * actual data (the elements), and a second one with the items that are
22 * currently displayed (the items).
24 * It also offers filter options, supports hovered changes and some more utility
30 * the type of elements and items (the same type)
32 public class ListModel
<T
> extends DefaultListModel6
<T
> {
33 private static final long serialVersionUID
= 1L;
36 * A filter interface, to check for a condition (note that a Predicate class
37 * already exists in Java 1.8+, and is compatible with this one if you
38 * change the signatures -- but I support java 1.6+).
43 * the type of elements and items (the same type)
45 public interface Predicate
<T
> {
47 * Check if an item or an element pass a filter.
52 * @return TRUE if the test passed, FALSE if not
54 public boolean test(T item
);
58 * A simple interface your elements must implement if you want to use
59 * {@link ListModel#generateRenderer(ListModel)}.
63 public interface Hoverable
{
65 * The element is currently selected.
68 * TRUE for selected, FALSE for unselected
70 public void setSelected(boolean selected
);
73 * The element is currently under the mouse cursor.
76 * TRUE if it is, FALSE if not
78 public void setHovered(boolean hovered
);
81 private int hoveredIndex
;
82 private List
<T
> items
= new ArrayList
<T
>();
83 private JList6
<T
> list
;
86 * Create a new {@link ListModel}.
89 * the {@link JList} we will handle the data of (cannot be NULL)
91 @SuppressWarnings({ "unchecked", "rawtypes" }) // not compatible Java 1.6
92 public ListModel(JList list
) {
93 this((JList6
<T
>) list
);
97 * Create a new {@link ListModel}.
100 * the {@link JList} we will handle the data of (cannot be NULL)
102 * the popup to use and keep track of (can be NULL)
104 @SuppressWarnings({ "unchecked", "rawtypes" }) // not compatible Java 1.6
105 public ListModel(final JList list
, final JPopupMenu popup
) {
106 this((JList6
<T
>) list
, popup
);
110 * Create a new {@link ListModel}.
113 * the {@link JList6} we will handle the data of (cannot be NULL)
115 public ListModel(JList6
<T
> list
) {
120 * Create a new {@link ListModel}.
123 * the {@link JList6} we will handle the data of (cannot be NULL)
125 * the popup to use and keep track of (can be NULL)
127 public ListModel(final JList6
<T
> list
, final JPopupMenu popup
) {
131 list
.addMouseMotionListener(new MouseAdapter() {
133 public void mouseMoved(MouseEvent me
) {
134 if (popup
!= null && popup
.isShowing())
137 Point p
= new Point(me
.getX(), me
.getY());
138 int index
= list
.locationToIndex(p
);
139 if (index
!= hoveredIndex
) {
140 int oldIndex
= hoveredIndex
;
141 hoveredIndex
= index
;
142 fireElementChanged(oldIndex
);
143 fireElementChanged(index
);
148 list
.addMouseListener(new MouseAdapter() {
150 public void mousePressed(MouseEvent e
) {
155 public void mouseReleased(MouseEvent e
) {
160 public void mouseExited(MouseEvent e
) {
161 if (popup
!= null && popup
.isShowing())
164 if (hoveredIndex
> -1) {
165 int oldIndex
= hoveredIndex
;
167 fireElementChanged(oldIndex
);
171 private void check(MouseEvent e
) {
176 if (e
.isPopupTrigger()) {
177 if (list
.getSelectedIndices().length
<= 1) {
178 list
.setSelectedIndex(
179 list
.locationToIndex(e
.getPoint()));
182 popup
.show(list
, e
.getX(), e
.getY());
189 * Check if this element is currently under the mouse.
192 * the element to check
194 * @return TRUE if it is
196 public boolean isHovered(T element
) {
197 return indexOf(element
) == hoveredIndex
;
201 * Check if this element is currently under the mouse.
204 * the index of the element to check
206 * @return TRUE if it is
208 public boolean isHovered(int index
) {
209 return index
== hoveredIndex
;
213 * Add an item to the model.
216 * the new item to add
218 public void addItem(T item
) {
223 * Add items to the model.
226 * the new items to add
228 public void addAllItems(Collection
<T
> items
) {
229 this.items
.addAll(items
);
233 * Removes the first occurrence of the specified element from this list, if
234 * it is present (optional operation).
237 * the item to remove if possible (can be NULL)
239 * @return TRUE if one element was removed, FALSE if not found
241 public boolean removeItem(T item
) {
242 return items
.remove(item
);
246 * Remove the items that pass the given filter (or all items if the filter
250 * the filter (if the filter returns TRUE, the item will be
253 * @return TRUE if at least one item was removed
255 public boolean removeItemIf(Predicate
<T
> filter
) {
256 boolean changed
= false;
257 if (filter
== null) {
258 changed
= !items
.isEmpty();
261 for (int i
= 0; i
< items
.size(); i
++) {
262 if (filter
.test(items
.get(i
))) {
273 * Removes all the items from this model.
275 public void clearItems() {
280 * Filter the current elements.
282 * This method will clear all the elements then look into all the items:
283 * those that pass the given filter will be copied as elements.
286 * the filter to select which elements to keep; an item that pass
287 * the filter will be copied as an element (can be NULL, in that
288 * case all items will be copied as elements)
290 @SuppressWarnings("unchecked") // ListModel<T> and JList<T> are not java 1.6
291 public void filter(Predicate
<T
> filter
) {
293 for (T item
: items
) {
294 if (filter
== null || filter
.test(item
)) {
303 * Return the currently selected elements.
305 * @return the selected elements
307 public List
<T
> getSelectedElements() {
308 List
<T
> selected
= new ArrayList
<T
>();
309 for (int index
: list
.getSelectedIndices()) {
310 selected
.add(get(index
));
317 * Return the selected element if <b>one</b> and <b>only one</b> element is
318 * selected. I.E., if zero, two or more elements are selected, NULL will be
321 * @return the element if it is the only selected element, NULL otherwise
323 public T
getUniqueSelectedElement() {
324 List
<T
> selected
= getSelectedElements();
325 if (selected
.size() == 1) {
326 return selected
.get(0);
333 * Notify that this element has been changed.
336 * the index of the element
338 public void fireElementChanged(int index
) {
340 fireContentsChanged(this, index
, index
);
345 * Notify that this element has been changed.
350 public void fireElementChanged(T element
) {
351 int index
= indexOf(element
);
353 fireContentsChanged(this, index
, index
);
357 @SuppressWarnings("unchecked") // ListModel<T> and JList<T> are not java 1.6
359 public T
get(int index
) {
360 return (T
) super.get(index
);
364 * Generate a {@link ListCellRenderer} that supports {@link Hoverable}
368 * the type of elements and items (the same type), which should
369 * implement {@link Hoverable} (it will not cause issues if not,
370 * but then, it will be a default renderer)
374 * @return a suitable, {@link Hoverable} compatible renderer
376 static public <T
extends Component
> ListCellRenderer6
<T
> generateRenderer(
377 final ListModel
<T
> model
) {
378 return new ListCellRenderer6
<T
>() {
380 public Component
getListCellRendererComponent(JList6
<T
> list
,
381 T item
, int index
, boolean isSelected
,
382 boolean cellHasFocus
) {
383 if (item
instanceof Hoverable
) {
384 Hoverable hoverable
= (Hoverable
) item
;
385 hoverable
.setSelected(isSelected
);
386 hoverable
.setHovered(model
.isHovered(index
));