1 /* DefaultTreeSelectionModel.java 2 Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 39 package javax.swing.tree; 40 41 import gnu.java.lang.CPStringBuilder; 42 43 import java.beans.PropertyChangeListener; 44 import java.io.IOException; 45 import java.io.ObjectInputStream; 46 import java.io.ObjectOutputStream; 47 import java.io.Serializable; 48 import java.util.Arrays; 49 import java.util.BitSet; 50 import java.util.EventListener; 51 import java.util.HashSet; 52 import java.util.Iterator; 53 import java.util.Vector; 54 55 import javax.swing.DefaultListSelectionModel; 56 import javax.swing.event.EventListenerList; 57 import javax.swing.event.SwingPropertyChangeSupport; 58 import javax.swing.event.TreeSelectionEvent; 59 import javax.swing.event.TreeSelectionListener; 60 61 /** 62 * The implementation of the default tree selection model. The installed 63 * listeners are notified about the path and not the row changes. If you 64 * specifically need to track the row changes, register the listener for the 65 * expansion events. 66 * 67 * @author Andrew Selkirk 68 * @author Audrius Meskauskas 69 */ 70 public class DefaultTreeSelectionModel 71 implements Cloneable, Serializable, TreeSelectionModel 72 { 73 74 /** 75 * According to the API docs, the method 76 * {@link DefaultTreeSelectionModel#notifyPathChange} should 77 * expect instances of a class PathPlaceHolder in the Vector parameter. 78 * This seems to be a non-public class, so I can only make guesses about the 79 * use of it. 80 */ 81 private static class PathPlaceHolder 82 { 83 /** 84 * The path that we wrap. 85 */ 86 TreePath path; 87 88 /** 89 * Indicates if the path is new or already in the selection. 90 */ 91 boolean isNew; 92 93 /** 94 * Creates a new instance. 95 * 96 * @param p the path to wrap 97 * @param n if the path is new or already in the selection 98 */ PathPlaceHolder(TreePath p, boolean n)99 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 */ DefaultTreeSelectionModel()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 */ clone()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 */ toString()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 */ writeObject(ObjectOutputStream value0)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 */ readObject(ObjectInputStream value0)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 */ setRowMapper(RowMapper mapper)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 */ getRowMapper()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 */ setSelectionMode(int mode)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 */ getSelectionMode()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 */ setSelectionPath(TreePath path)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 */ getRow(TreePath path)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 */ setSelectionPaths(TreePath[] paths)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 */ addSelectionPath(TreePath path)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 */ addSelectionPaths(TreePath[] paths)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 */ removeSelectionPath(TreePath path)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 */ removeSelectionPaths(TreePath[] paths)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 */ getSelectionPath()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 */ getSelectionPaths()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 */ getSelectionCount()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 */ isPathSelected(TreePath path)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 */ isSelectionEmpty()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 */ clearSelection()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 */ addTreeSelectionListener(TreeSelectionListener listener)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 */ removeTreeSelectionListener(TreeSelectionListener listener)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 */ getTreeSelectionListeners()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 */ fireValueChanged(TreeSelectionEvent event)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 */ getListeners(Class<T> listenerType)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 */ getSelectionRows()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 */ getMinSelectionRow()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 */ getMaxSelectionRow()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 */ isRowSelected(int row)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 */ resetRowSelection()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 */ getLeadSelectionRow()905 public int getLeadSelectionRow() 906 { 907 return leadRow; 908 } 909 910 /** 911 * getLeadSelectionPath 912 * 913 * @return TreePath 914 */ getLeadSelectionPath()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 */ addPropertyChangeListener(PropertyChangeListener listener)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 */ removePropertyChangeListener(PropertyChangeListener listener)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 */ getPropertyChangeListeners()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 */ insureRowContinuity()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 */ arePathsContiguous(TreePath[] paths)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 */ canPathsBeAdded(TreePath[] paths)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 */ canPathsBeRemoved(TreePath[] paths)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 */ notifyPathChange(Vector<PathPlaceHolder> vPaths, TreePath oldLeadSelection)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 */ updateLeadIndex()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 */ insureUniqueness()1198 protected void insureUniqueness() 1199 { 1200 // Following the API 1.4, the method should return without action. 1201 } 1202 } 1203