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.event.ListDataEvent;
010: import javax.swing.event.ListDataListener;
011: import javax.swing.event.ListSelectionEvent;
012: import javax.swing.event.ListSelectionListener;
013: import javax.swing.text.Position;
014: import java.awt.*;
015: import java.awt.event.KeyEvent;
016: import java.awt.event.KeyListener;
017: import java.awt.event.MouseEvent;
018: import java.awt.event.MouseListener;
019: import java.beans.PropertyChangeEvent;
020: import java.beans.PropertyChangeListener;
021: import java.util.Arrays;
022: import java.util.HashMap;
023: import java.util.Map;
024: import java.util.Vector;
025:
026: /**
027: * <code>CheckBoxList</code> is a special JList which uses JCheckBox as the list element.
028: * In addition to regular JList feature, it also allows you select any number
029: * of elements in the list by selecting the check boxes.
030: * <p/>
031: * To select an element, user can mouse click on the check box, or highlight the rows and press SPACE
032: * key to toggle the selections.
033: * <p/>
034: * We used cell renderer feature in JList to add the check box in each row. However you can still set your own
035: * cell renderer just like before using {@link #setCellRenderer(javax.swing.ListCellRenderer)}. CheckBoxList will
036: * use your cell renderer and automatically put a check box before it.
037: * <p/>
038: * Please note, we changed CheckBoxList implementation in 1.9.2 release. The old CheckBoxList class is
039: * renamed to {@link CheckBoxListWithSelectable}. If you want to use the old implementation, you can
040: * use CheckBoxListWithSelectable instead. The main difference between the two implementation is at
041: * how the selection state is kept. In new implementation, the selection state is
042: * kept at a separate ListSelectionModel which you can get using
043: * {@link CheckBoxList#getCheckBoxListSelectionModel()}. If you need to add a check to a check box or to find out
044: * if a check box is checked, you need to ask the getCheckBoxListSelectionModel() to do it.
045: * The old implementation
046: * kept the selection state at Selectable object in the ListModel. The new implementation is also inline
047: * with that of {@link CheckBoxTree}.
048: */
049: public class CheckBoxList extends JList {
050:
051: protected CheckBoxListCellRenderer _listCellRenderer;
052:
053: public final static String PROPERTY_CHECKBOX_ENABLED = "checkBoxEnabled";
054:
055: private boolean _checkBoxEnabled = true;
056:
057: private CheckBoxListSelectionModel _checkBoxListSelectionModel;
058: protected Handler _handler;
059:
060: /**
061: * Constructs a <code>CheckBoxList</code> with an empty model.
062: */
063: public CheckBoxList() {
064: init();
065: }
066:
067: /**
068: * Constructs a <code>CheckBoxList</code> that displays the elements in
069: * the specified <code>Vector</code>.
070: *
071: * @param listData the <code>Vector</code> to be loaded into the
072: * data model
073: */
074: public CheckBoxList(final Vector<?> listData) {
075: super (listData);
076: init();
077: }
078:
079: /**
080: * Constructs a <code>CheckBoxList</code> that displays the elements in
081: * the specified <code>Object[]</code>.
082: *
083: * @param listData the array of Objects to be loaded into the data model
084: */
085: public CheckBoxList(final Object[] listData) {
086: super (listData);
087: init();
088: }
089:
090: /**
091: * Constructs a <code>CheckBoxList</code> that displays the elements in the
092: * specified, non-<code>null</code> model.
093: * All <code>CheckBoxList</code> constructors delegate to this one.
094: * <p/>
095: *
096: * @param dataModel the data model for this list
097: * @throws IllegalArgumentException if <code>dataModel</code>
098: * is <code>null</code>
099: */
100: public CheckBoxList(ListModel dataModel) {
101: super (dataModel);
102: init();
103: }
104:
105: /**
106: * Initialize the CheckBoxList.
107: */
108: protected void init() {
109: _checkBoxListSelectionModel = createCheckBoxListSelectionModel(getModel());
110: setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
111: _listCellRenderer = createCellRenderer();
112: _handler = createHandler();
113: _checkBoxListSelectionModel.addListSelectionListener(_handler);
114: JideSwingUtilities.insertMouseListener(this , _handler, 0);
115: addKeyListener(_handler);
116: addPropertyChangeListener("model", _handler);
117: ListModel model = getModel();
118: if (model != null) {
119: model.addListDataListener(_handler);
120: }
121: }
122:
123: protected CheckBoxListSelectionModel createCheckBoxListSelectionModel(
124: ListModel model) {
125: return new CheckBoxListSelectionModel(model);
126: }
127:
128: /**
129: * Creates the cell renderer.
130: *
131: * @return the cell renderer.
132: */
133: protected CheckBoxListCellRenderer createCellRenderer() {
134: return new CheckBoxListCellRenderer();
135: }
136:
137: /**
138: * Creates the mouse listener and key listener used by CheckBoxList.
139: *
140: * @return the Handler.
141: */
142: protected Handler createHandler() {
143: return new Handler(this );
144: }
145:
146: @Override
147: public ListCellRenderer getCellRenderer() {
148: if (_listCellRenderer != null) {
149: _listCellRenderer.setActualListRenderer(super
150: .getCellRenderer());
151: return _listCellRenderer;
152: } else {
153: return super .getCellRenderer();
154: }
155: }
156:
157: public ListCellRenderer getActualCellRenderer() {
158: return super .getCellRenderer();
159: }
160:
161: protected static class Handler implements MouseListener,
162: KeyListener, ListSelectionListener, PropertyChangeListener,
163: ListDataListener {
164: protected CheckBoxList _list;
165: int _hotspot = new JCheckBox().getPreferredSize().width;
166:
167: public Handler(CheckBoxList list) {
168: _list = list;
169: }
170:
171: public void propertyChange(PropertyChangeEvent evt) {
172: if (evt.getOldValue() instanceof ListModel) {
173: ((ListModel) evt.getOldValue())
174: .removeListDataListener(this );
175: }
176: if (evt.getNewValue() instanceof ListModel) {
177: _list.getCheckBoxListSelectionModel().setModel(
178: (ListModel) evt.getNewValue());
179: ((ListModel) evt.getNewValue())
180: .addListDataListener(this );
181: }
182: }
183:
184: protected boolean clicksInCheckBox(MouseEvent e) {
185: int index = _list.locationToIndex(e.getPoint());
186: Rectangle bounds = _list.getCellBounds(index, index);
187:
188: if (bounds != null) {
189: if (_list.getComponentOrientation().isLeftToRight()) {
190: return e.getX() < bounds.x + _hotspot;
191: } else {
192: return e.getX() > bounds.x + bounds.width
193: - _hotspot;
194: }
195: } else {
196: return false;
197: }
198: }
199:
200: public void mouseClicked(MouseEvent e) {
201: }
202:
203: public void mousePressed(MouseEvent e) {
204: if (!_list.isCheckBoxEnabled()) {
205: return;
206: }
207:
208: if (clicksInCheckBox(e)) {
209: int index = _list.locationToIndex(e.getPoint());
210: toggleSelection(index);
211: e.consume();
212: }
213: }
214:
215: public void mouseReleased(MouseEvent e) {
216: if (!_list.isCheckBoxEnabled()) {
217: return;
218: }
219:
220: if (clicksInCheckBox(e)) {
221: e.consume();
222: }
223: }
224:
225: public void mouseEntered(MouseEvent e) {
226: }
227:
228: public void mouseExited(MouseEvent e) {
229: }
230:
231: public void keyPressed(KeyEvent e) {
232: if (e.isConsumed()) {
233: return;
234: }
235:
236: if (!_list.isCheckBoxEnabled()) {
237: return;
238: }
239:
240: if (e.getModifiers() == 0
241: && e.getKeyChar() == KeyEvent.VK_SPACE)
242: toggleSelections();
243: }
244:
245: public void keyTyped(KeyEvent e) {
246: }
247:
248: public void keyReleased(KeyEvent e) {
249: }
250:
251: protected void toggleSelections() {
252: int[] indices = _list.getSelectedIndices();
253: CheckBoxListSelectionModel selectionModel = _list
254: .getCheckBoxListSelectionModel();
255: selectionModel.removeListSelectionListener(this );
256: selectionModel.setValueIsAdjusting(true);
257: try {
258: for (int index : indices) {
259: if (!_list.isCheckBoxEnabled(index)) {
260: continue;
261: }
262: boolean selected = selectionModel
263: .isSelectedIndex(index);
264: if (selected) {
265: selectionModel.removeSelectionInterval(index,
266: index);
267: } else {
268: selectionModel.addSelectionInterval(index,
269: index);
270: }
271: }
272: } finally {
273: selectionModel.setValueIsAdjusting(false);
274: selectionModel.addListSelectionListener(this );
275: _list.repaint();
276: }
277: }
278:
279: public void valueChanged(ListSelectionEvent e) {
280: _list.repaint();
281: }
282:
283: protected void toggleSelection(int index) {
284: if (!_list.isEnabled() || !_list.isCheckBoxEnabled(index)) {
285: return;
286: }
287:
288: CheckBoxListSelectionModel selectionModel = _list
289: .getCheckBoxListSelectionModel();
290: boolean selected = selectionModel.isSelectedIndex(index);
291: selectionModel.removeListSelectionListener(this );
292: try {
293: if (selected)
294: selectionModel
295: .removeSelectionInterval(index, index);
296: else
297: selectionModel.addSelectionInterval(index, index);
298: } finally {
299: selectionModel.addListSelectionListener(this );
300: _list.repaint();
301: }
302: }
303:
304: protected void toggleSelection() {
305: int index = _list.getSelectedIndex();
306: toggleSelection(index);
307: }
308:
309: public void intervalAdded(ListDataEvent e) {
310: int minIndex = Math.min(e.getIndex0(), e.getIndex1());
311: int maxIndex = Math.max(e.getIndex0(), e.getIndex1());
312:
313: /* Sync the SelectionModel with the DataModel.
314: */
315:
316: ListSelectionModel sm = _list
317: .getCheckBoxListSelectionModel();
318: if (sm != null) {
319: sm.insertIndexInterval(minIndex, maxIndex - minIndex
320: + 1, true);
321: }
322: }
323:
324: public void intervalRemoved(ListDataEvent e) {
325: /* Sync the SelectionModel with the DataModel.
326: */
327: ListSelectionModel sm = _list
328: .getCheckBoxListSelectionModel();
329: if (sm != null) {
330: sm.removeIndexInterval(e.getIndex0(), e.getIndex1());
331: }
332: }
333:
334: public void contentsChanged(ListDataEvent e) {
335: }
336: }
337:
338: @Override
339: public int getNextMatch(String prefix, int startIndex,
340: Position.Bias bias) {
341: return -1;
342: }
343:
344: /**
345: * Gets the value of property checkBoxEnabled. If true, user can
346: * click on check boxes on each tree node to select and unselect.
347: * If false, user can't click but you as developer can programatically
348: * call API to select/unselect it.
349: *
350: * @return the value of property checkBoxEnabled.
351: */
352: public boolean isCheckBoxEnabled() {
353: return _checkBoxEnabled;
354: }
355:
356: /**
357: * Checks if check box is enabled. There is no setter for it. The only way is to override
358: * this method to return true or false.
359: *
360: * @param index the row index.
361: * @return true or false. If false, the check box on the particular row index will be disabled.
362: */
363: public boolean isCheckBoxEnabled(int index) {
364: return true;
365: }
366:
367: /**
368: * Checks if check box is visible. There is no setter for it. The only way is to override
369: * this method to return true or false.
370: *
371: * @param index whether the check box on the row index is visible.
372: * @return true or false. If false, there is not check box on the particular row index.
373: * By default, we always return true. You override this method to return true of false
374: * depending on your need.
375: */
376: public boolean isCheckBoxVisible(int index) {
377: return true;
378: }
379:
380: /**
381: * Sets the value of property checkBoxEnabled.
382: *
383: * @param checkBoxEnabled true to allow to check the check box. False to disable it
384: * which means user can see whether a row is checked or not but they cannot change it.
385: */
386: public void setCheckBoxEnabled(boolean checkBoxEnabled) {
387: if (checkBoxEnabled != _checkBoxEnabled) {
388: Boolean oldValue = _checkBoxEnabled ? Boolean.TRUE
389: : Boolean.FALSE;
390: Boolean newValue = checkBoxEnabled ? Boolean.TRUE
391: : Boolean.FALSE;
392: _checkBoxEnabled = checkBoxEnabled;
393: firePropertyChange(PROPERTY_CHECKBOX_ENABLED, oldValue,
394: newValue);
395: repaint();
396: }
397: }
398:
399: /**
400: * Gets the ListSelectionModel that keeps the check boxes' state information for CheckBoxList.
401: *
402: * @return the ListSelectionModel that keeps the check boxes' state information for CheckBoxList.
403: */
404: public CheckBoxListSelectionModel getCheckBoxListSelectionModel() {
405: return _checkBoxListSelectionModel;
406: }
407:
408: public void setCheckBoxListSelectionModel(
409: CheckBoxListSelectionModel checkBoxListSelectionModel) {
410: _checkBoxListSelectionModel = checkBoxListSelectionModel;
411: _checkBoxListSelectionModel.setModel(getModel());
412: }
413:
414: /**
415: * Returns an array of all of the selected indices in increasing
416: * order.
417: *
418: * @return all of the selected indices, in increasing order
419: * @see #removeSelectionInterval
420: * @see #addListSelectionListener
421: */
422: public int[] getCheckBoxListSelectedIndices() {
423: ListSelectionModel sm = getCheckBoxListSelectionModel();
424: int iMin = sm.getMinSelectionIndex();
425: int iMax = sm.getMaxSelectionIndex();
426:
427: if ((iMin < 0) || (iMax < 0)) {
428: return new int[0];
429: }
430:
431: int[] rvTmp = new int[1 + (iMax - iMin)];
432: int n = 0;
433: for (int i = iMin; i <= iMax; i++) {
434: if (sm.isSelectedIndex(i)) {
435: rvTmp[n++] = i;
436: }
437: }
438: int[] rv = new int[n];
439: System.arraycopy(rvTmp, 0, rv, 0, n);
440: return rv;
441: }
442:
443: /**
444: * Selects a single cell and clear all other selections.
445: *
446: * @param index the index of the one cell to select
447: * @see ListSelectionModel#setSelectionInterval
448: * @see #isSelectedIndex
449: * @see #addListSelectionListener
450: */
451: public void setCheckBoxListSelectedIndex(int index) {
452: if (index >= 0 && index < getModel().getSize()) {
453: getCheckBoxListSelectionModel().setSelectionInterval(index,
454: index);
455: }
456: }
457:
458: /**
459: * Selects a single cell and keeps all previous selections.
460: *
461: * @param index the index of the one cell to select
462: * @see ListSelectionModel#setSelectionInterval
463: * @see #isSelectedIndex
464: * @see #addListSelectionListener
465: */
466: public void addCheckBoxListSelectedIndex(int index) {
467: if (index >= 0 && index < getModel().getSize()) {
468: getCheckBoxListSelectionModel().addSelectionInterval(index,
469: index);
470: }
471: }
472:
473: /**
474: * Deselects a single cell.
475: *
476: * @param index the index of the one cell to select
477: * @see ListSelectionModel#setSelectionInterval
478: * @see #isSelectedIndex
479: * @see #addListSelectionListener
480: */
481: public void removeCheckBoxListSelectedIndex(int index) {
482: if (index >= 0 && index < getModel().getSize()) {
483: getCheckBoxListSelectionModel().removeSelectionInterval(
484: index, index);
485: }
486: }
487:
488: /**
489: * Selects a set of cells.
490: *
491: * @param indices an array of the indices of the cells to select
492: * @see ListSelectionModel#addSelectionInterval
493: * @see #isSelectedIndex
494: * @see #addListSelectionListener
495: */
496: public void setCheckBoxListSelectedIndices(int[] indices) {
497: ListSelectionModel sm = getCheckBoxListSelectionModel();
498: try {
499: sm.setValueIsAdjusting(true);
500: sm.clearSelection();
501: int size = getModel().getSize();
502: for (int indice : indices) {
503: if (indice >= 0 && indice < size) {
504: sm.addSelectionInterval(indice, indice);
505: }
506: }
507: } finally {
508: sm.setValueIsAdjusting(false);
509: }
510: }
511:
512: /**
513: * Sets the selected elements.
514: *
515: * @param elements sets the select elements.
516: * All the rows that have the value in the array will be checked.
517: */
518: public void setSelectedObjects(Object[] elements) {
519: Map<Object, String> selected = new HashMap<Object, String>();
520: for (Object element : elements) {
521: selected.put(element, "");
522: }
523: setSelectedObjects(selected);
524: }
525:
526: /**
527: * Sets the selected elements.
528: *
529: * @param elements sets the select elements.
530: * All the rows that have the value in the Vector will be checked.
531: */
532: public void setSelectedObjects(Vector<?> elements) {
533: Map<Object, String> selected = new HashMap<Object, String>();
534: for (Object element : elements) {
535: selected.put(element, "");
536: }
537: setSelectedObjects(selected);
538: }
539:
540: private void setSelectedObjects(Map<Object, String> selected) {
541: int[] indices = new int[selected.size()];
542: Arrays.fill(indices, -1);
543: int index = 0;
544: for (int i = 0; i < getModel().getSize(); i++) {
545: Object elementAt = getModel().getElementAt(i);
546: if (selected.get(elementAt) != null) {
547: indices[index++] = i;
548: }
549: }
550: setCheckBoxListSelectedIndices(indices);
551: }
552:
553: /**
554: * @return the values that are checked.
555: * @deprecated We keep this method in order to be compatible with {@link CheckBoxListWithSelectable}. You should use {@link #getCheckBoxListSelectedValues()}
556: * instead. Or you can use {@link #getCheckBoxListSelectionModel()} to get the selection model and get the selected indices from there.
557: */
558: public Object[] getSelectedObjects() {
559: return getCheckBoxListSelectedValues();
560: }
561:
562: /**
563: * Returns an array of the values for the selected cells.
564: * The returned values are sorted in increasing index order.
565: *
566: * @return the selected values or an empty list if
567: * nothing is selected
568: * @see #isSelectedIndex
569: * @see #getModel
570: * @see #addListSelectionListener
571: */
572: public Object[] getCheckBoxListSelectedValues() {
573: ListSelectionModel sm = getCheckBoxListSelectionModel();
574: ListModel dm = getModel();
575:
576: int iMin = sm.getMinSelectionIndex();
577: int iMax = sm.getMaxSelectionIndex();
578:
579: if ((iMin < 0) || (iMax < 0)) {
580: return new Object[0];
581: }
582:
583: Object[] rvTmp = new Object[1 + (iMax - iMin)];
584: int n = 0;
585: for (int i = iMin; i <= iMax; i++) {
586: if (sm.isSelectedIndex(i)) {
587: rvTmp[n++] = dm.getElementAt(i);
588: }
589: }
590: Object[] rv = new Object[n];
591: System.arraycopy(rvTmp, 0, rv, 0, n);
592: return rv;
593: }
594:
595: /**
596: * Returns the first selected index; returns -1 if there is no
597: * selected item.
598: *
599: * @return the value of <code>getMinSelectionIndex</code>
600: * @see #getMinSelectionIndex
601: * @see #addListSelectionListener
602: */
603: public int getCheckBoxListSelectedIndex() {
604: return getCheckBoxListSelectionModel().getMinSelectionIndex();
605: }
606:
607: /**
608: * Returns the first selected value, or <code>null</code> if the
609: * selection is empty.
610: *
611: * @return the first selected value
612: * @see #getMinSelectionIndex
613: * @see #getModel
614: * @see #addListSelectionListener
615: */
616: public Object getCheckBoxListSelectedValue() {
617: int i = getCheckBoxListSelectionModel().getMinSelectionIndex();
618: return (i == -1) ? null : getModel().getElementAt(i);
619: }
620:
621: /**
622: * Selects the specified object from the list and clear all other selections.
623: *
624: * @param anObject the object to select
625: * @param shouldScroll true if the list should scroll to display
626: * the selected object, if one exists; otherwise false
627: */
628: public void setCheckBoxListSelectedValue(Object anObject,
629: boolean shouldScroll) {
630: if (anObject == null)
631: setSelectedIndex(-1);
632: else {
633: int i, c;
634: ListModel dm = getModel();
635: for (i = 0, c = dm.getSize(); i < c; i++)
636: if (anObject.equals(dm.getElementAt(i))) {
637: setCheckBoxListSelectedIndex(i);
638: if (shouldScroll)
639: ensureIndexIsVisible(i);
640: repaint();
641: /** FIX-ME setSelectedIndex does not redraw all the time with the basic l&f**/
642: return;
643: }
644: setCheckBoxListSelectedIndex(-1);
645: }
646: repaint();
647: /** FIX-ME setSelectedIndex does not redraw all the time with the basic l&f**/
648: }
649:
650: /**
651: * Selects the specified object from the list and keep all previous selections.
652: *
653: * @param anObject the object to select
654: * @param shouldScroll true if the list should scroll to display
655: * the selected object, if one exists; otherwise false
656: */
657: public void addCheckBoxListSelectedValue(Object anObject,
658: boolean shouldScroll) {
659: if (anObject != null) {
660: int i, c;
661: ListModel dm = getModel();
662: for (i = 0, c = dm.getSize(); i < c; i++)
663: if (anObject.equals(dm.getElementAt(i))) {
664: addCheckBoxListSelectedIndex(i);
665: if (shouldScroll)
666: ensureIndexIsVisible(i);
667: repaint();
668: /** FIX-ME setSelectedIndex does not redraw all the time with the basic l&f**/
669: return;
670: }
671: }
672: }
673:
674: /**
675: * Deselects the specified object from the list.
676: *
677: * @param anObject the object to select
678: * @param shouldScroll true if the list should scroll to display
679: * the selected object, if one exists; otherwise false
680: */
681: public void removeCheckBoxListSelectedValue(Object anObject,
682: boolean shouldScroll) {
683: if (anObject != null) {
684: int i, c;
685: ListModel dm = getModel();
686: for (i = 0, c = dm.getSize(); i < c; i++)
687: if (anObject.equals(dm.getElementAt(i))) {
688: removeCheckBoxListSelectedIndex(i);
689: if (shouldScroll)
690: ensureIndexIsVisible(i);
691: repaint();
692: /** FIX-ME setSelectedIndex does not redraw all the time with the basic l&f**/
693: return;
694: }
695: }
696: }
697:
698: public void clearCheckBoxListSelection() {
699: getCheckBoxListSelectionModel().clearSelection();
700: }
701:
702: /**
703: * Selects all objects in this list except those are disabled.
704: */
705: public void selectAll() {
706: getCheckBoxListSelectionModel().setSelectionInterval(0,
707: getModel().getSize() - 1);
708: }
709:
710: /**
711: * Unselects all objects in this list except those are disabled.
712: */
713: public void selectNone() {
714: getCheckBoxListSelectionModel().removeIndexInterval(0,
715: getModel().getSize() - 1);
716: }
717: }
|