001 /* DefaultTreeSelectionModel.java
002 Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.tree;
040
041 import java.beans.PropertyChangeListener;
042 import java.io.IOException;
043 import java.io.ObjectInputStream;
044 import java.io.ObjectOutputStream;
045 import java.io.Serializable;
046 import java.util.Arrays;
047 import java.util.BitSet;
048 import java.util.EventListener;
049 import java.util.HashSet;
050 import java.util.Iterator;
051 import java.util.Vector;
052
053 import javax.swing.DefaultListSelectionModel;
054 import javax.swing.event.EventListenerList;
055 import javax.swing.event.SwingPropertyChangeSupport;
056 import javax.swing.event.TreeSelectionEvent;
057 import javax.swing.event.TreeSelectionListener;
058
059 /**
060 * The implementation of the default tree selection model. The installed
061 * listeners are notified about the path and not the row changes. If you
062 * specifically need to track the row changes, register the listener for the
063 * expansion events.
064 *
065 * @author Andrew Selkirk
066 * @author Audrius Meskauskas
067 */
068 public class DefaultTreeSelectionModel
069 implements Cloneable, Serializable, TreeSelectionModel
070 {
071
072 /**
073 * According to the API docs, the method
074 * {@link DefaultTreeSelectionModel#notifyPathChange} should
075 * expect instances of a class PathPlaceHolder in the Vector parameter.
076 * This seems to be a non-public class, so I can only make guesses about the
077 * use of it.
078 */
079 private static class PathPlaceHolder
080 {
081 /**
082 * The path that we wrap.
083 */
084 TreePath path;
085
086 /**
087 * Indicates if the path is new or already in the selection.
088 */
089 boolean isNew;
090
091 /**
092 * Creates a new instance.
093 *
094 * @param p the path to wrap
095 * @param n if the path is new or already in the selection
096 */
097 PathPlaceHolder(TreePath p, boolean n)
098 {
099 path = p;
100 isNew = n;
101 }
102 }
103
104 /**
105 * Use serialVersionUID for interoperability.
106 */
107 static final long serialVersionUID = 3288129636638950196L;
108
109 /**
110 * The name of the selection mode property.
111 */
112 public static final String SELECTION_MODE_PROPERTY = "selectionMode";
113
114 /**
115 * Our Swing property change support.
116 */
117 protected SwingPropertyChangeSupport changeSupport;
118
119 /**
120 * The current selection.
121 */
122 protected TreePath[] selection;
123
124 /**
125 * Our TreeSelectionListeners.
126 */
127 protected EventListenerList listenerList;
128
129 /**
130 * The current RowMapper.
131 */
132 protected transient RowMapper rowMapper;
133
134 /**
135 * The current listSelectionModel.
136 */
137 protected DefaultListSelectionModel listSelectionModel;
138
139 /**
140 * The current selection mode.
141 */
142 protected int selectionMode;
143
144 /**
145 * The path that has been added last.
146 */
147 protected TreePath leadPath;
148
149 /**
150 * The index of the last added path.
151 */
152 protected int leadIndex;
153
154 /**
155 * The row of the last added path according to the RowMapper.
156 */
157 protected int leadRow = -1;
158
159 /**
160 * A supporting datastructure that is used in addSelectionPaths() and
161 * removeSelectionPaths(). It contains currently selected paths.
162 *
163 * @see #addSelectionPaths(TreePath[])
164 * @see #removeSelectionPaths(TreePath[])
165 * @see #setSelectionPaths(TreePath[])
166 */
167 private transient HashSet selectedPaths;
168
169 /**
170 * A supporting datastructure that is used in addSelectionPaths() and
171 * removeSelectionPaths(). It contains the paths that are added or removed.
172 *
173 * @see #addSelectionPaths(TreePath[])
174 * @see #removeSelectionPaths(TreePath[])
175 * @see #setSelectionPaths(TreePath[])
176 */
177 private transient HashSet tmpPaths;
178
179 /**
180 * Constructs a new DefaultTreeSelectionModel.
181 */
182 public DefaultTreeSelectionModel()
183 {
184 setSelectionMode(DISCONTIGUOUS_TREE_SELECTION);
185 listSelectionModel = new DefaultListSelectionModel();
186 listenerList = new EventListenerList();
187 leadIndex = -1;
188 tmpPaths = new HashSet();
189 selectedPaths = new HashSet();
190 }
191
192 /**
193 * Creates a clone of this DefaultTreeSelectionModel with the same selection.
194 * The cloned instance will have the same registered listeners, the listeners
195 * themselves will not be cloned. The selection will be cloned.
196 *
197 * @exception CloneNotSupportedException should not be thrown here
198 * @return a copy of this DefaultTreeSelectionModel
199 */
200 public Object clone() throws CloneNotSupportedException
201 {
202 DefaultTreeSelectionModel cloned =
203 (DefaultTreeSelectionModel) super.clone();
204 cloned.changeSupport = null;
205 cloned.selection = (TreePath[]) selection.clone();
206 cloned.listenerList = new EventListenerList();
207 cloned.listSelectionModel =
208 (DefaultListSelectionModel) listSelectionModel.clone();
209 cloned.selectedPaths = new HashSet();
210 cloned.tmpPaths = new HashSet();
211
212 return cloned;
213 }
214
215 /**
216 * Returns a string that shows this object's properties.
217 * The returned string lists the selected tree rows, if any.
218 *
219 * @return a string that shows this object's properties
220 */
221 public String toString()
222 {
223 if (isSelectionEmpty())
224 return "[selection empty]";
225 else
226 {
227 StringBuffer b = new StringBuffer("selected rows: [");
228 for (int i = 0; i < selection.length; i++)
229 {
230 b.append(getRow(selection[i]));
231 b.append(' ');
232 }
233 b.append(", lead " + getLeadSelectionRow());
234 return b.toString();
235 }
236 }
237
238 /**
239 * writeObject
240 *
241 * @param value0 TODO
242 * @exception IOException TODO
243 */
244 private void writeObject(ObjectOutputStream value0) throws IOException
245 {
246 // TODO
247 }
248
249 /**
250 * readObject
251 *
252 * @param value0 TODO
253 * @exception IOException TODO
254 * @exception ClassNotFoundException TODO
255 */
256 private void readObject(ObjectInputStream value0) throws IOException,
257 ClassNotFoundException
258 {
259 // TODO
260 }
261
262 /**
263 * Sets the RowMapper that should be used to map between paths and their rows.
264 *
265 * @param mapper the RowMapper to set
266 * @see RowMapper
267 */
268 public void setRowMapper(RowMapper mapper)
269 {
270 rowMapper = mapper;
271 resetRowSelection();
272 }
273
274 /**
275 * Returns the RowMapper that is currently used to map between paths and their
276 * rows.
277 *
278 * @return the current RowMapper
279 * @see RowMapper
280 */
281 public RowMapper getRowMapper()
282 {
283 return rowMapper;
284 }
285
286 /**
287 * Sets the current selection mode. Possible values are
288 * {@link #SINGLE_TREE_SELECTION}, {@link #CONTIGUOUS_TREE_SELECTION} and
289 * {@link #DISCONTIGUOUS_TREE_SELECTION}.
290 *
291 * @param mode the selection mode to be set
292 * @see #getSelectionMode
293 * @see #SINGLE_TREE_SELECTION
294 * @see #CONTIGUOUS_TREE_SELECTION
295 * @see #DISCONTIGUOUS_TREE_SELECTION
296 */
297 public void setSelectionMode(int mode)
298 {
299 int oldMode = selectionMode;
300 selectionMode = mode;
301 // Make sure we have a valid selection mode.
302 if (selectionMode != SINGLE_TREE_SELECTION
303 && selectionMode != CONTIGUOUS_TREE_SELECTION
304 && selectionMode != DISCONTIGUOUS_TREE_SELECTION)
305 selectionMode = DISCONTIGUOUS_TREE_SELECTION;
306
307 // Fire property change event.
308 if (oldMode != selectionMode && changeSupport != null)
309 changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY, oldMode,
310 selectionMode);
311 }
312
313 /**
314 * Returns the current selection mode.
315 *
316 * @return the current selection mode
317 * @see #setSelectionMode
318 * @see #SINGLE_TREE_SELECTION
319 * @see #CONTIGUOUS_TREE_SELECTION
320 * @see #DISCONTIGUOUS_TREE_SELECTION
321 */
322 public int getSelectionMode()
323 {
324 return selectionMode;
325 }
326
327 /**
328 * Sets this path as the only selection. If this changes the selection the
329 * registered TreeSelectionListeners are notified.
330 *
331 * @param path the path to set as selection
332 */
333 public void setSelectionPath(TreePath path)
334 {
335 TreePath[] paths = null;
336 if (path != null)
337 paths = new TreePath[]{ path };
338 setSelectionPaths(paths);
339 }
340
341 /**
342 * Get the number of the tree row for the given path.
343 *
344 * @param path the tree path
345 * @return the tree row for this path or -1 if the path is not visible.
346 */
347 int getRow(TreePath path)
348 {
349 RowMapper mapper = getRowMapper();
350
351 if (mapper instanceof AbstractLayoutCache)
352 {
353 // The absolute majority of cases, unless the TreeUI is very
354 // seriously rewritten
355 AbstractLayoutCache ama = (AbstractLayoutCache) mapper;
356 return ama.getRowForPath(path);
357 }
358 else if (mapper != null)
359 {
360 // Generic non optimized implementation.
361 int[] rows = mapper.getRowsForPaths(new TreePath[] { path });
362 if (rows.length == 0)
363 return - 1;
364 else
365 return rows[0];
366 }
367 return -1;
368 }
369
370 /**
371 * Sets the paths as selection. This method checks for duplicates and removes
372 * them. If this changes the selection the registered TreeSelectionListeners
373 * are notified.
374 *
375 * @param paths the paths to set as selection
376 */
377 public void setSelectionPaths(TreePath[] paths)
378 {
379 int oldLength = 0;
380 if (selection != null)
381 oldLength = selection.length;
382 int newLength = 0;
383 if (paths != null)
384 newLength = paths.length;
385 if (newLength > 0 || oldLength > 0)
386 {
387 // For SINGLE_TREE_SELECTION and for CONTIGUOUS_TREE_SELECTION with
388 // a non-contiguous path, we only allow the first path element.
389 if ((selectionMode == SINGLE_TREE_SELECTION && newLength > 1)
390 || (selectionMode == CONTIGUOUS_TREE_SELECTION && newLength > 0
391 && ! arePathsContiguous(paths)))
392 {
393 paths = new TreePath[] { paths[0] };
394 newLength = 1;
395 }
396 // Find new paths.
397 Vector changedPaths = null;
398 tmpPaths.clear();
399 int validPaths = 0;
400 TreePath oldLeadPath = leadPath;
401 for (int i = 0; i < newLength; i++)
402 {
403 if (paths[i] != null && ! tmpPaths.contains(paths[i]))
404 {
405 validPaths++;
406 tmpPaths.add(paths[i]);
407 if (! selectedPaths.contains(paths[i]))
408 {
409 if (changedPaths == null)
410 changedPaths = new Vector();
411 changedPaths.add(new PathPlaceHolder(paths[i], true));
412 }
413 leadPath = paths[i];
414 }
415 }
416 // Put together the new selection.
417 TreePath[] newSelection = null;
418 if (validPaths != 0)
419 {
420 if (validPaths != newLength)
421 {
422 // Some of the paths are already selected, put together
423 // the new selection carefully.
424 newSelection = new TreePath[validPaths];
425 Iterator newPaths = tmpPaths.iterator();
426 validPaths = 0;
427 for (int i = 0; newPaths.hasNext(); i++)
428 newSelection[i] = (TreePath) newPaths.next();
429 }
430 else
431 {
432 newSelection = new TreePath[paths.length];
433 System.arraycopy(paths, 0, newSelection, 0, paths.length);
434 }
435 }
436
437 // Find paths that have been selected, but are no more.
438 for (int i = 0; i < oldLength; i++)
439 {
440 if (selection[i] != null && ! tmpPaths.contains(selection[i]))
441 {
442 if (changedPaths == null)
443 changedPaths = new Vector();
444 changedPaths.add(new PathPlaceHolder(selection[i], false));
445 }
446 }
447
448 // Perform changes and notification.
449 selection = newSelection;
450 HashSet tmp = selectedPaths;
451 selectedPaths = tmpPaths;
452 tmpPaths = tmp;
453 tmpPaths.clear();
454
455 // Not necessary, but required according to the specs and to tests.
456 if (selection != null)
457 insureUniqueness();
458 updateLeadIndex();
459 resetRowSelection();
460 if (changedPaths != null && changedPaths.size() > 0)
461 notifyPathChange(changedPaths, oldLeadPath);
462 }
463 }
464
465 /**
466 * Adds a path to the list of selected paths. This method checks if the path
467 * is already selected and doesn't add the same path twice. If this changes
468 * the selection the registered TreeSelectionListeners are notified.
469 *
470 * The lead path is changed to the added path. This also happen if the
471 * passed path was already selected before.
472 *
473 * @param path the path to add to the selection
474 */
475 public void addSelectionPath(TreePath path)
476 {
477 if (path != null)
478 {
479 TreePath[] add = new TreePath[]{ path };
480 addSelectionPaths(add);
481 }
482 }
483
484 /**
485 * Adds the paths to the list of selected paths. This method checks if the
486 * paths are already selected and doesn't add the same path twice. If this
487 * changes the selection the registered TreeSelectionListeners are notified.
488 *
489 * @param paths the paths to add to the selection
490 */
491 public void addSelectionPaths(TreePath[] paths)
492 {
493 int length = paths != null ? paths.length : 0;
494 if (length > 0)
495 {
496 if (selectionMode == SINGLE_TREE_SELECTION)
497 setSelectionPaths(paths);
498 else if (selectionMode == CONTIGUOUS_TREE_SELECTION
499 && ! canPathsBeAdded(paths))
500 {
501 if (arePathsContiguous(paths))
502 setSelectionPaths(paths);
503 else
504 setSelectionPaths(new TreePath[] { paths[0] });
505 }
506 else
507 {
508 Vector changedPaths = null;
509 tmpPaths.clear();
510 int validPaths = 0;
511 TreePath oldLeadPath = leadPath;
512 int oldPaths = 0;
513 if (selection != null)
514 oldPaths = selection.length;
515 int i;
516 for (i = 0; i < length; i++)
517 {
518 if (paths[i] != null)
519 {
520 if (! selectedPaths.contains(paths[i]))
521 {
522 validPaths++;
523 if (changedPaths == null)
524 changedPaths = new Vector();
525 changedPaths.add(new PathPlaceHolder(paths[i], true));
526 selectedPaths.add(paths[i]);
527 tmpPaths.add(paths[i]);
528 }
529 leadPath = paths[i];
530 }
531 }
532 if (validPaths > 0)
533 {
534 TreePath[] newSelection = new TreePath[oldPaths + validPaths];
535 if (oldPaths > 0)
536 System.arraycopy(selection, 0, newSelection, 0, oldPaths);
537 if (validPaths != paths.length)
538 {
539 // Some of the paths are already selected, put together
540 // the new selection carefully.
541 Iterator newPaths = tmpPaths.iterator();
542 i = oldPaths;
543 while (newPaths.hasNext())
544 {
545 newSelection[i] = (TreePath) newPaths.next();
546 i++;
547 }
548 }
549 else
550 System.arraycopy(paths, 0, newSelection, oldPaths,
551 validPaths);
552 selection = newSelection;
553 insureUniqueness();
554 updateLeadIndex();
555 resetRowSelection();
556 if (changedPaths != null && changedPaths.size() > 0)
557 notifyPathChange(changedPaths, oldLeadPath);
558 }
559 else
560 leadPath = oldLeadPath;
561 tmpPaths.clear();
562 }
563 }
564 }
565
566 /**
567 * Removes the path from the selection. If this changes the selection the
568 * registered TreeSelectionListeners are notified.
569 *
570 * @param path the path to remove
571 */
572 public void removeSelectionPath(TreePath path)
573 {
574 if (path != null)
575 removeSelectionPaths(new TreePath[]{ path });
576 }
577
578 /**
579 * Removes the paths from the selection. If this changes the selection the
580 * registered TreeSelectionListeners are notified.
581 *
582 * @param paths the paths to remove
583 */
584 public void removeSelectionPaths(TreePath[] paths)
585 {
586 if (paths != null && selection != null && paths.length > 0)
587 {
588 if (! canPathsBeRemoved(paths))
589 clearSelection();
590 else
591 {
592 Vector pathsToRemove = null;
593 for (int i = paths.length - 1; i >= 0; i--)
594 {
595 if (paths[i] != null && selectedPaths.contains(paths[i]))
596 {
597 if (pathsToRemove == null)
598 pathsToRemove = new Vector();
599 selectedPaths.remove(paths[i]);
600 pathsToRemove.add(new PathPlaceHolder(paths[i],
601 false));
602 }
603 }
604 if (pathsToRemove != null)
605 {
606 int numRemove = pathsToRemove.size();
607 TreePath oldLead = leadPath;
608 if (numRemove == selection.length)
609 selection = null;
610 else
611 {
612 selection = new TreePath[selection.length - numRemove];
613 Iterator keep = selectedPaths.iterator();
614 for (int valid = 0; keep.hasNext(); valid++)
615 selection[valid] = (TreePath) keep.next();
616 }
617 // Update lead path.
618 if (leadPath != null && ! selectedPaths.contains(leadPath))
619 {
620 if (selection != null)
621 leadPath = selection[selection.length - 1];
622 else
623 leadPath = null;
624 }
625 else if (selection != null)
626 leadPath = selection[selection.length - 1];
627 else
628 leadPath = null;
629 updateLeadIndex();
630 resetRowSelection();
631 notifyPathChange(pathsToRemove, oldLead);
632 }
633 }
634 }
635 }
636
637 /**
638 * Returns the first path in the selection. This is especially useful when the
639 * selectionMode is {@link #SINGLE_TREE_SELECTION}.
640 *
641 * @return the first path in the selection
642 */
643 public TreePath getSelectionPath()
644 {
645 if ((selection == null) || (selection.length == 0))
646 return null;
647 else
648 return selection[0];
649 }
650
651 /**
652 * Returns the complete selection.
653 *
654 * @return the complete selection
655 */
656 public TreePath[] getSelectionPaths()
657 {
658 return selection;
659 }
660
661 /**
662 * Returns the number of paths in the selection.
663 *
664 * @return the number of paths in the selection
665 */
666 public int getSelectionCount()
667 {
668 if (selection == null)
669 return 0;
670 else
671 return selection.length;
672 }
673
674 /**
675 * Checks if a given path is in the selection.
676 *
677 * @param path the path to check
678 * @return <code>true</code> if the path is in the selection,
679 * <code>false</code> otherwise
680 */
681 public boolean isPathSelected(TreePath path)
682 {
683 if (selection == null)
684 return false;
685
686 for (int i = 0; i < selection.length; i++)
687 {
688 if (selection[i].equals(path))
689 return true;
690 }
691 return false;
692 }
693
694 /**
695 * Checks if the selection is empty.
696 *
697 * @return <code>true</code> if the selection is empty, <code>false</code>
698 * otherwise
699 */
700 public boolean isSelectionEmpty()
701 {
702 return (selection == null) || (selection.length == 0);
703 }
704
705 /**
706 * Removes all paths from the selection. Fire the unselection event.
707 */
708 public void clearSelection()
709 {
710 if (selection != null)
711 {
712 int selectionLength = selection.length;
713 boolean[] news = new boolean[selectionLength];
714 Arrays.fill(news, false);
715 TreeSelectionEvent event = new TreeSelectionEvent(this, selection,
716 news, leadPath,
717 null);
718 leadPath = null;
719 leadIndex = 0;
720 leadRow = 0;
721 selectedPaths.clear();
722 selection = null;
723 resetRowSelection();
724 fireValueChanged(event);
725 }
726 }
727
728 /**
729 * Adds a <code>TreeSelectionListener</code> object to this model.
730 *
731 * @param listener the listener to add
732 */
733 public void addTreeSelectionListener(TreeSelectionListener listener)
734 {
735 listenerList.add(TreeSelectionListener.class, listener);
736 }
737
738 /**
739 * Removes a <code>TreeSelectionListener</code> object from this model.
740 *
741 * @param listener the listener to remove
742 */
743 public void removeTreeSelectionListener(TreeSelectionListener listener)
744 {
745 listenerList.remove(TreeSelectionListener.class, listener);
746 }
747
748 /**
749 * Returns all <code>TreeSelectionListener</code> added to this model.
750 *
751 * @return an array of listeners
752 * @since 1.4
753 */
754 public TreeSelectionListener[] getTreeSelectionListeners()
755 {
756 return (TreeSelectionListener[]) getListeners(TreeSelectionListener.class);
757 }
758
759 /**
760 * fireValueChanged
761 *
762 * @param event the event to fire.
763 */
764 protected void fireValueChanged(TreeSelectionEvent event)
765 {
766 TreeSelectionListener[] listeners = getTreeSelectionListeners();
767
768 for (int i = 0; i < listeners.length; ++i)
769 listeners[i].valueChanged(event);
770 }
771
772 /**
773 * Returns all added listeners of a special type.
774 *
775 * @param listenerType the listener type
776 * @return an array of listeners
777 * @since 1.3
778 */
779 public <T extends EventListener> T[] getListeners(Class<T> listenerType)
780 {
781 return listenerList.getListeners(listenerType);
782 }
783
784 /**
785 * Returns the currently selected rows.
786 *
787 * @return the currently selected rows
788 */
789 public int[] getSelectionRows()
790 {
791 int[] rows = null;
792 if (rowMapper != null && selection != null)
793 {
794 rows = rowMapper.getRowsForPaths(selection);
795 if (rows != null)
796 {
797 // Find invisible rows.
798 int invisible = 0;
799 for (int i = rows.length - 1; i >= 0; i--)
800 {
801 if (rows[i] == -1)
802 invisible++;
803
804 }
805 // Clean up invisible rows.
806 if (invisible > 0)
807 {
808 if (invisible == rows.length)
809 rows = null;
810 else
811 {
812 int[] newRows = new int[rows.length - invisible];
813 int visCount = 0;
814 for (int i = rows.length - 1; i >= 0; i--)
815 {
816 if (rows[i] != -1)
817 {
818 newRows[visCount] = rows[i];
819 visCount++;
820 }
821 }
822 rows = newRows;
823 }
824 }
825 }
826 }
827 return rows;
828 }
829
830 /**
831 * Returns the smallest row index from the selection.
832 *
833 * @return the smallest row index from the selection
834 */
835 public int getMinSelectionRow()
836 {
837 return listSelectionModel.getMinSelectionIndex();
838 }
839
840 /**
841 * Returns the largest row index from the selection.
842 *
843 * @return the largest row index from the selection
844 */
845 public int getMaxSelectionRow()
846 {
847 return listSelectionModel.getMaxSelectionIndex();
848 }
849
850 /**
851 * Checks if a particular row is selected.
852 *
853 * @param row the index of the row to check
854 * @return <code>true</code> if the row is in this selection,
855 * <code>false</code> otherwise
856 * @throws NullPointerException if the row mapper is not set (can only happen
857 * if the user has plugged in the custom incorrect TreeUI
858 * implementation.
859 */
860 public boolean isRowSelected(int row)
861 {
862 return listSelectionModel.isSelectedIndex(row);
863 }
864
865 /**
866 * Updates the mappings from TreePaths to row indices.
867 */
868 public void resetRowSelection()
869 {
870 listSelectionModel.clearSelection();
871 if (selection != null && rowMapper != null)
872 {
873 int[] rows = rowMapper.getRowsForPaths(selection);
874 // Update list selection model.
875 for (int i = 0; i < rows.length; i++)
876 {
877 int row = rows[i];
878 if (row != -1)
879 listSelectionModel.addSelectionInterval(row, row);
880 }
881 // Update lead selection.
882 if (leadIndex != -1 && rows != null)
883 leadRow = rows[leadIndex];
884 else if (leadPath != null)
885 {
886 TreePath[] tmp = new TreePath[]{ leadPath };
887 rows = rowMapper.getRowsForPaths(tmp);
888 leadRow = rows != null ? rows[0] : -1;
889 }
890 else
891 leadRow = -1;
892 insureRowContinuity();
893 }
894 else
895 leadRow = -1;
896 }
897
898 /**
899 * getLeadSelectionRow
900 *
901 * @return int
902 */
903 public int getLeadSelectionRow()
904 {
905 return leadRow;
906 }
907
908 /**
909 * getLeadSelectionPath
910 *
911 * @return TreePath
912 */
913 public TreePath getLeadSelectionPath()
914 {
915 return leadPath;
916 }
917
918 /**
919 * Adds a <code>PropertyChangeListener</code> object to this model.
920 *
921 * @param listener the listener to add.
922 */
923 public void addPropertyChangeListener(PropertyChangeListener listener)
924 {
925 if (changeSupport == null)
926 changeSupport = new SwingPropertyChangeSupport(this);
927 changeSupport.addPropertyChangeListener(listener);
928 }
929
930 /**
931 * Removes a <code>PropertyChangeListener</code> object from this model.
932 *
933 * @param listener the listener to remove.
934 */
935 public void removePropertyChangeListener(PropertyChangeListener listener)
936 {
937 if (changeSupport != null)
938 changeSupport.removePropertyChangeListener(listener);
939 }
940
941 /**
942 * Returns all added <code>PropertyChangeListener</code> objects.
943 *
944 * @return an array of listeners.
945 * @since 1.4
946 */
947 public PropertyChangeListener[] getPropertyChangeListeners()
948 {
949 PropertyChangeListener[] listeners = null;
950 if (changeSupport != null)
951 listeners = changeSupport.getPropertyChangeListeners();
952 else
953 listeners = new PropertyChangeListener[0];
954 return listeners;
955 }
956
957 /**
958 * Makes sure the currently selected paths are valid according to the current
959 * selectionMode. If the selectionMode is set to
960 * {@link #CONTIGUOUS_TREE_SELECTION} and the selection isn't contiguous then
961 * the selection is reset to the first set of contguous paths. If the
962 * selectionMode is set to {@link #SINGLE_TREE_SELECTION} and the selection
963 * has more than one path, the selection is reset to the contain only the
964 * first path.
965 */
966 protected void insureRowContinuity()
967 {
968 if (selectionMode == CONTIGUOUS_TREE_SELECTION && selection != null
969 && rowMapper != null)
970 {
971 int min = listSelectionModel.getMinSelectionIndex();
972 if (min != -1)
973 {
974 int max = listSelectionModel.getMaxSelectionIndex();
975 for (int i = min; i <= max; i++)
976 {
977 if (! listSelectionModel.isSelectedIndex(i))
978 {
979 if (i == min)
980 clearSelection();
981 else
982 {
983 TreePath[] newSelection = new TreePath[i - min];
984 int[] rows = rowMapper.getRowsForPaths(selection);
985 for (int j = 0; j < rows.length; j++)
986 {
987 if (rows[j] < i)
988 newSelection[rows[j] - min] = selection[j];
989 }
990 setSelectionPaths(newSelection);
991 break;
992 }
993 }
994 }
995 }
996 }
997 else if (selectionMode == SINGLE_TREE_SELECTION && selection != null
998 && selection.length > 1)
999 setSelectionPath(selection[0]);
1000 }
1001
1002 /**
1003 * Returns <code>true</code> if the paths are contiguous (take subsequent
1004 * rows in the diplayed tree view. The method returns <code>true</code> if
1005 * we have no RowMapper assigned.
1006 *
1007 * @param paths the paths to check for continuity
1008 * @return <code>true</code> if the paths are contiguous or we have no
1009 * RowMapper assigned
1010 */
1011 protected boolean arePathsContiguous(TreePath[] paths)
1012 {
1013 if (rowMapper == null || paths.length < 2)
1014 return true;
1015
1016 int length = paths.length;
1017 TreePath[] tmp = new TreePath[1];
1018 tmp[0] = paths[0];
1019 int min = rowMapper.getRowsForPaths(tmp)[0];
1020 BitSet selected = new BitSet();
1021 int valid = 0;
1022 for (int i = 0; i < length; i++)
1023 {
1024 if (paths[i] != null)
1025 {
1026 tmp[0] = paths[i];
1027 int[] rows = rowMapper.getRowsForPaths(tmp);
1028 if (rows == null)
1029 return false; // No row mapping yet, can't be selected.
1030 int row = rows[0];
1031 if (row == -1 || row < (min - length) || row > (min + length))
1032 return false; // Not contiguous.
1033 min = Math.min(min, row);
1034 if (! selected.get(row))
1035 {
1036 selected.set(row);
1037 valid++;
1038 }
1039
1040 }
1041 }
1042 int max = valid + min;
1043 for (int i = min; i < max; i++)
1044 if (! selected.get(i))
1045 return false; // Not contiguous.
1046 return true;
1047 }
1048
1049 /**
1050 * Checks if the paths can be added. This returns <code>true</code> if:
1051 * <ul>
1052 * <li><code>paths</code> is <code>null</code> or empty</li>
1053 * <li>we have no RowMapper assigned</li>
1054 * <li>nothing is currently selected</li>
1055 * <li>selectionMode is {@link #DISCONTIGUOUS_TREE_SELECTION}</li>
1056 * <li>adding the paths to the selection still results in a contiguous set of
1057 * paths</li>
1058 *
1059 * @param paths the paths to check
1060 * @return <code>true</code> if the paths can be added with respect to the
1061 * selectionMode
1062 */
1063 protected boolean canPathsBeAdded(TreePath[] paths)
1064 {
1065 if (paths == null || paths.length == 0 || rowMapper == null
1066 || selection == null || selectionMode == DISCONTIGUOUS_TREE_SELECTION)
1067 return true;
1068
1069 BitSet selected = new BitSet();
1070 int min = listSelectionModel.getMinSelectionIndex();
1071 int max = listSelectionModel.getMaxSelectionIndex();
1072 TreePath[] tmp = new TreePath[1];
1073 if (min != -1)
1074 {
1075 // Set the bitmask of selected elements.
1076 for (int i = min; i <= max; i++)
1077 selected.set(i);
1078 }
1079 else
1080 {
1081 tmp[0] = paths[0];
1082 min = rowMapper.getRowsForPaths(tmp)[0];
1083 max = min;
1084 }
1085 // Mark new paths as selected.
1086 for (int i = paths.length - 1; i >= 0; i--)
1087 {
1088 if (paths[i] != null)
1089 {
1090 tmp[0] = paths[i];
1091 int[] rows = rowMapper.getRowsForPaths(tmp);
1092 if (rows == null)
1093 return false; // Now row mapping yet, can't be selected.
1094 int row = rows[0];
1095 if (row == -1)
1096 return false; // Now row mapping yet, can't be selected.
1097 min = Math.min(min, row);
1098 max = Math.max(max, row);
1099 selected.set(row);
1100 }
1101 }
1102 // Now look if the new selection would be contiguous.
1103 for (int i = min; i <= max; i++)
1104 if (! selected.get(i))
1105 return false;
1106 return true;
1107 }
1108
1109 /**
1110 * Checks if the paths can be removed without breaking the continuity of the
1111 * selection according to selectionMode.
1112 *
1113 * @param paths the paths to check
1114 * @return <code>true</code> if the paths can be removed with respect to the
1115 * selectionMode
1116 */
1117 protected boolean canPathsBeRemoved(TreePath[] paths)
1118 {
1119 if (rowMapper == null || isSelectionEmpty()
1120 || selectionMode == DISCONTIGUOUS_TREE_SELECTION)
1121 return true;
1122
1123 HashSet set = new HashSet();
1124 for (int i = 0; i < selection.length; i++)
1125 set.add(selection[i]);
1126
1127 for (int i = 0; i < paths.length; i++)
1128 set.remove(paths[i]);
1129
1130 TreePath[] remaining = new TreePath[set.size()];
1131 Iterator iter = set.iterator();
1132
1133 for (int i = 0; i < remaining.length; i++)
1134 remaining[i] = (TreePath) iter.next();
1135
1136 return arePathsContiguous(remaining);
1137 }
1138
1139 /**
1140 * Notify the installed listeners that the given patches have changed. This
1141 * method will call listeners if invoked, but it is not called from the
1142 * implementation of this class.
1143 *
1144 * @param vPaths the vector of the changed patches
1145 * @param oldLeadSelection the old selection index
1146 */
1147 protected void notifyPathChange(Vector vPaths, TreePath oldLeadSelection)
1148 {
1149
1150 int numChangedPaths = vPaths.size();
1151 boolean[] news = new boolean[numChangedPaths];
1152 TreePath[] paths = new TreePath[numChangedPaths];
1153 for (int i = 0; i < numChangedPaths; i++)
1154 {
1155 PathPlaceHolder p = (PathPlaceHolder) vPaths.get(i);
1156 news[i] = p.isNew;
1157 paths[i] = p.path;
1158 }
1159
1160 TreeSelectionEvent event = new TreeSelectionEvent(this, paths, news,
1161 oldLeadSelection,
1162 leadPath);
1163 fireValueChanged(event);
1164 }
1165
1166 /**
1167 * Updates the lead selection row number after changing the lead selection
1168 * path.
1169 */
1170 protected void updateLeadIndex()
1171 {
1172 leadIndex = -1;
1173 if (leadPath != null)
1174 {
1175 leadRow = -1;
1176 if (selection == null)
1177 leadPath = null;
1178 else
1179 {
1180 for (int i = selection.length - 1; i >= 0 && leadIndex == -1; i--)
1181 {
1182 if (selection[i] == leadPath)
1183 leadIndex = i;
1184 }
1185 }
1186 }
1187 }
1188
1189 /**
1190 * This method exists due historical reasons and returns without action
1191 * (unless overridden). For compatibility with the applications that override
1192 * it, it is still called from the {@link #setSelectionPaths(TreePath[])} and
1193 * {@link #addSelectionPaths(TreePath[])}.
1194 */
1195 protected void insureUniqueness()
1196 {
1197 // Following the API 1.4, the method should return without action.
1198 }
1199 }