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