001: /*
002: * @(#)CheckBoxList.java 4/21/2005
003: *
004: * Copyright 2002 - 2005 JIDE Software Inc. All rights reserved.
005: */
006: package com.jidesoft.swing;
007:
008: import javax.swing.*;
009: import javax.swing.text.Position;
010: import java.awt.*;
011: import java.awt.event.*;
012: import java.util.HashMap;
013: import java.util.Map;
014: import java.util.Vector;
015:
016: /**
017: * <code>CheckBoxListWithSelectable</code> is a special JList which uses JCheckBox as the list element.
018: * In addition to regular JList feature, it also allows you select any number
019: * of elements in the list by selecting the check boxes.
020: * <p/>
021: * The element is ListModel should be an instance of {@link Selectable}. If you have
022: * your own class that represents the element in the list, you can implement <code>Selectable</code>
023: * and implements a few very simple methods. If your elements are already in an array or Vector
024: * that you pass in to the constructor of JList, we will convert them to {@link DefaultSelectable} which
025: * implements <code>Selectable</code> interface.
026: * <p/>
027: * To select an element, user can mouse click on the check box, or highlight the rows and press SPACE
028: * key to toggle the selections.
029: * <p/>
030: * Please note, there are two implementions of CheckBoxList. CheckBoxListWithSelectable is one. There is also another
031: * one call CheckBoxList. CheckBoxListWithSelectable is actually the old implementation. In 1.9.2, we introduced
032: * a new implementation and renamed the old implementation to CheckBoxListWithSelectable.
033: * The main difference between the two implementation is at
034: * how the selection state is kept. In new implementation, the selection state is
035: * kept at a separate ListSelectionModel which you can get using
036: * {@link CheckBoxList#getCheckBoxListSelectionModel()}. The old implementation
037: * kept the selection state at Selectable object in the ListModel.
038: */
039: public class CheckBoxListWithSelectable extends JList implements
040: ItemSelectable {
041:
042: protected CheckBoxListCellRenderer _listCellRenderer;
043:
044: public final static String PROPERTY_CHECKBOX_ENABLED = "checkBoxEnabled";
045:
046: private boolean _checkBoxEnabled = true;
047:
048: /**
049: * Constructs a <code>CheckBoxList</code> with an empty model.
050: */
051: public CheckBoxListWithSelectable() {
052: init();
053: }
054:
055: /**
056: * Constructs a <code>CheckBoxList</code> that displays the elements in
057: * the specified <code>Vector</code>. If the Vector contains elements which
058: * is not an instance of {@link Selectable}, it will wrap it automatically
059: * into {@link DefaultSelectable} and add to ListModel.
060: *
061: * @param listData the <code>Vector</code> to be loaded into the
062: * data model
063: */
064: public CheckBoxListWithSelectable(final Vector<?> listData) {
065: super (wrap(listData));
066: init();
067: }
068:
069: /**
070: * Constructs a <code>CheckBoxList</code> that displays the elements in
071: * the specified <code>Object[]</code>. If the Object array contains elements which
072: * is not an instance of {@link Selectable}, it will wrap it automatically
073: * into {@link DefaultSelectable} and add to ListModel.
074: *
075: * @param listData the array of Objects to be loaded into the data model
076: */
077: public CheckBoxListWithSelectable(final Object[] listData) {
078: super (wrap(listData));
079: init();
080: }
081:
082: /**
083: * Constructs a <code>CheckBoxList</code> that displays the elements in the
084: * specified, non-<code>null</code> model.
085: * All <code>CheckBoxList</code> constructors delegate to this one.
086: * <p/>
087: * Please note, if you are using this constructor, please make sure
088: * all elements in dataModel are instance of {@link Selectable}.
089: *
090: * @param dataModel the data model for this list
091: * @throws IllegalArgumentException if <code>dataModel</code>
092: * is <code>null</code>
093: */
094: public CheckBoxListWithSelectable(ListModel dataModel) {
095: super (wrap(dataModel));
096: init();
097: }
098:
099: /**
100: * Initialize the CheckBoxList.
101: */
102: protected void init() {
103: setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
104: _listCellRenderer = createCellRenderer();
105: Handler handle = createHandler();
106: addMouseListener(handle);
107: addKeyListener(handle);
108: }
109:
110: /**
111: * Creates the cell renderer.
112: *
113: * @return the cell renderer.
114: */
115: protected CheckBoxListCellRenderer createCellRenderer() {
116: return new CheckBoxListCellRenderer();
117: }
118:
119: /**
120: * Creates the mouse listener and key listener used by CheckBoxList.
121: *
122: * @return the Handler.
123: */
124: protected Handler createHandler() {
125: return new Handler(this );
126: }
127:
128: /**
129: * Sets the selected elements.
130: *
131: * @param elements the elements to be selected
132: */
133: public void setSelectedObjects(Object[] elements) {
134: Map<Object, String> selected = new HashMap<Object, String>();
135: for (Object element : elements) {
136: selected.put(element, "");
137: }
138: setSelectedObjects(selected);
139: }
140:
141: /**
142: * Sets the selected objects.
143: *
144: * @param objects the elements to be selected in a Vector.
145: */
146: public void setSelectedObjects(Vector<?> objects) {
147: Map<Object, String> selected = new HashMap<Object, String>();
148: for (Object element : objects) {
149: selected.put(element, "");
150: }
151: setSelectedObjects(selected);
152: }
153:
154: @Override
155: public ListCellRenderer getCellRenderer() {
156: if (_listCellRenderer != null) {
157: _listCellRenderer.setActualListRenderer(super
158: .getCellRenderer());
159: return _listCellRenderer;
160: } else {
161: return super .getCellRenderer();
162: }
163: }
164:
165: public ListCellRenderer getActualCellRenderer() {
166: if (_listCellRenderer != null) {
167: return _listCellRenderer.getActualListRenderer();
168: } else {
169: return super .getCellRenderer();
170: }
171: }
172:
173: private void setSelectedObjects(Map<Object, String> selected) {
174: for (int i = 0; i < getModel().getSize(); i++) {
175: Object elementAt = getModel().getElementAt(i);
176: if (elementAt instanceof Selectable) {
177: Selectable selectable = (Selectable) elementAt;
178: if (selectable instanceof DefaultSelectable) {
179: elementAt = ((DefaultSelectable) selectable)
180: .getObject();
181: }
182: if (selected.get(elementAt) != null) {
183: selectable.setSelected(true);
184: fireItemStateChanged(new ItemEvent(this ,
185: ItemEvent.ITEM_STATE_CHANGED, selectable,
186: ItemEvent.SELECTED));
187: selected.remove(elementAt);
188: if (selected.size() == 0) {
189: break;
190: }
191: } else {
192: if (selectable.isSelected()) {
193: selectable.setSelected(false);
194: fireItemStateChanged(new ItemEvent(this ,
195: ItemEvent.ITEM_STATE_CHANGED,
196: selectable, ItemEvent.DESELECTED));
197: }
198: }
199: }
200: }
201: repaint();
202: }
203:
204: private static ListModel wrap(ListModel dataModel) {
205: for (int i = 0; i < dataModel.getSize(); i++) {
206: if (!(dataModel.getElementAt(i) instanceof Selectable)) {
207: throw new IllegalArgumentException(
208: "The ListModel contains an element which is not an instance of Selectable at index "
209: + i + ".");
210: }
211: }
212: return dataModel;
213: }
214:
215: private static Selectable[] wrap(Object[] objects) {
216: if (objects instanceof Selectable[]) {
217: return (Selectable[]) objects;
218: } else {
219: Selectable[] elements = new Selectable[objects.length];
220: for (int i = 0; i < elements.length; i++) {
221: elements[i] = new DefaultSelectable(objects[i]);
222: }
223: return elements;
224: }
225: }
226:
227: private static Vector<?> wrap(Vector<?> objects) {
228: Vector<Selectable> elements = new Vector<Selectable>();
229: for (Object o : objects) {
230: if (o instanceof Selectable) {
231: elements.add((Selectable) o);
232: } else {
233: elements.add(new DefaultSelectable(o));
234: }
235: }
236: return elements;
237: }
238:
239: protected static class Handler implements MouseListener,
240: KeyListener {
241: protected CheckBoxListWithSelectable _list;
242: int _hotspot = new JCheckBox().getPreferredSize().width;
243:
244: public Handler(CheckBoxListWithSelectable list) {
245: _list = list;
246: }
247:
248: protected boolean clicksInCheckBox(MouseEvent e) {
249: int index = _list.locationToIndex(e.getPoint());
250: Rectangle bounds = _list.getCellBounds(index, index);
251: if (bounds != null) {
252: if (_list.getComponentOrientation().isLeftToRight()) {
253: return e.getX() < bounds.x + _hotspot;
254: } else {
255: return e.getX() > bounds.x + bounds.width
256: - _hotspot;
257: }
258: } else {
259: return false;
260: }
261:
262: }
263:
264: public void mouseClicked(MouseEvent e) {
265: }
266:
267: public void mousePressed(MouseEvent e) {
268: if (!_list.isCheckBoxEnabled() || !_list.isEnabled()) {
269: return;
270: }
271:
272: if (clicksInCheckBox(e)) {
273: int index = _list.locationToIndex(e.getPoint());
274: toggleSelection(index);
275: }
276: }
277:
278: public void mouseReleased(MouseEvent e) {
279: }
280:
281: public void mouseEntered(MouseEvent e) {
282: }
283:
284: public void mouseExited(MouseEvent e) {
285: }
286:
287: public void keyPressed(KeyEvent e) {
288: if (!_list.isCheckBoxEnabled() || !_list.isEnabled()) {
289: return;
290: }
291:
292: if (e.getModifiers() == 0
293: && e.getKeyChar() == KeyEvent.VK_SPACE)
294: toggleSelections();
295: }
296:
297: public void keyTyped(KeyEvent e) {
298: }
299:
300: public void keyReleased(KeyEvent e) {
301: }
302:
303: protected void toggleSelections() {
304: int[] indices = _list.getSelectedIndices();
305: ListModel model = _list.getModel();
306: for (int index : indices) {
307: Object element = model.getElementAt(index);
308: if (element instanceof Selectable
309: && ((Selectable) element).isEnabled()) {
310: ((Selectable) element).invertSelected();
311: boolean selected = ((Selectable) element)
312: .isSelected();
313: _list.fireItemStateChanged(new ItemEvent(_list,
314: ItemEvent.ITEM_STATE_CHANGED, element,
315: selected ? ItemEvent.SELECTED
316: : ItemEvent.DESELECTED));
317: }
318: }
319: _list.repaint();
320: }
321:
322: protected void toggleSelection(int index) {
323: ListModel model = _list.getModel();
324: if (index >= 0) {
325: Object element = model.getElementAt(index);
326: if (element instanceof Selectable
327: && ((Selectable) element).isEnabled()) {
328: ((Selectable) element).invertSelected();
329: boolean selected = ((Selectable) element)
330: .isSelected();
331: _list.fireItemStateChanged(new ItemEvent(_list,
332: ItemEvent.ITEM_STATE_CHANGED, element,
333: selected ? ItemEvent.SELECTED
334: : ItemEvent.DESELECTED));
335: }
336: _list.repaint();
337: }
338: }
339:
340: protected void toggleSelection() {
341: int index = _list.getSelectedIndex();
342: toggleSelection(index);
343: }
344: }
345:
346: /**
347: * Adds a listener to the list that's notified each time a change
348: * to the item selection occurs. Listeners added directly to the
349: * <code>CheckBoxList</code>
350: * will have their <code>ItemEvent.getSource() ==
351: * this CheckBoxList</code>.
352: *
353: * @param listener the <code>ItemListener</code> to add
354: */
355: public void addItemListener(ItemListener listener) {
356: listenerList.add(ItemListener.class, listener);
357: }
358:
359: /**
360: * Removes a listener from the list that's notified each time a
361: * change to the item selection occurs.
362: *
363: * @param listener the <code>ItemListener</code> to remove
364: */
365: public void removeItemListener(ItemListener listener) {
366: listenerList.remove(ItemListener.class, listener);
367: }
368:
369: /**
370: * Returns an array of all the <code>ItemListener</code>s added
371: * to this JList with addItemListener().
372: *
373: * @return all of the <code>ItemListener</code>s added or an empty
374: * array if no listeners have been added
375: * @see #addItemListener
376: */
377: public ItemListener[] getItemListeners() {
378: return listenerList.getListeners(ItemListener.class);
379: }
380:
381: /**
382: * Notifies all listeners that have registered interest for
383: * notification on this event type. The event instance
384: * is lazily created using the <code>event</code> parameter.
385: *
386: * @param event the <code>ItemEvent</code> object
387: * @see javax.swing.event.EventListenerList
388: */
389: protected void fireItemStateChanged(ItemEvent event) {
390: // Guaranteed to return a non-null array
391: Object[] listeners = listenerList.getListenerList();
392: ItemEvent e = null;
393: // Process the listeners last to first, notifying
394: // those that are interested in this event
395: for (int i = listeners.length - 2; i >= 0; i -= 2) {
396: if (listeners[i] == ItemListener.class) {
397: // Lazily create the event:
398: if (e == null) {
399: e = new ItemEvent(CheckBoxListWithSelectable.this ,
400: ItemEvent.ITEM_STATE_CHANGED, event
401: .getItem(), event.getStateChange());
402: }
403: ((ItemListener) listeners[i + 1]).itemStateChanged(e);
404: }
405: }
406: }
407:
408: /**
409: * Gets the selected objects. This is different from {@link #getSelectedValues()} which is
410: * a JList's feature. The List returned from this method contains the objects that is
411: * checked in the CheckBoxList.
412: *
413: * @return the selected objects.
414: */
415: public Object[] getSelectedObjects() {
416: Vector<Object> elements = new Vector<Object>();
417: for (int i = 0; i < getModel().getSize(); i++) {
418: Object elementAt = getModel().getElementAt(i);
419: if (elementAt instanceof Selectable) {
420: Selectable selectable = (Selectable) elementAt;
421: if (selectable.isSelected()) {
422: if (selectable instanceof DefaultSelectable) {
423: elements.add(((DefaultSelectable) selectable)
424: .getObject());
425: } else {
426: elements.add(selectable);
427: }
428: }
429: }
430: }
431: return elements.toArray();
432: }
433:
434: /**
435: * Selects all objects in this list except those are disabled.
436: */
437: public void selectAll() {
438: for (int i = 0; i < getModel().getSize(); i++) {
439: Object elementAt = getModel().getElementAt(i);
440: if (elementAt instanceof Selectable) {
441: Selectable selectable = (Selectable) elementAt;
442: if (selectable.isEnabled() && !selectable.isSelected()) {
443: selectable.setSelected(true);
444: fireItemStateChanged(new ItemEvent(this ,
445: ItemEvent.ITEM_STATE_CHANGED, selectable,
446: ItemEvent.SELECTED));
447: }
448: }
449: }
450: repaint();
451: }
452:
453: /**
454: * Unselects all objects in this list except those are disabled.
455: */
456: public void selectNone() {
457: for (int i = 0; i < getModel().getSize(); i++) {
458: Object elementAt = getModel().getElementAt(i);
459: if (elementAt instanceof Selectable) {
460: Selectable selectable = (Selectable) elementAt;
461: if (selectable.isEnabled() && selectable.isSelected()) {
462: selectable.setSelected(false);
463: fireItemStateChanged(new ItemEvent(this ,
464: ItemEvent.ITEM_STATE_CHANGED, selectable,
465: ItemEvent.DESELECTED));
466: }
467: }
468: }
469: repaint();
470: }
471:
472: @Override
473: public void setListData(Vector<?> listData) {
474: super .setListData(wrap(listData));
475: }
476:
477: @Override
478: public void setListData(Object[] listData) {
479: super .setListData(wrap(listData));
480: }
481:
482: @Override
483: public int getNextMatch(String prefix, int startIndex,
484: Position.Bias bias) {
485: return -1;
486: }
487:
488: /**
489: * Gets the value of property checkBoxEnabled. If true, user can
490: * click on check boxes on each tree node to select and unselect.
491: * If false, user can't click but you as developer can programatically
492: * call API to select/unselect it.
493: *
494: * @return the value of property checkBoxEnabled.
495: */
496: public boolean isCheckBoxEnabled() {
497: return _checkBoxEnabled;
498: }
499:
500: /**
501: * Checks if check box is visible. There is no setter for it. The only way is to override
502: * this method to return true or false.
503: *
504: * @param index the row index.
505: * @return true or false. If false, there is not check box on the particular row index.
506: */
507: public boolean isCheckBoxVisible(int index) {
508: return true;
509: }
510:
511: /**
512: * Sets the value of property checkBoxEnabled.
513: *
514: * @param checkBoxEnabled true to enable all the check boxes. False to disable all of them.
515: */
516: public void setCheckBoxEnabled(boolean checkBoxEnabled) {
517: if (checkBoxEnabled != _checkBoxEnabled) {
518: Boolean oldValue = _checkBoxEnabled ? Boolean.TRUE
519: : Boolean.FALSE;
520: Boolean newValue = checkBoxEnabled ? Boolean.TRUE
521: : Boolean.FALSE;
522: _checkBoxEnabled = checkBoxEnabled;
523: firePropertyChange(PROPERTY_CHECKBOX_ENABLED, oldValue,
524: newValue);
525: repaint();
526: }
527: }
528: }
|