KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > edu > rice > cs > util > docnavigation > JListNavigator


1 /*BEGIN_COPYRIGHT_BLOCK
2  *
3  * This file is part of DrJava. Download the current version of this project:
4  * http://sourceforge.net/projects/drjava/ or http://www.drjava.org/
5  *
6  * DrJava Open Source License
7  *
8  * Copyright (C) 2001-2003 JavaPLT group at Rice University (javaplt@rice.edu)
9  * All rights reserved.
10  *
11  * Developed by: Java Programming Languages Team
12  * Rice University
13  * http://www.cs.rice.edu/~javaplt/
14  *
15  * Permission is hereby granted, free of charge, to any person obtaining a
16  * copy of this software and associated documentation files (the "Software"),
17  * to deal with the Software without restriction, including without
18  * limitation the rights to use, copy, modify, merge, publish, distribute,
19  * sublicense, and/or sell copies of the Software, and to permit persons to
20  * whom the Software is furnished to do so, subject to the following
21  * conditions:
22  *
23  * - Redistributions of source code must retain the above copyright
24  * notice, this list of conditions and the following disclaimers.
25  * - Redistributions in binary form must reproduce the above copyright
26  * notice, this list of conditions and the following disclaimers in the
27  * documentation and/or other materials provided with the distribution.
28  * - Neither the names of DrJava, the JavaPLT, Rice University, nor the
29  * names of its contributors may be used to endorse or promote products
30  * derived from this Software without specific prior written permission.
31  * - Products derived from this software may not be called "DrJava" nor
32  * use the term "DrJava" as part of their names without prior written
33  * permission from the JavaPLT group. For permission, write to
34  *
35  * javaplt@rice.edu.
36  *
37  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
38  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
39  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
40  * THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
41  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
42  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
43  * OTHER DEALINGS WITH THE SOFTWARE.
44  *
45  END_COPYRIGHT_BLOCK*/

46
47 package edu.rice.cs.util.docnavigation;
48
49 import java.awt.*;
50 import javax.swing.*;
51 import javax.swing.event.*;
52 import java.util.*;
53 import edu.rice.cs.util.swing.Utilities;
54 //import edu.rice.cs.util.swing.RightClickMouseAdapter;
55

56 /** This class is an extension of JList that adds data shadowing the model embedded in a JList.
57  * Since all changes to the model (except for the selected item!) must go through this interface,
58  * we can support access to methods from non-event threads as long as these methods do not modify
59  * the model. However, all of the public methods that access and modify the model (the latter only running
60  * in the event thread) must be atomic relative to each other, so synchronization is required in most
61  * cases.
62  *
63  * TODO: generify this class and IDocumentNavigator with respect to its element type once JList is.
64  */

65
66 class JListNavigator<ItemT extends INavigatorItem> extends JList implements IDocumentNavigator<ItemT> {
67   
68   /** The list model (extending AbstractListModel) for this JList. */
69   protected DefaultListModel _model;
70   
71   /** The current selection value. A cached copy of getSelectedValue(). */
72   private volatile ItemT _current = null;
73   
74 // /** The index of _current */
75
// private int _currentIndex = -1;
76

77   /** The cell renderer for this JList */
78   private volatile CustomListCellRenderer _renderer;
79   
80   /** the collection of INavigationListeners listening to this JListNavigator */
81   private final Vector<INavigationListener<? super ItemT>> navListeners = new Vector<INavigationListener<? super ItemT>>();
82   
83   /** Standard constructor. */
84   public JListNavigator() {
85     super();
86     init(new DefaultListModel());
87   }
88   
89   private void init(DefaultListModel m) {
90     _model = m;
91     setModel(m);
92     setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
93     addListSelectionListener(new ListSelectionListener() {
94       /** Called when the list value has changed. Should only run in the event thread.
95        * @param e the event corresponding to the change
96        */

97       public void valueChanged(final ListSelectionEvent e) {
98         Utilities.invokeLater( new Runnable JavaDoc() {
99           public void run() {
100             if (!e.getValueIsAdjusting() && !_model.isEmpty()) {
101               @SuppressWarnings JavaDoc("unchecked") final ItemT newItem = (ItemT) getSelectedValue();
102 // final int newIndex = getSelectedIndex();
103
if (_current != newItem) {
104                 final ItemT oldItem = _current;
105                 NodeData<ItemT> oldData = new NodeData<ItemT>() {
106                   public <Ret> Ret execute(NodeDataVisitor<? super ItemT, Ret> v, Object JavaDoc... p) { return v.itemCase(oldItem, p); }
107                 };
108                 NodeData<ItemT> newData = new NodeData<ItemT>() {
109                   public <Ret> Ret execute(NodeDataVisitor<? super ItemT, Ret> v, Object JavaDoc... p) { return v.itemCase(newItem, p); }
110                 };
111                 for(INavigationListener<? super ItemT> listener: navListeners) {
112                   if (oldItem != null) listener.lostSelection(oldData, isNextChangeModelInitiated());
113                   if (newItem != null) listener.gainedSelection(newData, isNextChangeModelInitiated());
114                 }
115                 setNextChangeModelInitiated(false);
116                 _current = newItem;
117 // _currentIndex = newIndex;
118
}
119             }
120           }
121         });
122       }
123     });
124     
125     _renderer = new CustomListCellRenderer();
126     _renderer.setOpaque(true);
127     this.setCellRenderer(_renderer);
128   }
129   
130   /** Adds the document doc to this navigator. Should only be executed in event thread.
131    * @param doc the document to add
132    */

133   public void addDocument(ItemT doc) { synchronized(_model) { _model.addElement(doc); } }
134   
135   /** Adds the document to this navigator and ignores the specified path. Should only be
136    * executed in event thread.
137    * @param doc the document to add -- assumed to be of type T
138    * @param path unused parameter in this class
139    */

140   public void addDocument(ItemT doc, String JavaDoc path) { addDocument(doc); }
141   
142   /** A typesafe version of {@code _model.get(i)}. This is a workaround for the
143    * non-generic implementation of DefaultListModel, and should be removed once that
144    * is fixed.
145    */

146   protected ItemT getFromModel(int i) {
147     @SuppressWarnings JavaDoc("unchecked") ItemT result = (ItemT) _model.get(i);
148     return result;
149   }
150   
151   /** Gets the next document after doc in the series.
152    * @param doc the document to reference from
153    * @return the document after doc in the list; if doc is the last
154    * document, returns doc
155    */

156   public ItemT getNext(ItemT doc) {
157     synchronized(_model) {
158       int i = _model.indexOf(doc);
159       if (i == -1)
160         throw new IllegalArgumentException JavaDoc("No such document " + doc.toString() + " found in collection of open documents");
161       if ( i + 1 == _model.size()) return doc;
162       
163       return getFromModel(i + 1);
164     }
165   }
166   
167   /** Gets the previous document in the series.
168    * @param doc to reference from
169    * @return the document which comes before doc in the list
170    */

171   public ItemT getPrevious(ItemT doc) {
172     synchronized(_model) {
173       int i = _model.indexOf(doc);
174       if (i == -1)
175         throw new IllegalArgumentException JavaDoc("No such document " + doc.toString() + " found in collection of open documents");
176       if (i == 0) return doc;
177       return getFromModel(i - 1);
178     }
179   }
180   
181   /** Gets the first document in the series.
182    * @return the first document in the collection
183    */

184   public ItemT getFirst() { synchronized(_model) { return getFromModel(0); } }
185   
186   /** Gets the first document in the series.
187    * @return the first document in the collection
188    */

189   public ItemT getLast() { synchronized(_model) { return getFromModel(_model.size() - 1); } }
190   
191   /** Returns the currently selected item, or null if none. */
192   public ItemT getCurrent() { return _current; }
193   
194   /** Returns the model lock. */
195   public Object JavaDoc getModelLock() { return _model; }
196   
197   /** Removes the document from the navigator. Should only be executed in event thread.
198    * @param doc the document to remove
199    */

200   public ItemT removeDocument(ItemT doc) {
201     synchronized(_model) {
202       // System.err.println("removing from old list " + doc);
203
int i = _model.indexOf(doc);
204       if( i == -1 )
205         throw new IllegalArgumentException JavaDoc("Document " + doc + " not found in Document Navigator");
206       ItemT result = getFromModel(i);
207       _model.remove(i);
208       return result;
209     }
210   }
211
212   /** Resets a given <code>INavigatorItem<code> in the tree. This may affect the placement of the item or its
213    * display to reflect any changes made in the model. Should only be executed in event thread.
214    * @param doc the docment to be refreshed
215    * @throws IllegalArgumentException if this navigator contains no document
216    * that is equal to the passed document.
217    */

218   public void refreshDocument(ItemT doc, String JavaDoc path) {
219     synchronized(_model) {
220       removeDocument(doc);
221       addDocument(doc);
222     }
223   }
224   
225   /** Sets the specified document as selected. Should only be called from event thread.
226    * @param doc the document to select
227    */

228   public void setActiveDoc(ItemT doc) {
229     synchronized(_model) {
230       if (_current == doc) return; // doc is already _current (the active doc)
231
if (_model.contains(doc)) {
232         setSelectedValue(doc, true);
233 // _current = doc; // already done by ListSelectionEvent listener created in init()
234
}
235     }
236   }
237     
238   /** Returns whether or not the navigator contains the document
239     * @param doc the document to find
240     * @return true if this list contains doc (using identity as equality measure), false if not.
241     */

242   public boolean contains(ItemT doc) {
243     synchronized(_model) { return _model.contains(doc); }
244   }
245   
246   /** @return an Enumeration of the documents in this list (ordering is consistent with getNext() and getPrev()).
247     * This cast in this method required to work around the stupid partial generification of DefaultListModel in Java 1.5.
248     * The class should be generic: DefaultListModel<T> { ... Enumeration<T> elements() {...} ... } instead of
249     * DefaultListModel { ... Enumeration<?> elements() {...} ... }.
250     */

251   public Enumeration<ItemT> getDocuments() {
252     synchronized(_model) {
253 // Cast forced by lousy generic typing of DefaultListModel in Java 1.5
254
@SuppressWarnings JavaDoc("unchecked") Enumeration<ItemT> result = (Enumeration<ItemT>) _model.elements();
255       return result;
256     }
257   }
258   
259   /** @return the number of documents in the navigator. */
260   public int getDocumentCount() { return _model.size(); }
261   
262   /** @return whether or not the navigator is empty. */
263   public boolean isEmpty() { return _model.isEmpty(); }
264   
265   /** Adds listener to the collection of listeners.
266     * @param listener
267     */

268   public void addNavigationListener(INavigationListener<? super ItemT> listener) {
269     synchronized(_model) { navListeners.add(listener); }
270   }
271   
272   /** Unregisters the listener listener
273     * @param listener
274     */

275   public void removeNavigationListener(INavigationListener<? super ItemT> listener) {
276     synchronized(_model) { navListeners.remove(listener); }
277   }
278   
279   /** @return the navigator listeners. */
280   public Collection<INavigationListener<? super ItemT>> getNavigatorListeners() { return navListeners; }
281   
282   /** Clears the navigator and removes all documents. Should only be executed from event thread. */
283   public void clear() { synchronized(_model) { _model.clear(); } }
284   
285   /** Executes the list case of the visitor.
286    * @param algo the visitor to execute
287    * @param input the input to run on the visitor
288    */

289   public <InType, ReturnType> ReturnType execute(IDocumentNavigatorAlgo<ItemT, InType, ReturnType> algo, InType input) {
290     return algo.forList(this, input);
291   }
292   
293   /** Returns a Container representation of this navigator */
294   public Container asContainer() { return this; }
295   
296   /** Selects the document at the x,y coordinate of the navigator pane and sets it to be
297    * the currently active document. Should only be called from event-handling thread.
298    * @param x the x coordinate of the navigator pane
299    * @param y the y coordinate of the navigator pane
300    *
301    */

302   public boolean selectDocumentAt(final int x, final int y) {
303     synchronized(_model) {
304       final int idx = locationToIndex(new java.awt.Point JavaDoc(x,y));
305       java.awt.Rectangle JavaDoc rect = getCellBounds(idx, idx);
306       if (rect.contains(x, y)) {
307         setActiveDoc(getFromModel(idx));
308         return true;
309       }
310       return false;
311     }
312   }
313     
314
315   /** @return the renderer for this object. */
316   public Component getRenderer(){ return _renderer; }
317   
318   /** @return true if a group if INavigatorItems selected. */
319   public boolean isGroupSelected() { return false; }
320   
321   /** @return true if the INavigatorItem is in the selected group, if a group is selected. */
322   public boolean isSelectedInGroup(ItemT i) { return false; }
323   
324   public void addTopLevelGroup(String JavaDoc name, INavigatorItemFilter<? super ItemT> f) { /* noop */ }
325   
326   public boolean isTopLevelGroupSelected() { return false; }
327   
328   public String JavaDoc getNameOfSelectedTopLevelGroup() throws GroupNotSelectedException{
329     throw new GroupNotSelectedException("A top level group is not selected");
330   }
331   
332   /** Since in the JListNavigator it is impossible to select anything but an INavigatorItem,
333    * this method doesn't need to do anything. See JTreeSortNavigator and IDocumentNavigator.
334    */

335   public void requestSelectionUpdate(ItemT doc) { /* nothing */ }
336   
337 // /** Notify this ListModel that doc has changed and may need updating (if it has changed
338
// * from modified to unmodified). Should only be performed in the event thread
339
// */
340
// public void activeDocumentModified() {
341
// synchronized(_model) {
342
// int current = _currentIndex;
343
// fireSelectionValueChanged(current, current, false);
344
// }
345
// }
346
//
347
public String JavaDoc toString() { synchronized(_model) { return _model.toString(); } }
348   
349   /** The cell renderer for this list. */
350   private static class CustomListCellRenderer extends DefaultListCellRenderer {
351     
352     /** Rreturns the renderer component for a cell
353      * @param list
354      * @param value
355      * @param index
356      * @param isSelected
357      * @param hasFocus
358      */

359     public Component getListCellRendererComponent(JList list, Object JavaDoc value, int index, boolean isSelected, boolean hasFocus) {
360
361       super.getListCellRendererComponent(list, value, index, isSelected, hasFocus);
362       setText(((INavigatorItem)value).getName());
363 // repaint(); // appears to be required to repaint the text for this list item; inconsistent with JTree analog
364
return this;
365     }
366   }
367   
368   /** Marks the next selection change as model-initiated (true) or user-initiated (false; default). */
369   public void setNextChangeModelInitiated(boolean b) {
370     putClientProperty(MODEL_INITIATED_PROPERTY_NAME, b?Boolean.TRUE:null);
371   }
372   
373   /** @return whether the next selection change is model-initiated (true) or user-initiated (false). */
374   public boolean isNextChangeModelInitiated() {
375     return getClientProperty(MODEL_INITIATED_PROPERTY_NAME)!=null;
376   }
377 }
378
Popular Tags