View Javadoc

1   /*
2   
3       dsh-venn  Lightweight components for venn diagrams.
4       Copyright (c) 2009-2013 held jointly by the individual authors.
5   
6       This library is free software; you can redistribute it and/or modify it
7       under the terms of the GNU Lesser General Public License as published
8       by the Free Software Foundation; either version 3 of the License, or (at
9       your option) any later version.
10  
11      This library is distributed in the hope that it will be useful, but WITHOUT
12      ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
13      FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14      License for more details.
15  
16      You should have received a copy of the GNU Lesser General Public License
17      along with this library;  if not, write to the Free Software Foundation,
18      Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA.
19  
20      > http://www.fsf.org/licensing/licenses/lgpl.html
21      > http://www.opensource.org/licenses/lgpl-license.php
22  
23  */
24  package org.dishevelled.venn.swing;
25  
26  import java.awt.GridLayout;
27  
28  import java.beans.PropertyChangeEvent;
29  import java.beans.PropertyChangeListener;
30  
31  import java.util.List;
32  import java.util.Set;
33  
34  import javax.swing.Box;
35  import javax.swing.JList;
36  import javax.swing.JPanel;
37  import javax.swing.JScrollPane;
38  import javax.swing.ListSelectionModel;
39  
40  import javax.swing.event.ListSelectionEvent;
41  import javax.swing.event.ListSelectionListener;
42  
43  import ca.odell.glazedlists.swing.EventListModel;
44  
45  import org.dishevelled.layout.LabelFieldPanel;
46  
47  import org.dishevelled.observable.event.SetChangeEvent;
48  import org.dishevelled.observable.event.SetChangeListener;
49  
50  import org.dishevelled.venn.BinaryVennModel;
51  
52  /**
53   * Binary venn diagram list.
54   *
55   * @param <E> value type
56   * @author  Michael Heuer
57   * @version $Revision$ $Date$
58   */
59  public final class BinaryVennList<E>
60      extends AbstractBinaryVennDiagram<E>
61  {
62      /** Contents of the first set. */
63      private final JList first = new JList();
64  
65      /** Contents of the second set. */
66      private final JList second = new JList();
67  
68      /** Contents of the first only view. */
69      private final JList firstOnly = new JList();
70  
71      /** Contents for the second only view. */
72      private final JList secondOnly = new JList();
73  
74      /** Contents of the intersection view. */
75      private final JList intersection = new JList();
76  
77      /** Contents of the union view. */
78      private final JList union = new JList();
79  
80      /** Adapter for the first list model. */
81      private ObservableSetEventListAdapter<E> firstAdapter;
82  
83      /** Adapter for the second list model. */
84      private ObservableSetEventListAdapter<E> secondAdapter;
85  
86      /** Adapter for the first only list model. */
87      private SetEventListAdapter<E> firstOnlyAdapter;
88  
89      /** Adapter for the second only list model. */
90      private SetEventListAdapter<E> secondOnlyAdapter;
91  
92      /** Adapter for the intersection list model. */
93      private SetEventListAdapter<E> intersectionAdapter;
94  
95      /** Adapter for the union list model. */
96      private SetEventListAdapter<E> unionAdapter;
97  
98      /** Model change listener. */
99      private final PropertyChangeListener modelChange = new PropertyChangeListener()
100     {
101         /** {@inheritDoc} */
102         public void propertyChange(final PropertyChangeEvent event)
103         {
104             uninstallListModels((BinaryVennModel<E>) event.getOldValue());
105             installListModels();
106         }
107     };
108 
109     /** Update list models from model. */
110     private final SetChangeListener<E> updateListModels = new SetChangeListener<E>()
111     {
112         /** {@inheritDoc} */
113         public void setChanged(final SetChangeEvent<E> event)
114         {
115             updateListModels();
116         }
117     };
118 
119     /** Update list selection from model. */
120     private final SetChangeListener<E> updateSelection = new SetChangeListener<E>()
121     {
122         /** {@inheritDoc} */
123         public void setChanged(final SetChangeEvent<E> event)
124         {
125             updateSelection();
126         }
127     };
128 
129 
130     /**
131      * Create a new empty binary venn list.
132      */
133     public BinaryVennList()
134     {
135         super();
136         installListModels();
137         installSelectionListeners();
138         layoutComponents();
139         addPropertyChangeListener("model", modelChange);
140     }
141 
142     /**
143      * Create a new binary venn list with the specified sets.
144      *
145      * @param firstLabelText label text for the first set
146      * @param first first set, must not be null
147      * @param secondLabelText label text for the second set
148      * @param second second set, must not be null
149      */
150     public BinaryVennList(final String firstLabelText, final Set<? extends E> first,
151         final String secondLabelText, final Set<? extends E> second)
152     {
153         super(firstLabelText, first, secondLabelText, second);
154         installListModels();
155         installSelectionListeners();
156         layoutComponents();
157         addPropertyChangeListener("model", modelChange);
158     }
159 
160     /**
161      * Create a new binary venn list with the specified model.
162      *
163      * @param model model for this binary venn list, must not be null
164      */
165     public BinaryVennList(final BinaryVennModel<E> model)
166     {
167         super(model);
168         installListModels();
169         installSelectionListeners();
170         layoutComponents();
171         addPropertyChangeListener("model", modelChange);
172     }
173 
174 
175     // todo:  keyboard copy & paste does not work as expected always
176 
177     /**
178      * Clear selection.
179      */
180     public void clearSelection()
181     {
182         union.requestFocusInWindow();
183         getModel().selection().clear();
184     }
185 
186     /**
187      * Select all.
188      */
189     public void selectAll()
190     {
191         union.requestFocusInWindow();
192         // todo:  dreadfully inefficient
193         getModel().selection().addAll(getModel().union());
194     }
195 
196     /**
197      * Return the contents of the first set.  The model for the returned
198      * JList should not be changed, as the current model implementation is
199      * synchronized to the binary venn model backing this venn diagram.
200      *
201      * @return the contents of the first set
202      */
203     public JList getFirst()
204     {
205         return first;
206     }
207 
208     /**
209      * Return the contents of the second set.  The model for the returned
210      * JList should not be changed, as the current model implementation is
211      * synchronized to the binary venn model backing this venn diagram.
212      *
213      * @return the contents of the second set
214      */
215     public JList getSecond()
216     {
217         return second;
218     }
219 
220     /**
221      * Return the contents of the first only view.  The model for the returned
222      * JList should not be changed, as the current model implementation is
223      * synchronized to the binary venn model backing this venn diagram.
224      *
225      * @return the contents of the first only view
226      */
227     public JList getFirstOnly()
228     {
229         return firstOnly;
230     }
231 
232     /**
233      * Return the contents of the second only view.  The model for the returned
234      * JList should not be changed, as the current model implementation is
235      * synchronized to the binary venn model backing this venn diagram.
236      *
237      * @return the contents of the second only view
238      */
239     public JList getSecondOnly()
240     {
241         return secondOnly;
242     }
243 
244     /**
245      * Return the contents of the intersection view.  The model for the returned
246      * JList should not be changed, as the current model implementation is
247      * synchronized to the binary venn model backing this venn diagram.
248      *
249      * @return the contents of the intersection view
250      */
251     public JList getIntersection()
252     {
253         return intersection;
254     }
255 
256     /**
257      * Return the contents of the union view.  The model for the returned
258      * JList should not be changed, as the current model implementation is
259      * synchronized to the binary venn model backing this venn diagram.
260      *
261      * @return the contents of the union view
262      */
263     public JList getUnion()
264     {
265         return union;
266     }
267 
268     /**
269      * Install list models.
270      */
271     private void installListModels()
272     {
273         firstAdapter = new ObservableSetEventListAdapter<E>(getModel().first());
274         first.setModel(new EventListModel<E>(firstAdapter));
275         secondAdapter = new ObservableSetEventListAdapter<E>(getModel().second());
276         second.setModel(new EventListModel<E>(secondAdapter));
277 
278         firstOnlyAdapter = new SetEventListAdapter<E>(getModel().firstOnly());
279         firstOnly.setModel(new EventListModel<E>(firstOnlyAdapter));
280         secondOnlyAdapter = new SetEventListAdapter<E>(getModel().secondOnly());
281         secondOnly.setModel(new EventListModel<E>(secondOnlyAdapter));
282         intersectionAdapter = new SetEventListAdapter<E>(getModel().intersection());
283         intersection.setModel(new EventListModel<E>(intersectionAdapter));
284         unionAdapter = new SetEventListAdapter<E>(getModel().union());
285         union.setModel(new EventListModel<E>(unionAdapter));
286 
287         getModel().first().addSetChangeListener(updateListModels);
288         getModel().second().addSetChangeListener(updateListModels);
289         getModel().first().addSetChangeListener(updateSelection);
290         getModel().second().addSetChangeListener(updateSelection);
291         getModel().selection().addSetChangeListener(updateSelection);
292     }
293 
294     /**
295      * Update list models.
296      */
297     private void updateListModels()
298     {
299         firstOnlyAdapter.updateEventList();
300         secondOnlyAdapter.updateEventList();
301         intersectionAdapter.updateEventList();
302         unionAdapter.updateEventList();
303     }
304 
305     /**
306      * Uninstall list models.
307      *
308      * @param oldModel old model
309      */
310     private void uninstallListModels(final BinaryVennModel<E> oldModel)
311     {
312         firstAdapter.dispose();
313         secondAdapter.dispose();
314         ((EventListModel<E>) first.getModel()).dispose();
315         ((EventListModel<E>) second.getModel()).dispose();
316         ((EventListModel<E>) firstOnly.getModel()).dispose();
317         ((EventListModel<E>) secondOnly.getModel()).dispose();
318         ((EventListModel<E>) intersection.getModel()).dispose();
319         ((EventListModel<E>) union.getModel()).dispose();
320         oldModel.first().removeSetChangeListener(updateListModels);
321         oldModel.second().removeSetChangeListener(updateListModels);
322         oldModel.first().removeSetChangeListener(updateSelection);
323         oldModel.second().removeSetChangeListener(updateSelection);
324         oldModel.selection().removeSetChangeListener(updateSelection);
325     }
326 
327     /**
328      * Install selection listeners.
329      */
330     private void installSelectionListeners()
331     {
332         first.addListSelectionListener(new UpdateSelectionView());
333         second.addListSelectionListener(new UpdateSelectionView());
334         firstOnly.addListSelectionListener(new UpdateSelectionView());
335         secondOnly.addListSelectionListener(new UpdateSelectionView());
336         intersection.addListSelectionListener(new UpdateSelectionView());
337         union.addListSelectionListener(new UpdateSelectionView());
338     }
339 
340     /** {@inheritDoc} */
341     protected void updateContents()
342     {
343         // empty
344     }
345 
346     /**
347      * Layout components.
348      */
349     private void layoutComponents()
350     {
351         addFinalField(createMainPanel());
352     }
353 
354     /**
355      * Create and return the main panel.
356      *
357      * @return the main panel
358      */
359     private JPanel createMainPanel()
360     {
361         JPanel panel = new JPanel();
362         panel.setLayout(new GridLayout(2, 4, 12, 12));
363 
364         LabelFieldPanel f = new LabelFieldPanel();
365         f.addLabel(getFirstLabel());
366         f.addFinalField(new JScrollPane(first));
367         panel.add(f);
368 
369         LabelFieldPanel s = new LabelFieldPanel();
370         s.addLabel(getSecondLabel());
371         s.addFinalField(new JScrollPane(second));
372         panel.add(s);
373 
374         panel.add(Box.createGlue());
375         panel.add(Box.createGlue());
376 
377         LabelFieldPanel fo = new LabelFieldPanel();
378         fo.addLabel(getFirstOnlyLabel());
379         fo.addFinalField(new JScrollPane(firstOnly));
380         panel.add(fo);
381 
382         LabelFieldPanel so = new LabelFieldPanel();
383         so.addLabel(getSecondOnlyLabel());
384         so.addFinalField(new JScrollPane(secondOnly));
385         panel.add(so);
386 
387         LabelFieldPanel n = new LabelFieldPanel();
388         n.addLabel(getIntersectionLabel());
389         n.addFinalField(new JScrollPane(intersection));
390         panel.add(n);
391 
392         LabelFieldPanel u = new LabelFieldPanel();
393         u.addLabel(getUnionLabel());
394         u.addFinalField(new JScrollPane(union));
395         panel.add(u);
396 
397         return panel;
398     }
399 
400     /**
401      * Update list selection from the selection view in the model.
402      */
403     private void updateSelection()
404     {
405         if (getModel().selection().isEmpty())
406         {
407             clearSelection(first);
408             clearSelection(second);
409             clearSelection(firstOnly);
410             clearSelection(secondOnly);
411             clearSelection(intersection);
412             clearSelection(union);
413         }
414         else
415         {
416             // todo:  need element(s) that were added from set change event
417             for (E e : getModel().selection())
418             {
419                 addToSelection(getModel().first(), first, firstAdapter, e);
420                 addToSelection(getModel().second(), second, secondAdapter, e);
421                 addToSelection(getModel().firstOnly(), firstOnly, firstOnlyAdapter, e);
422                 addToSelection(getModel().secondOnly(), secondOnly, secondOnlyAdapter, e);
423                 addToSelection(getModel().intersection(), intersection, intersectionAdapter, e);
424                 addToSelection(getModel().union(), union, unionAdapter, e);
425             }
426 
427             removeFromSelection(getModel().first(), first, firstAdapter);
428             removeFromSelection(getModel().second(), second, secondAdapter);
429             removeFromSelection(getModel().firstOnly(), firstOnly, firstOnlyAdapter);
430             removeFromSelection(getModel().secondOnly(), secondOnly, secondOnlyAdapter);
431             removeFromSelection(getModel().intersection(), intersection, intersectionAdapter);
432             removeFromSelection(getModel().union(), union, unionAdapter);
433         }
434     }
435 
436     /**
437      * Clear the selection for the specified list if it is not a focus owner.
438      *
439      * @param list list
440      */
441     private void clearSelection(final JList list)
442     {
443         if (!list.isFocusOwner())
444         {
445             list.clearSelection();
446         }
447     }
448 
449     /**
450      * Add the specified element to the list selection if it is contained
451      * in the specified model and the list is not a focus owner.
452      *
453      * @param model model
454      * @param list list
455      * @param adapter adapter
456      * @param e element
457      */
458     private void addToSelection(final Set<E> model, final JList list, final List<E> adapter, final E e)
459     {
460         if (!list.isFocusOwner() && model.contains(e))
461         {
462             int index = adapter.indexOf(e);
463             list.getSelectionModel().addSelectionInterval(index, index);
464         }
465     }
466 
467     /**
468      * Remove elements from the list selection if they are not present in
469      * the specified model and the list is not a focus owner.
470      *
471      * @param model model
472      * @param list list
473      * @param adapter adapter
474      */
475     private void removeFromSelection(final Set<E> model, final JList list, final List<E> adapter)
476     {
477         if (!list.isFocusOwner())
478         {
479             // todo:  need element(s) that were removed from set change event
480             for (E e : model)
481             {
482                 if (!getModel().selection().contains(e))
483                 {
484                     int index = adapter.indexOf(e);
485                     list.getSelectionModel().removeSelectionInterval(index, index);
486                 }
487             }
488         }
489     }
490 
491     /**
492      * Update selection view.
493      */
494     private class UpdateSelectionView implements ListSelectionListener
495     {
496         /** {@inheritDoc} */
497         public void valueChanged(final ListSelectionEvent event)
498         {
499             JList list = (JList) event.getSource();
500             if (list.isFocusOwner() && !event.getValueIsAdjusting())
501             {
502                 ListSelectionModel selectionModel = list.getSelectionModel();
503                 for (int index = event.getFirstIndex(); index < (event.getLastIndex() + 1); index++)
504                 {
505                     E e = (E) list.getModel().getElementAt(index);
506                     if (selectionModel.isSelectedIndex(index))
507                     {
508                         if (!getModel().selection().contains(e))
509                         {
510                             getModel().selection().add(e);
511                         }
512                     }
513                     else
514                     {
515                         if (getModel().selection().contains(e))
516                         {
517                             getModel().selection().remove(e);
518                         }
519                     }
520                 }
521             }
522             // todo:  may need to remove from selection view those
523             //    elements not present in model for focused list
524             //    e.g.  say "bar" is selected, select "foo" in First only
525         }
526     }
527 }