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 }