e349deef344a0bdbe33078da7c5cd7fdc19edbce
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 boolean keepSelection
= true;
85 @SuppressWarnings("rawtypes") // JList<?> not compatible Java 1.6
89 * Create a new {@link ListModel}.
92 * the {@link JList6} we will handle the data of (cannot be NULL)
94 @SuppressWarnings("rawtypes") // JList<?> not compatible Java 1.6
95 public ListModel(JList6
<T
> list
) {
100 * Create a new {@link ListModel}.
103 * the {@link JList6} we will handle the data of (cannot be NULL)
105 * the popup to use and keep track of (can be NULL)
107 @SuppressWarnings("rawtypes") // JList<?> not compatible Java 1.6
108 public ListModel(final JList6
<T
> list
, final JPopupMenu popup
) {
109 this((JList
) list
, popup
);
113 * Create a new {@link ListModel}.
115 * Note that you must take care of passing a {@link JList} that only handles
116 * elements of the type of this {@link ListModel} -- you can also use
117 * {@link ListModel#ListModel(JList6)} instead.
120 * the {@link JList} we will handle the data of (cannot be NULL,
121 * must only contain elements of the type of this
124 @SuppressWarnings("rawtypes") // JList<?> not compatible Java 1.6
125 public ListModel(JList list
) {
130 * Create a new {@link ListModel}.
132 * Note that you must take care of passing a {@link JList} that only handles
133 * elements of the type of this {@link ListModel} -- you can also use
134 * {@link ListModel#ListModel(JList6, JPopupMenu)} instead.
137 * the {@link JList} we will handle the data of (cannot be NULL,
138 * must only contain elements of the type of this
141 * the popup to use and keep track of (can be NULL)
143 @SuppressWarnings({ "unchecked", "rawtypes" }) // JList<?> not in Java 1.6
144 public ListModel(final JList list
, final JPopupMenu popup
) {
148 list
.addMouseMotionListener(new MouseAdapter() {
150 public void mouseMoved(MouseEvent me
) {
151 if (popup
!= null && popup
.isShowing())
154 Point p
= new Point(me
.getX(), me
.getY());
155 int index
= list
.locationToIndex(p
);
156 if (index
!= hoveredIndex
) {
157 int oldIndex
= hoveredIndex
;
158 hoveredIndex
= index
;
159 fireElementChanged(oldIndex
);
160 fireElementChanged(index
);
165 list
.addMouseListener(new MouseAdapter() {
167 public void mousePressed(MouseEvent e
) {
172 public void mouseReleased(MouseEvent e
) {
177 public void mouseExited(MouseEvent e
) {
178 if (popup
!= null && popup
.isShowing())
181 if (hoveredIndex
> -1) {
182 int oldIndex
= hoveredIndex
;
184 fireElementChanged(oldIndex
);
188 private void check(MouseEvent e
) {
193 if (e
.isPopupTrigger()) {
194 if (list
.getSelectedIndices().length
<= 1) {
195 list
.setSelectedIndex(
196 list
.locationToIndex(e
.getPoint()));
199 popup
.show(list
, e
.getX(), e
.getY());
206 * (Try and) keep the elements that were selected when filtering.
208 * This will use toString on the elements to identify them, and can be a bit
209 * resource intensive.
211 * @return TRUE if we do
213 public boolean isKeepSelection() {
214 return keepSelection
;
218 * (Try and) keep the elements that were selected when filtering.
220 * This will use toString on the elements to identify them, and can be a bit
221 * resource intensive.
223 * @param keepSelection
224 * TRUE to try and keep them selected
226 public void setKeepSelection(boolean keepSelection
) {
227 this.keepSelection
= keepSelection
;
231 * Check if this element is currently under the mouse.
234 * the element to check
236 * @return TRUE if it is
238 public boolean isHovered(T element
) {
239 return indexOf(element
) == hoveredIndex
;
243 * Check if this element is currently under the mouse.
246 * the index of the element to check
248 * @return TRUE if it is
250 public boolean isHovered(int index
) {
251 return index
== hoveredIndex
;
255 * Add an item to the model.
258 * the new item to add
260 public void addItem(T item
) {
265 * Add items to the model.
268 * the new items to add
270 public void addAllItems(Collection
<T
> items
) {
271 this.items
.addAll(items
);
275 * Removes the first occurrence of the specified element from this list, if
276 * it is present (optional operation).
279 * the item to remove if possible (can be NULL)
281 * @return TRUE if one element was removed, FALSE if not found
283 public boolean removeItem(T item
) {
284 return items
.remove(item
);
288 * Remove the items that pass the given filter (or all items if the filter
292 * the filter (if the filter returns TRUE, the item will be
295 * @return TRUE if at least one item was removed
297 public boolean removeItemIf(Predicate
<T
> filter
) {
298 boolean changed
= false;
299 if (filter
== null) {
300 changed
= !items
.isEmpty();
303 for (int i
= 0; i
< items
.size(); i
++) {
304 if (filter
.test(items
.get(i
))) {
315 * Removes all the items from this model.
317 public void clearItems() {
322 * Filter the current elements.
324 * This method will clear all the elements then look into all the items:
325 * those that pass the given filter will be copied as elements.
328 * the filter to select which elements to keep; an item that pass
329 * the filter will be copied as an element (can be NULL, in that
330 * case all items will be copied as elements)
332 @SuppressWarnings("unchecked") // JList<?> not compatible Java 1.6
333 public void filter(Predicate
<T
> filter
) {
334 ListSnapshot snapshot
= null;
337 snapshot
= new ListSnapshot(list
);
340 for (T item
: items
) {
341 if (filter
== null || filter
.test(item
)) {
353 * Return the currently selected elements.
355 * @return the selected elements
357 public List
<T
> getSelectedElements() {
358 List
<T
> selected
= new ArrayList
<T
>();
359 for (int index
: list
.getSelectedIndices()) {
360 selected
.add(get(index
));
367 * Return the selected element if <b>one</b> and <b>only one</b> element is
368 * selected. I.E., if zero, two or more elements are selected, NULL will be
371 * @return the element if it is the only selected element, NULL otherwise
373 public T
getUniqueSelectedElement() {
374 List
<T
> selected
= getSelectedElements();
375 if (selected
.size() == 1) {
376 return selected
.get(0);
383 * Notify that this element has been changed.
386 * the index of the element
388 public void fireElementChanged(int index
) {
390 fireContentsChanged(this, index
, index
);
395 * Notify that this element has been changed.
400 public void fireElementChanged(T element
) {
401 int index
= indexOf(element
);
403 fireContentsChanged(this, index
, index
);
407 @SuppressWarnings("unchecked") // JList<?> not compatible Java 1.6
409 public T
get(int index
) {
410 return (T
) super.get(index
);
414 * Generate a {@link ListCellRenderer} that supports {@link Hoverable}
418 * the type of elements and items (the same type), which should
419 * implement {@link Hoverable} (it will not cause issues if not,
420 * but then, it will be a default renderer)
424 * @return a suitable, {@link Hoverable} compatible renderer
426 static public <T
extends Component
> ListCellRenderer6
<T
> generateRenderer(
427 final ListModel
<T
> model
) {
428 return new ListCellRenderer6
<T
>() {
430 public Component
getListCellRendererComponent(JList6
<T
> list
,
431 T item
, int index
, boolean isSelected
,
432 boolean cellHasFocus
) {
433 if (item
instanceof Hoverable
) {
434 Hoverable hoverable
= (Hoverable
) item
;
435 hoverable
.setSelected(isSelected
);
436 hoverable
.setHovered(model
.isHovered(index
));