001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: /**
018: * @author Alexander T. Simbirtsev
019: * @version $Revision$
020: */package javax.swing.tree;
021:
022: import java.beans.PropertyChangeListener;
023: import java.io.Serializable;
024: import java.util.Arrays;
025: import java.util.EventListener;
026: import java.util.HashSet;
027: import java.util.Vector;
028:
029: import javax.swing.DefaultListSelectionModel;
030: import javax.swing.event.EventListenerList;
031: import javax.swing.event.SwingPropertyChangeSupport;
032: import javax.swing.event.TreeSelectionEvent;
033: import javax.swing.event.TreeSelectionListener;
034:
035: import org.apache.harmony.x.swing.Utilities;
036:
037: import org.apache.harmony.x.swing.internal.nls.Messages;
038:
039: public class DefaultTreeSelectionModel implements Cloneable,
040: Serializable, TreeSelectionModel {
041:
042: public static final String SELECTION_MODE_PROPERTY = "selectionMode";
043:
044: protected SwingPropertyChangeSupport changeSupport;
045: protected EventListenerList listenerList = new EventListenerList();
046:
047: protected DefaultListSelectionModel listSelectionModel = new DefaultListSelectionModel();
048: protected TreePath[] selection;
049: protected transient RowMapper rowMapper;
050: protected int selectionMode = DISCONTIGUOUS_TREE_SELECTION;
051: protected TreePath leadPath;
052: protected int leadIndex = -1;
053: protected int leadRow = -1;
054:
055: private static final TreePath[] singlePathArray = new TreePath[] { null };
056:
057: private static final int CLEAR_SELECTED_PATHS = 1;
058: private static final int REMOVE_SELECTED_PATHS = 2;
059: private static final int ADD_SELECTED_PATHS = 3;
060: private static final int SET_SELECTED_PATHS = 4;
061:
062: public void addPropertyChangeListener(final PropertyChangeListener l) {
063: if (changeSupport == null) {
064: changeSupport = new SwingPropertyChangeSupport(this );
065: }
066: changeSupport.addPropertyChangeListener(l);
067: }
068:
069: public void removePropertyChangeListener(
070: final PropertyChangeListener l) {
071: if (changeSupport != null) {
072: changeSupport.removePropertyChangeListener(l);
073: }
074: }
075:
076: public PropertyChangeListener[] getPropertyChangeListeners() {
077: return (changeSupport != null) ? changeSupport
078: .getPropertyChangeListeners()
079: : new PropertyChangeListener[0];
080: }
081:
082: public <T extends EventListener> T[] getListeners(
083: final Class<T> listenerType) {
084: return listenerList.getListeners(listenerType);
085: }
086:
087: public void addTreeSelectionListener(final TreeSelectionListener l) {
088: listenerList.add(TreeSelectionListener.class, l);
089: }
090:
091: public void removeTreeSelectionListener(
092: final TreeSelectionListener l) {
093: listenerList.remove(TreeSelectionListener.class, l);
094: }
095:
096: public TreeSelectionListener[] getTreeSelectionListeners() {
097: return (TreeSelectionListener[]) listenerList
098: .getListeners(TreeSelectionListener.class);
099: }
100:
101: protected void fireValueChanged(final TreeSelectionEvent e) {
102: TreeSelectionListener[] listeners = getTreeSelectionListeners();
103: for (int i = 0; i < listeners.length; i++) {
104: listeners[i].valueChanged(e);
105: }
106: }
107:
108: public void setSelectionPath(final TreePath path) {
109: setSelectionPaths(getSinglePathArray(path));
110: }
111:
112: public void addSelectionPath(final TreePath path) {
113: addSelectionPaths(getSinglePathArray(path));
114: }
115:
116: public TreePath getSelectionPath() {
117: return !isSelectionEmpty() ? selection[0] : null;
118: }
119:
120: public void removeSelectionPath(final TreePath path) {
121: removeSelectionPaths(getSinglePathArray(path));
122: }
123:
124: public void setSelectionPaths(final TreePath[] paths) {
125: if (paths == null) {
126: clearSelection();
127: return;
128: }
129:
130: if (selectionMode == CONTIGUOUS_TREE_SELECTION
131: && !arePathsContiguous(paths)
132: || selectionMode == SINGLE_TREE_SELECTION
133: && paths.length > 1) {
134: setSelectionPath(paths[0]);
135: return;
136: }
137:
138: modifyPathsSelection(paths, SET_SELECTED_PATHS);
139: insureUniqueness();
140: }
141:
142: public void addSelectionPaths(final TreePath[] paths) {
143: if (paths == null) {
144: return;
145: }
146:
147: if (!canPathsBeAdded(paths)) {
148: setSelectionPaths(getContigousPathsArray(paths));
149: return;
150: }
151:
152: modifyPathsSelection(paths, ADD_SELECTED_PATHS);
153: insureUniqueness();
154: }
155:
156: public TreePath[] getSelectionPaths() {
157: return (((selection != null) && (selection.length > 0)) ? selection
158: : null);
159: }
160:
161: public void removeSelectionPaths(final TreePath[] paths) {
162: if (paths == null || isSelectionEmpty()) {
163: return;
164: }
165:
166: if (!canPathsBeRemoved(paths)) {
167: clearSelection();
168: return;
169: }
170:
171: modifyPathsSelection(paths, REMOVE_SELECTED_PATHS);
172: }
173:
174: public boolean isPathSelected(final TreePath path) {
175: if (path == null || Utilities.isEmptyArray(selection)) {
176: return false;
177: }
178:
179: for (int i = 0; i < selection.length; i++) {
180: if (path.equals(selection[i])) {
181: return true;
182: }
183: }
184:
185: return false;
186: }
187:
188: public void clearSelection() {
189: modifyPathsSelection(null, CLEAR_SELECTED_PATHS);
190: }
191:
192: public TreePath getLeadSelectionPath() {
193: return leadPath;
194: }
195:
196: public int getLeadSelectionRow() {
197: return leadRow;
198: }
199:
200: public int getMaxSelectionRow() {
201: return listSelectionModel.getMaxSelectionIndex();
202: }
203:
204: public int getMinSelectionRow() {
205: return listSelectionModel.getMinSelectionIndex();
206: }
207:
208: public int getSelectionCount() {
209: return (selection != null) ? selection.length : 0;
210: }
211:
212: public int[] getSelectionRows() {
213: if (rowMapper == null || isSelectionEmpty()) {
214: return null;
215: }
216: int[] ret = getCleanedSortedRowsArray(rowMapper
217: .getRowsForPaths(getSelectionPaths()));
218: return (ret.length == 0 ? null : ret);
219: }
220:
221: public boolean isRowSelected(final int i) {
222: return listSelectionModel.isSelectedIndex(i);
223: }
224:
225: public boolean isSelectionEmpty() {
226: return Utilities.isEmptyArray(selection);
227: }
228:
229: public void resetRowSelection() {
230: listSelectionModel.clearSelection();
231: leadRow = -1;
232: if (rowMapper == null || isSelectionEmpty()) {
233: return;
234: }
235: int[] rows = rowMapper.getRowsForPaths(getSelectionPaths());
236: for (int i = 0; i < rows.length; i++) {
237: if (rows[i] != -1) {
238: listSelectionModel.addSelectionInterval(rows[i],
239: rows[i]);
240: }
241: }
242: if (leadPath != null) {
243: leadRow = getPathIndex(leadPath);
244: }
245: insureRowContinuity();
246: }
247:
248: public void setRowMapper(final RowMapper mapper) {
249: rowMapper = mapper;
250: resetRowSelection();
251: }
252:
253: public RowMapper getRowMapper() {
254: return rowMapper;
255: }
256:
257: public int getSelectionMode() {
258: return selectionMode;
259: }
260:
261: public void setSelectionMode(final int mode) {
262: int oldValue = selectionMode;
263: if (mode != SINGLE_TREE_SELECTION
264: && mode != CONTIGUOUS_TREE_SELECTION
265: && mode != DISCONTIGUOUS_TREE_SELECTION) {
266: selectionMode = DISCONTIGUOUS_TREE_SELECTION;
267: } else {
268: selectionMode = mode;
269: }
270:
271: insureRowContinuity();
272: if (changeSupport != null) {
273: changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY,
274: oldValue, selectionMode);
275: }
276: }
277:
278: public String toString() {
279: String result = getClass().getName() + " " + hashCode();
280: String paths = "";
281: if (!isSelectionEmpty()) {
282: for (int i = 0; i < selection.length; i++) {
283: paths += selection[i];
284: }
285: }
286: result += " [ " + paths + " ]";
287: return result;
288: }
289:
290: public Object clone() throws CloneNotSupportedException {
291: final DefaultTreeSelectionModel result = new DefaultTreeSelectionModel();
292: result.selection = (selection != null) ? (TreePath[]) selection
293: .clone() : null;
294: result.leadIndex = leadIndex;
295: result.leadRow = leadRow;
296: result.leadPath = leadPath;
297: result.rowMapper = rowMapper;
298: result.selectionMode = selectionMode;
299: result.listSelectionModel = (listSelectionModel != null) ? (DefaultListSelectionModel) listSelectionModel
300: .clone()
301: : null;
302:
303: return result;
304: }
305:
306: protected void insureRowContinuity() {
307: if (selectionMode == SINGLE_TREE_SELECTION) {
308: if (getSelectionCount() > 1) {
309: setSelectionPath(getSelectionPath());
310: }
311: } else if (selectionMode == CONTIGUOUS_TREE_SELECTION) {
312: if (!arePathsContiguous(getSelectionPaths())) {
313: setSelectionPaths(getContigousPathsArray(selection));
314: }
315: }
316: }
317:
318: protected boolean arePathsContiguous(final TreePath[] paths) {
319: if (rowMapper == null) {
320: return true;
321: }
322:
323: if (paths == null || paths.length <= 1) {
324: return true;
325: }
326:
327: return areRowsContigous(rowMapper.getRowsForPaths(paths));
328: }
329:
330: protected boolean canPathsBeAdded(final TreePath[] paths) {
331: if (paths == null || isSelectionModifiableAnyhow()) {
332: return true;
333: }
334:
335: TreePath[] united = new TreePath[selection.length
336: + paths.length];
337: System.arraycopy(selection, 0, united, 0, selection.length);
338: System.arraycopy(paths, 0, united, selection.length,
339: paths.length);
340: return arePathsContiguous(united);
341: }
342:
343: protected boolean canPathsBeRemoved(final TreePath[] paths) {
344: if (paths == null || isSelectionModifiableAnyhow()) {
345: return true;
346: }
347:
348: HashSet rows = arrayToHashSet(selection);
349: for (int i = 0; i < paths.length; i++) {
350: rows.remove(paths[i]);
351: }
352: return arePathsContiguous(hashSetToTreePathArray(rows));
353: }
354:
355: protected void notifyPathChange(
356: final Vector<PathPlaceHolder> changedPaths,
357: final TreePath oldLeadSelection) {
358:
359: if (changedPaths == null || changedPaths.size() == 0) {
360: return;
361: }
362:
363: TreeSelectionEvent event = new TreeSelectionEvent(this ,
364: PathPlaceHolder.getPathsArray(changedPaths),
365: PathPlaceHolder.getAreNewArray(changedPaths),
366: oldLeadSelection, getLeadSelectionPath());
367: fireValueChanged(event);
368: }
369:
370: protected void updateLeadIndex() {
371: leadIndex = -1;
372: if (leadPath == null || isSelectionEmpty()) {
373: return;
374: }
375:
376: leadIndex = getPathIndex(leadPath);
377: }
378:
379: protected void insureUniqueness() {
380: }
381:
382: private void modifyPathsSelection(final TreePath[] paths,
383: final int modifyMode) {
384:
385: final TreePath[] oldSelection = selection;
386: final TreePath oldLeadSelection = getLeadSelectionPath();
387:
388: updateSelectionArray(paths, modifyMode);
389: leadPath = getNewLeadPath(modifyMode);
390:
391: updateLeadIndex();
392: resetRowSelection();
393: notifyPathChange(createPlaceHolders(oldSelection, selection),
394: oldLeadSelection);
395: }
396:
397: private void updateSelectionArray(final TreePath[] paths,
398: final int modifyMode) {
399: switch (modifyMode) {
400: case CLEAR_SELECTED_PATHS:
401: selection = null;
402: break;
403: case REMOVE_SELECTED_PATHS:
404: final HashSet selectionHashSet = arrayToHashSet(selection);
405: selectionHashSet.removeAll(arrayToHashSet(paths));
406: selection = hashSetToTreePathArray(selectionHashSet);
407: break;
408: case SET_SELECTED_PATHS:
409: selection = getUniquePathArray(paths);
410: break;
411: case ADD_SELECTED_PATHS:
412: TreePath[] combined = selection != null ? new TreePath[paths.length
413: + selection.length]
414: : paths;
415: if (selection != null) {
416: System.arraycopy(selection, 0, combined, 0,
417: selection.length);
418: System.arraycopy(paths, 0, combined, selection.length,
419: paths.length);
420: }
421: selection = getUniquePathArray(combined);
422: break;
423: default:
424: throw new IllegalArgumentException(Messages
425: .getString("swing.B0")); //$NON-NLS-1$
426: }
427: }
428:
429: private Vector<PathPlaceHolder> createPlaceHolders(
430: final TreePath[] oldSelection, final TreePath[] newSelection) {
431:
432: final HashSet addedElements = arrayToHashSet(newSelection);
433: final HashSet removedElements = arrayToHashSet(oldSelection);
434: addedElements.removeAll(removedElements);
435: removedElements.removeAll(arrayToHashSet(newSelection));
436:
437: Vector<PathPlaceHolder> result = PathPlaceHolder
438: .createPathsPlaceHolders(
439: hashSetToTreePathArray(addedElements), true);
440: result.addAll(PathPlaceHolder.createPathsPlaceHolders(
441: hashSetToTreePathArray(removedElements), false));
442:
443: return result;
444: }
445:
446: private TreePath getNewLeadPath(final int mode) {
447: return (mode == ADD_SELECTED_PATHS || mode == SET_SELECTED_PATHS) ? getLastSelectionPath()
448: : getSelectionPath();
449: }
450:
451: private TreePath getLastSelectionPath() {
452: return !isSelectionEmpty() ? selection[selection.length - 1]
453: : null;
454: }
455:
456: private TreePath[] getSinglePathArray(final TreePath path) {
457: singlePathArray[0] = path;
458: return (path != null) ? singlePathArray : null;
459: }
460:
461: private boolean isSelectionModifiableAnyhow() {
462: if (rowMapper == null) {
463: return true;
464: }
465: if (isSelectionEmpty()) {
466: return true;
467: }
468: if (selectionMode == DISCONTIGUOUS_TREE_SELECTION) {
469: return true;
470: }
471:
472: return false;
473: }
474:
475: private TreePath[] hashSetToTreePathArray(final HashSet set) {
476: return (TreePath[]) set.toArray(new TreePath[set.size()]);
477: }
478:
479: private HashSet arrayToHashSet(final Object[] array) {
480: if (array == null) {
481: return new HashSet();
482: }
483:
484: final HashSet result = new HashSet(array.length);
485: result.addAll(Arrays.asList(array));
486: return result;
487: }
488:
489: private boolean areRowsContigous(final int[] rows) {
490: Arrays.sort(rows);
491: return areSortedRowsContigous(rows, rows.length);
492: }
493:
494: private boolean areSortedRowsContigous(final int[] sortedRows,
495: final int checkedRange) {
496: for (int i = 0; i < checkedRange - 1; i++) {
497: if (sortedRows[i + 1] - sortedRows[i] > 1) {
498: return false;
499: }
500: }
501: return true;
502: }
503:
504: private TreePath[] getContigousPathsArray(final TreePath[] paths) {
505: TreePath[] result = new TreePath[getContiguousPathsLength(paths)];
506: System.arraycopy(paths, 0, result, 0, result.length);
507: return result;
508: }
509:
510: private int getContiguousPathsLength(final TreePath[] paths) {
511: final int[] rows = rowMapper.getRowsForPaths(paths);
512: Arrays.sort(rows);
513: for (int i = 1; i <= rows.length; i++) {
514: if (!areSortedRowsContigous(rows, i)) {
515: return i - 1;
516: }
517: }
518:
519: return paths.length;
520: }
521:
522: private int getPathIndex(final TreePath path) {
523: if (rowMapper != null) {
524: return getRowForPath(path);
525: }
526: if (isSelectionEmpty()) {
527: return -1;
528: }
529: for (int i = 0; i < selection.length; i++) {
530: if (selection[i].equals(path)) {
531: return i;
532: }
533: }
534:
535: return -1;
536: }
537:
538: private TreePath[] getUniquePathArray(final TreePath[] paths) {
539: TreePath[] filtered = hashSetToTreePathArray(arrayToHashSet(paths));
540: return (filtered.length == paths.length) ? (TreePath[]) paths
541: .clone() : filtered;
542: }
543:
544: private int getRowForPath(final TreePath path) {
545: return rowMapper.getRowsForPaths(getSinglePathArray(path))[0];
546: }
547:
548: private static int[] getCleanedSortedRowsArray(final int[] array) {
549: Arrays.sort(array);
550: if (array[0] != -1) {
551: return array;
552: }
553:
554: int numOdds = 1;
555: while (numOdds < array.length && (array[numOdds] == -1)) {
556: numOdds++;
557: }
558: int[] result = new int[array.length - numOdds];
559: System.arraycopy(array, numOdds, result, 0, result.length);
560:
561: return result;
562: }
563: }
|