1 /* 2 * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing.tree; 27 28 import java.beans.PropertyChangeListener; 29 import java.io.*; 30 import java.util.ArrayList; 31 import java.util.BitSet; 32 import java.util.Enumeration; 33 import java.util.EventListener; 34 import java.util.Hashtable; 35 import java.util.List; 36 import java.util.Vector; 37 import javax.swing.event.*; 38 import javax.swing.DefaultListSelectionModel; 39 40 /** 41 * Default implementation of TreeSelectionModel. Listeners are notified 42 * whenever 43 * the paths in the selection change, not the rows. In order 44 * to be able to track row changes you may wish to become a listener 45 * for expansion events on the tree and test for changes from there. 46 * <p>resetRowSelection is called from any of the methods that update 47 * the selected paths. If you subclass any of these methods to 48 * filter what is allowed to be selected, be sure and message 49 * <code>resetRowSelection</code> if you do not message super. 50 * 51 * <strong>Warning:</strong> 52 * Serialized objects of this class will not be compatible with 53 * future Swing releases. The current serialization support is 54 * appropriate for short term storage or RMI between applications running 55 * the same version of Swing. As of 1.4, support for long term storage 56 * of all JavaBeans™ 57 * has been added to the <code>java.beans</code> package. 58 * Please see {@link java.beans.XMLEncoder}. 59 * 60 * @see javax.swing.JTree 61 * 62 * @author Scott Violet 63 */ 64 @SuppressWarnings("serial") 65 public class DefaultTreeSelectionModel implements Cloneable, Serializable, TreeSelectionModel 66 { 67 /** Property name for selectionMode. */ 68 public static final String SELECTION_MODE_PROPERTY = "selectionMode"; 69 70 /** Used to messaged registered listeners. */ 71 protected SwingPropertyChangeSupport changeSupport; 72 73 /** Paths that are currently selected. Will be null if nothing is 74 * currently selected. */ 75 protected TreePath[] selection; 76 77 /** Event listener list. */ 78 protected EventListenerList listenerList = new EventListenerList(); 79 80 /** Provides a row for a given path. */ 81 protected transient RowMapper rowMapper; 82 83 /** Handles maintaining the list selection model. The RowMapper is used 84 * to map from a TreePath to a row, and the value is then placed here. */ 85 protected DefaultListSelectionModel listSelectionModel; 86 87 /** Mode for the selection, will be either SINGLE_TREE_SELECTION, 88 * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION. 89 */ 90 protected int selectionMode; 91 92 /** Last path that was added. */ 93 protected TreePath leadPath; 94 /** Index of the lead path in selection. */ 95 protected int leadIndex; 96 /** Lead row. */ 97 protected int leadRow; 98 99 /** Used to make sure the paths are unique, will contain all the paths 100 * in <code>selection</code>. 101 */ 102 private Hashtable<TreePath, Boolean> uniquePaths; 103 private Hashtable<TreePath, Boolean> lastPaths; 104 private TreePath[] tempPaths; 105 106 107 /** 108 * Creates a new instance of DefaultTreeSelectionModel that is 109 * empty, with a selection mode of DISCONTIGUOUS_TREE_SELECTION. 110 */ 111 public DefaultTreeSelectionModel() { 112 listSelectionModel = new DefaultListSelectionModel(); 113 selectionMode = DISCONTIGUOUS_TREE_SELECTION; 114 leadIndex = leadRow = -1; 115 uniquePaths = new Hashtable<TreePath, Boolean>(); 116 lastPaths = new Hashtable<TreePath, Boolean>(); 117 tempPaths = new TreePath[1]; 118 } 119 120 /** 121 * Sets the RowMapper instance. This instance is used to determine 122 * the row for a particular TreePath. 123 */ 124 public void setRowMapper(RowMapper newMapper) { 125 rowMapper = newMapper; 126 resetRowSelection(); 127 } 128 129 /** 130 * Returns the RowMapper instance that is able to map a TreePath to a 131 * row. 132 */ 133 public RowMapper getRowMapper() { 134 return rowMapper; 135 } 136 137 /** 138 * Sets the selection model, which must be one of SINGLE_TREE_SELECTION, 139 * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION. If mode 140 * is not one of the defined value, 141 * <code>DISCONTIGUOUS_TREE_SELECTION</code> is assumed. 142 * <p>This may change the selection if the current selection is not valid 143 * for the new mode. For example, if three TreePaths are 144 * selected when the mode is changed to <code>SINGLE_TREE_SELECTION</code>, 145 * only one TreePath will remain selected. It is up to the particular 146 * implementation to decide what TreePath remains selected. 147 * <p> 148 * Setting the mode to something other than the defined types will 149 * result in the mode becoming <code>DISCONTIGUOUS_TREE_SELECTION</code>. 150 */ 151 public void setSelectionMode(int mode) { 152 int oldMode = selectionMode; 153 154 selectionMode = validateSelectionMode(mode); 155 if(oldMode != selectionMode && changeSupport != null) 156 changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY, 157 Integer.valueOf(oldMode), 158 Integer.valueOf(selectionMode)); 159 } 160 161 private static int validateSelectionMode(int mode) { 162 return (mode != TreeSelectionModel.SINGLE_TREE_SELECTION 163 && mode != TreeSelectionModel.CONTIGUOUS_TREE_SELECTION 164 && mode != TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) 165 ? TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION : mode; 166 } 167 168 /** 169 * Returns the selection mode, one of <code>SINGLE_TREE_SELECTION</code>, 170 * <code>DISCONTIGUOUS_TREE_SELECTION</code> or 171 * <code>CONTIGUOUS_TREE_SELECTION</code>. 172 */ 173 public int getSelectionMode() { 174 return selectionMode; 175 } 176 177 /** 178 * Sets the selection to path. If this represents a change, then 179 * the TreeSelectionListeners are notified. If <code>path</code> is 180 * null, this has the same effect as invoking <code>clearSelection</code>. 181 * 182 * @param path new path to select 183 */ 184 public void setSelectionPath(TreePath path) { 185 if(path == null) 186 setSelectionPaths(null); 187 else { 188 TreePath[] newPaths = new TreePath[1]; 189 190 newPaths[0] = path; 191 setSelectionPaths(newPaths); 192 } 193 } 194 195 /** 196 * Sets the selection. Whether the supplied paths are taken as the 197 * new selection depends upon the selection mode. If the supplied 198 * array is {@code null}, or empty, the selection is cleared. If 199 * the selection mode is {@code SINGLE_TREE_SELECTION}, only the 200 * first path in {@code pPaths} is used. If the selection 201 * mode is {@code CONTIGUOUS_TREE_SELECTION} and the supplied paths 202 * are not contiguous, then only the first path in {@code pPaths} is 203 * used. If the selection mode is 204 * {@code DISCONTIGUOUS_TREE_SELECTION}, then all paths are used. 205 * <p> 206 * All {@code null} paths in {@code pPaths} are ignored. 207 * <p> 208 * If this represents a change, all registered {@code 209 * TreeSelectionListener}s are notified. 210 * <p> 211 * The lead path is set to the last unique path. 212 * <p> 213 * The paths returned from {@code getSelectionPaths} are in the same 214 * order as those supplied to this method. 215 * 216 * @param pPaths the new selection 217 */ 218 public void setSelectionPaths(TreePath[] pPaths) { 219 int newCount, newCounter, oldCount, oldCounter; 220 TreePath[] paths = pPaths; 221 222 if(paths == null) 223 newCount = 0; 224 else 225 newCount = paths.length; 226 if(selection == null) 227 oldCount = 0; 228 else 229 oldCount = selection.length; 230 if((newCount + oldCount) != 0) { 231 if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) { 232 /* If single selection and more than one path, only allow 233 first. */ 234 if(newCount > 1) { 235 paths = new TreePath[1]; 236 paths[0] = pPaths[0]; 237 newCount = 1; 238 } 239 } 240 else if(selectionMode == 241 TreeSelectionModel.CONTIGUOUS_TREE_SELECTION) { 242 /* If contiguous selection and paths aren't contiguous, 243 only select the first path item. */ 244 if(newCount > 0 && !arePathsContiguous(paths)) { 245 paths = new TreePath[1]; 246 paths[0] = pPaths[0]; 247 newCount = 1; 248 } 249 } 250 251 TreePath beginLeadPath = leadPath; 252 Vector<PathPlaceHolder> cPaths = new Vector<PathPlaceHolder>(newCount + oldCount); 253 List<TreePath> newSelectionAsList = 254 new ArrayList<TreePath>(newCount); 255 256 lastPaths.clear(); 257 leadPath = null; 258 /* Find the paths that are new. */ 259 for(newCounter = 0; newCounter < newCount; newCounter++) { 260 TreePath path = paths[newCounter]; 261 if (path != null && lastPaths.get(path) == null) { 262 lastPaths.put(path, Boolean.TRUE); 263 if (uniquePaths.get(path) == null) { 264 cPaths.addElement(new PathPlaceHolder(path, true)); 265 } 266 leadPath = path; 267 newSelectionAsList.add(path); 268 } 269 } 270 271 TreePath[] newSelection = newSelectionAsList.toArray( 272 new TreePath[newSelectionAsList.size()]); 273 274 /* Get the paths that were selected but no longer selected. */ 275 for(oldCounter = 0; oldCounter < oldCount; oldCounter++) 276 if(selection[oldCounter] != null && 277 lastPaths.get(selection[oldCounter]) == null) 278 cPaths.addElement(new PathPlaceHolder 279 (selection[oldCounter], false)); 280 281 selection = newSelection; 282 283 Hashtable<TreePath, Boolean> tempHT = uniquePaths; 284 285 uniquePaths = lastPaths; 286 lastPaths = tempHT; 287 lastPaths.clear(); 288 289 // No reason to do this now, but will still call it. 290 insureUniqueness(); 291 292 updateLeadIndex(); 293 294 resetRowSelection(); 295 /* Notify of the change. */ 296 if(cPaths.size() > 0) 297 notifyPathChange(cPaths, beginLeadPath); 298 } 299 } 300 301 /** 302 * Adds path to the current selection. If path is not currently 303 * in the selection the TreeSelectionListeners are notified. This has 304 * no effect if <code>path</code> is null. 305 * 306 * @param path the new path to add to the current selection 307 */ 308 public void addSelectionPath(TreePath path) { 309 if(path != null) { 310 TreePath[] toAdd = new TreePath[1]; 311 312 toAdd[0] = path; 313 addSelectionPaths(toAdd); 314 } 315 } 316 317 /** 318 * Adds paths to the current selection. If any of the paths in 319 * paths are not currently in the selection the TreeSelectionListeners 320 * are notified. This has 321 * no effect if <code>paths</code> is null. 322 * <p>The lead path is set to the last element in <code>paths</code>. 323 * <p>If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>, 324 * and adding the new paths would make the selection discontiguous. 325 * Then two things can result: if the TreePaths in <code>paths</code> 326 * are contiguous, then the selection becomes these TreePaths, 327 * otherwise the TreePaths aren't contiguous and the selection becomes 328 * the first TreePath in <code>paths</code>. 329 * 330 * @param paths the new path to add to the current selection 331 */ 332 public void addSelectionPaths(TreePath[] paths) { 333 int newPathLength = ((paths == null) ? 0 : paths.length); 334 335 if(newPathLength > 0) { 336 if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) { 337 setSelectionPaths(paths); 338 } 339 else if(selectionMode == TreeSelectionModel. 340 CONTIGUOUS_TREE_SELECTION && !canPathsBeAdded(paths)) { 341 if(arePathsContiguous(paths)) { 342 setSelectionPaths(paths); 343 } 344 else { 345 TreePath[] newPaths = new TreePath[1]; 346 347 newPaths[0] = paths[0]; 348 setSelectionPaths(newPaths); 349 } 350 } 351 else { 352 int counter, validCount; 353 int oldCount; 354 TreePath beginLeadPath = leadPath; 355 Vector<PathPlaceHolder> cPaths = null; 356 357 if(selection == null) 358 oldCount = 0; 359 else 360 oldCount = selection.length; 361 /* Determine the paths that aren't currently in the 362 selection. */ 363 lastPaths.clear(); 364 for(counter = 0, validCount = 0; counter < newPathLength; 365 counter++) { 366 if(paths[counter] != null) { 367 if (uniquePaths.get(paths[counter]) == null) { 368 validCount++; 369 if(cPaths == null) 370 cPaths = new Vector<PathPlaceHolder>(); 371 cPaths.addElement(new PathPlaceHolder 372 (paths[counter], true)); 373 uniquePaths.put(paths[counter], Boolean.TRUE); 374 lastPaths.put(paths[counter], Boolean.TRUE); 375 } 376 leadPath = paths[counter]; 377 } 378 } 379 380 if(leadPath == null) { 381 leadPath = beginLeadPath; 382 } 383 384 if(validCount > 0) { 385 TreePath[] newSelection = new TreePath[oldCount + 386 validCount]; 387 388 /* And build the new selection. */ 389 if(oldCount > 0) 390 System.arraycopy(selection, 0, newSelection, 0, 391 oldCount); 392 if(validCount != paths.length) { 393 /* Some of the paths in paths are already in 394 the selection. */ 395 Enumeration<TreePath> newPaths = lastPaths.keys(); 396 397 counter = oldCount; 398 while (newPaths.hasMoreElements()) { 399 newSelection[counter++] = newPaths.nextElement(); 400 } 401 } 402 else { 403 System.arraycopy(paths, 0, newSelection, oldCount, 404 validCount); 405 } 406 407 selection = newSelection; 408 409 insureUniqueness(); 410 411 updateLeadIndex(); 412 413 resetRowSelection(); 414 415 notifyPathChange(cPaths, beginLeadPath); 416 } 417 else 418 leadPath = beginLeadPath; 419 lastPaths.clear(); 420 } 421 } 422 } 423 424 /** 425 * Removes path from the selection. If path is in the selection 426 * The TreeSelectionListeners are notified. This has no effect if 427 * <code>path</code> is null. 428 * 429 * @param path the path to remove from the selection 430 */ 431 public void removeSelectionPath(TreePath path) { 432 if(path != null) { 433 TreePath[] rPath = new TreePath[1]; 434 435 rPath[0] = path; 436 removeSelectionPaths(rPath); 437 } 438 } 439 440 /** 441 * Removes paths from the selection. If any of the paths in paths 442 * are in the selection the TreeSelectionListeners are notified. 443 * This has no effect if <code>paths</code> is null. 444 * 445 * @param paths the paths to remove from the selection 446 */ 447 public void removeSelectionPaths(TreePath[] paths) { 448 if (paths != null && selection != null && paths.length > 0) { 449 if(!canPathsBeRemoved(paths)) { 450 /* Could probably do something more interesting here! */ 451 clearSelection(); 452 } 453 else { 454 Vector<PathPlaceHolder> pathsToRemove = null; 455 456 /* Find the paths that can be removed. */ 457 for (int removeCounter = paths.length - 1; removeCounter >= 0; 458 removeCounter--) { 459 if(paths[removeCounter] != null) { 460 if (uniquePaths.get(paths[removeCounter]) != null) { 461 if(pathsToRemove == null) 462 pathsToRemove = new Vector<PathPlaceHolder>(paths.length); 463 uniquePaths.remove(paths[removeCounter]); 464 pathsToRemove.addElement(new PathPlaceHolder 465 (paths[removeCounter], false)); 466 } 467 } 468 } 469 if(pathsToRemove != null) { 470 int removeCount = pathsToRemove.size(); 471 TreePath beginLeadPath = leadPath; 472 473 if(removeCount == selection.length) { 474 selection = null; 475 } 476 else { 477 Enumeration<TreePath> pEnum = uniquePaths.keys(); 478 int validCount = 0; 479 480 selection = new TreePath[selection.length - 481 removeCount]; 482 while (pEnum.hasMoreElements()) { 483 selection[validCount++] = pEnum.nextElement(); 484 } 485 } 486 if (leadPath != null && 487 uniquePaths.get(leadPath) == null) { 488 if (selection != null) { 489 leadPath = selection[selection.length - 1]; 490 } 491 else { 492 leadPath = null; 493 } 494 } 495 else if (selection != null) { 496 leadPath = selection[selection.length - 1]; 497 } 498 else { 499 leadPath = null; 500 } 501 updateLeadIndex(); 502 503 resetRowSelection(); 504 505 notifyPathChange(pathsToRemove, beginLeadPath); 506 } 507 } 508 } 509 } 510 511 /** 512 * Returns the first path in the selection. This is useful if there 513 * if only one item currently selected. 514 */ 515 public TreePath getSelectionPath() { 516 if (selection != null && selection.length > 0) { 517 return selection[0]; 518 } 519 return null; 520 } 521 522 /** 523 * Returns the selection. 524 * 525 * @return the selection 526 */ 527 public TreePath[] getSelectionPaths() { 528 if(selection != null) { 529 int pathSize = selection.length; 530 TreePath[] result = new TreePath[pathSize]; 531 532 System.arraycopy(selection, 0, result, 0, pathSize); 533 return result; 534 } 535 return new TreePath[0]; 536 } 537 538 /** 539 * Returns the number of paths that are selected. 540 */ 541 public int getSelectionCount() { 542 return (selection == null) ? 0 : selection.length; 543 } 544 545 /** 546 * Returns true if the path, <code>path</code>, 547 * is in the current selection. 548 */ 549 public boolean isPathSelected(TreePath path) { 550 return (path != null) ? (uniquePaths.get(path) != null) : false; 551 } 552 553 /** 554 * Returns true if the selection is currently empty. 555 */ 556 public boolean isSelectionEmpty() { 557 return (selection == null || selection.length == 0); 558 } 559 560 /** 561 * Empties the current selection. If this represents a change in the 562 * current selection, the selection listeners are notified. 563 */ 564 public void clearSelection() { 565 if (selection != null && selection.length > 0) { 566 int selSize = selection.length; 567 boolean[] newness = new boolean[selSize]; 568 569 for(int counter = 0; counter < selSize; counter++) 570 newness[counter] = false; 571 572 TreeSelectionEvent event = new TreeSelectionEvent 573 (this, selection, newness, leadPath, null); 574 575 leadPath = null; 576 leadIndex = leadRow = -1; 577 uniquePaths.clear(); 578 selection = null; 579 resetRowSelection(); 580 fireValueChanged(event); 581 } 582 } 583 584 /** 585 * Adds x to the list of listeners that are notified each time the 586 * set of selected TreePaths changes. 587 * 588 * @param x the new listener to be added 589 */ 590 public void addTreeSelectionListener(TreeSelectionListener x) { 591 listenerList.add(TreeSelectionListener.class, x); 592 } 593 594 /** 595 * Removes x from the list of listeners that are notified each time 596 * the set of selected TreePaths changes. 597 * 598 * @param x the listener to remove 599 */ 600 public void removeTreeSelectionListener(TreeSelectionListener x) { 601 listenerList.remove(TreeSelectionListener.class, x); 602 } 603 604 /** 605 * Returns an array of all the tree selection listeners 606 * registered on this model. 607 * 608 * @return all of this model's <code>TreeSelectionListener</code>s 609 * or an empty 610 * array if no tree selection listeners are currently registered 611 * 612 * @see #addTreeSelectionListener 613 * @see #removeTreeSelectionListener 614 * 615 * @since 1.4 616 */ 617 public TreeSelectionListener[] getTreeSelectionListeners() { 618 return listenerList.getListeners(TreeSelectionListener.class); 619 } 620 621 /** 622 * Notifies all listeners that are registered for 623 * tree selection events on this object. 624 * 625 * @param e the event that characterizes the change 626 * 627 * @see #addTreeSelectionListener 628 * @see EventListenerList 629 */ 630 protected void fireValueChanged(TreeSelectionEvent e) { 631 // Guaranteed to return a non-null array 632 Object[] listeners = listenerList.getListenerList(); 633 // TreeSelectionEvent e = null; 634 // Process the listeners last to first, notifying 635 // those that are interested in this event 636 for (int i = listeners.length-2; i>=0; i-=2) { 637 if (listeners[i]==TreeSelectionListener.class) { 638 // Lazily create the event: 639 // if (e == null) 640 // e = new ListSelectionEvent(this, firstIndex, lastIndex); 641 ((TreeSelectionListener)listeners[i+1]).valueChanged(e); 642 } 643 } 644 } 645 646 /** 647 * Returns an array of all the objects currently registered 648 * as <code><em>Foo</em>Listener</code>s 649 * upon this model. 650 * <code><em>Foo</em>Listener</code>s are registered using the 651 * <code>add<em>Foo</em>Listener</code> method. 652 * 653 * <p> 654 * 655 * You can specify the <code>listenerType</code> argument 656 * with a class literal, 657 * such as 658 * <code><em>Foo</em>Listener.class</code>. 659 * For example, you can query a 660 * <code>DefaultTreeSelectionModel</code> <code>m</code> 661 * for its tree selection listeners with the following code: 662 * 663 * <pre>TreeSelectionListener[] tsls = (TreeSelectionListener[])(m.getListeners(TreeSelectionListener.class));</pre> 664 * 665 * If no such listeners exist, this method returns an empty array. 666 * 667 * @param <T> the listener type 668 * @param listenerType the type of listeners requested 669 * @return an array of all objects registered as 670 * <code><em>Foo</em>Listener</code>s on this component, 671 * or an empty array if no such 672 * listeners have been added 673 * @exception ClassCastException if <code>listenerType</code> 674 * doesn't specify a class or interface that implements 675 * <code>java.util.EventListener</code> 676 * 677 * @see #getTreeSelectionListeners 678 * @see #getPropertyChangeListeners 679 * 680 * @since 1.3 681 */ 682 public <T extends EventListener> T[] getListeners(Class<T> listenerType) { 683 return listenerList.getListeners(listenerType); 684 } 685 686 /** 687 * Returns the selection in terms of rows. There is not 688 * necessarily a one-to-one mapping between the {@code TreePath}s 689 * returned from {@code getSelectionPaths} and this method. In 690 * particular, if a {@code TreePath} is not viewable (the {@code 691 * RowMapper} returns {@code -1} for the row corresponding to the 692 * {@code TreePath}), then the corresponding row is not included 693 * in the returned array. For example, if the selection consists 694 * of two paths, {@code A} and {@code B}, with {@code A} at row 695 * {@code 10}, and {@code B} not currently viewable, then this method 696 * returns an array with the single entry {@code 10}. 697 * 698 * @return the selection in terms of rows 699 */ 700 public int[] getSelectionRows() { 701 // This is currently rather expensive. Needs 702 // to be better support from ListSelectionModel to speed this up. 703 if (rowMapper != null && selection != null && selection.length > 0) { 704 int[] rows = rowMapper.getRowsForPaths(selection); 705 706 if (rows != null) { 707 int invisCount = 0; 708 709 for (int counter = rows.length - 1; counter >= 0; counter--) { 710 if (rows[counter] == -1) { 711 invisCount++; 712 } 713 } 714 if (invisCount > 0) { 715 if (invisCount == rows.length) { 716 rows = null; 717 } 718 else { 719 int[] tempRows = new int[rows.length - invisCount]; 720 721 for (int counter = rows.length - 1, visCounter = 0; 722 counter >= 0; counter--) { 723 if (rows[counter] != -1) { 724 tempRows[visCounter++] = rows[counter]; 725 } 726 } 727 rows = tempRows; 728 } 729 } 730 } 731 return rows; 732 } 733 return new int[0]; 734 } 735 736 /** 737 * Returns the smallest value obtained from the RowMapper for the 738 * current set of selected TreePaths. If nothing is selected, 739 * or there is no RowMapper, this will return -1. 740 */ 741 public int getMinSelectionRow() { 742 return listSelectionModel.getMinSelectionIndex(); 743 } 744 745 /** 746 * Returns the largest value obtained from the RowMapper for the 747 * current set of selected TreePaths. If nothing is selected, 748 * or there is no RowMapper, this will return -1. 749 */ 750 public int getMaxSelectionRow() { 751 return listSelectionModel.getMaxSelectionIndex(); 752 } 753 754 /** 755 * Returns true if the row identified by <code>row</code> is selected. 756 */ 757 public boolean isRowSelected(int row) { 758 return listSelectionModel.isSelectedIndex(row); 759 } 760 761 /** 762 * Updates this object's mapping from TreePath to rows. This should 763 * be invoked when the mapping from TreePaths to integers has changed 764 * (for example, a node has been expanded). 765 * <p>You do not normally have to call this, JTree and its associated 766 * Listeners will invoke this for you. If you are implementing your own 767 * View class, then you will have to invoke this. 768 * <p>This will invoke <code>insureRowContinuity</code> to make sure 769 * the currently selected TreePaths are still valid based on the 770 * selection mode. 771 */ 772 public void resetRowSelection() { 773 listSelectionModel.clearSelection(); 774 if(selection != null && rowMapper != null) { 775 int aRow; 776 int validCount = 0; 777 int[] rows = rowMapper.getRowsForPaths(selection); 778 779 for(int counter = 0, maxCounter = selection.length; 780 counter < maxCounter; counter++) { 781 aRow = rows[counter]; 782 if(aRow != -1) { 783 listSelectionModel.addSelectionInterval(aRow, aRow); 784 } 785 } 786 if(leadIndex != -1 && rows != null) { 787 leadRow = rows[leadIndex]; 788 } 789 else if (leadPath != null) { 790 // Lead selection path doesn't have to be in the selection. 791 tempPaths[0] = leadPath; 792 rows = rowMapper.getRowsForPaths(tempPaths); 793 leadRow = (rows != null) ? rows[0] : -1; 794 } 795 else { 796 leadRow = -1; 797 } 798 insureRowContinuity(); 799 800 } 801 else 802 leadRow = -1; 803 } 804 805 /** 806 * Returns the lead selection index. That is the last index that was 807 * added. 808 */ 809 public int getLeadSelectionRow() { 810 return leadRow; 811 } 812 813 /** 814 * Returns the last path that was added. This may differ from the 815 * leadSelectionPath property maintained by the JTree. 816 */ 817 public TreePath getLeadSelectionPath() { 818 return leadPath; 819 } 820 821 /** 822 * Adds a PropertyChangeListener to the listener list. 823 * The listener is registered for all properties. 824 * <p> 825 * A PropertyChangeEvent will get fired when the selection mode 826 * changes. 827 * 828 * @param listener the PropertyChangeListener to be added 829 */ 830 public synchronized void addPropertyChangeListener( 831 PropertyChangeListener listener) { 832 if (changeSupport == null) { 833 changeSupport = new SwingPropertyChangeSupport(this); 834 } 835 changeSupport.addPropertyChangeListener(listener); 836 } 837 838 /** 839 * Removes a PropertyChangeListener from the listener list. 840 * This removes a PropertyChangeListener that was registered 841 * for all properties. 842 * 843 * @param listener the PropertyChangeListener to be removed 844 */ 845 846 public synchronized void removePropertyChangeListener( 847 PropertyChangeListener listener) { 848 if (changeSupport == null) { 849 return; 850 } 851 changeSupport.removePropertyChangeListener(listener); 852 } 853 854 /** 855 * Returns an array of all the property change listeners 856 * registered on this <code>DefaultTreeSelectionModel</code>. 857 * 858 * @return all of this model's <code>PropertyChangeListener</code>s 859 * or an empty 860 * array if no property change listeners are currently registered 861 * 862 * @see #addPropertyChangeListener 863 * @see #removePropertyChangeListener 864 * 865 * @since 1.4 866 */ 867 public PropertyChangeListener[] getPropertyChangeListeners() { 868 if (changeSupport == null) { 869 return new PropertyChangeListener[0]; 870 } 871 return changeSupport.getPropertyChangeListeners(); 872 } 873 874 /** 875 * Makes sure the currently selected <code>TreePath</code>s are valid 876 * for the current selection mode. 877 * If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code> 878 * and a <code>RowMapper</code> exists, this will make sure all 879 * the rows are contiguous, that is, when sorted all the rows are 880 * in order with no gaps. 881 * If the selection isn't contiguous, the selection is 882 * reset to contain the first set, when sorted, of contiguous rows. 883 * <p> 884 * If the selection mode is <code>SINGLE_TREE_SELECTION</code> and 885 * more than one TreePath is selected, the selection is reset to 886 * contain the first path currently selected. 887 */ 888 protected void insureRowContinuity() { 889 if(selectionMode == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION && 890 selection != null && rowMapper != null) { 891 DefaultListSelectionModel lModel = listSelectionModel; 892 int min = lModel.getMinSelectionIndex(); 893 894 if(min != -1) { 895 for(int counter = min, 896 maxCounter = lModel.getMaxSelectionIndex(); 897 counter <= maxCounter; counter++) { 898 if(!lModel.isSelectedIndex(counter)) { 899 if(counter == min) { 900 clearSelection(); 901 } 902 else { 903 TreePath[] newSel = new TreePath[counter - min]; 904 int[] selectionIndex = rowMapper.getRowsForPaths(selection); 905 // find the actual selection pathes corresponded to the 906 // rows of the new selection 907 for (int i = 0; i < selectionIndex.length; i++) { 908 if (selectionIndex[i]<counter) { 909 newSel[selectionIndex[i]-min] = selection[i]; 910 } 911 } 912 setSelectionPaths(newSel); 913 break; 914 } 915 } 916 } 917 } 918 } 919 else if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION && 920 selection != null && selection.length > 1) { 921 setSelectionPath(selection[0]); 922 } 923 } 924 925 /** 926 * Returns true if the paths are contiguous, 927 * or this object has no RowMapper. 928 * 929 * @param paths array of paths to check 930 * @return whether the paths are contiguous, or this object has no RowMapper 931 */ 932 protected boolean arePathsContiguous(TreePath[] paths) { 933 if(rowMapper == null || paths.length < 2) 934 return true; 935 else { 936 BitSet bitSet = new BitSet(32); 937 int anIndex, counter, min; 938 int pathCount = paths.length; 939 int validCount = 0; 940 TreePath[] tempPath = new TreePath[1]; 941 942 tempPath[0] = paths[0]; 943 min = rowMapper.getRowsForPaths(tempPath)[0]; 944 for(counter = 0; counter < pathCount; counter++) { 945 if(paths[counter] != null) { 946 tempPath[0] = paths[counter]; 947 int[] rows = rowMapper.getRowsForPaths(tempPath); 948 if (rows == null) { 949 return false; 950 } 951 anIndex = rows[0]; 952 if(anIndex == -1 || anIndex < (min - pathCount) || 953 anIndex > (min + pathCount)) 954 return false; 955 if(anIndex < min) 956 min = anIndex; 957 if(!bitSet.get(anIndex)) { 958 bitSet.set(anIndex); 959 validCount++; 960 } 961 } 962 } 963 int maxCounter = validCount + min; 964 965 for(counter = min; counter < maxCounter; counter++) 966 if(!bitSet.get(counter)) 967 return false; 968 } 969 return true; 970 } 971 972 /** 973 * Used to test if a particular set of <code>TreePath</code>s can 974 * be added. This will return true if <code>paths</code> is null (or 975 * empty), or this object has no RowMapper, or nothing is currently selected, 976 * or the selection mode is <code>DISCONTIGUOUS_TREE_SELECTION</code>, or 977 * adding the paths to the current selection still results in a 978 * contiguous set of <code>TreePath</code>s. 979 * 980 * @param paths array of {@code TreePaths} to check 981 * @return whether the particular set of {@code TreePaths} can be added 982 */ 983 protected boolean canPathsBeAdded(TreePath[] paths) { 984 if(paths == null || paths.length == 0 || rowMapper == null || 985 selection == null || selectionMode == 986 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) 987 return true; 988 else { 989 BitSet bitSet = new BitSet(); 990 DefaultListSelectionModel lModel = listSelectionModel; 991 int anIndex; 992 int counter; 993 int min = lModel.getMinSelectionIndex(); 994 int max = lModel.getMaxSelectionIndex(); 995 TreePath[] tempPath = new TreePath[1]; 996 997 if(min != -1) { 998 for(counter = min; counter <= max; counter++) { 999 if(lModel.isSelectedIndex(counter)) 1000 bitSet.set(counter); 1001 } 1002 } 1003 else { 1004 tempPath[0] = paths[0]; 1005 min = max = rowMapper.getRowsForPaths(tempPath)[0]; 1006 } 1007 for(counter = paths.length - 1; counter >= 0; counter--) { 1008 if(paths[counter] != null) { 1009 tempPath[0] = paths[counter]; 1010 int[] rows = rowMapper.getRowsForPaths(tempPath); 1011 if (rows == null) { 1012 return false; 1013 } 1014 anIndex = rows[0]; 1015 min = Math.min(anIndex, min); 1016 max = Math.max(anIndex, max); 1017 if(anIndex == -1) 1018 return false; 1019 bitSet.set(anIndex); 1020 } 1021 } 1022 for(counter = min; counter <= max; counter++) 1023 if(!bitSet.get(counter)) 1024 return false; 1025 } 1026 return true; 1027 } 1028 1029 /** 1030 * Returns true if the paths can be removed without breaking the 1031 * continuity of the model. 1032 * This is rather expensive. 1033 * 1034 * @param paths array of {@code TreePath} to check 1035 * @return whether the paths can be removed without breaking the 1036 * continuity of the model 1037 */ 1038 protected boolean canPathsBeRemoved(TreePath[] paths) { 1039 if(rowMapper == null || selection == null || 1040 selectionMode == TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) 1041 return true; 1042 else { 1043 BitSet bitSet = new BitSet(); 1044 int counter; 1045 int pathCount = paths.length; 1046 int anIndex; 1047 int min = -1; 1048 int validCount = 0; 1049 TreePath[] tempPath = new TreePath[1]; 1050 int[] rows; 1051 1052 /* Determine the rows for the removed entries. */ 1053 lastPaths.clear(); 1054 for (counter = 0; counter < pathCount; counter++) { 1055 if (paths[counter] != null) { 1056 lastPaths.put(paths[counter], Boolean.TRUE); 1057 } 1058 } 1059 for(counter = selection.length - 1; counter >= 0; counter--) { 1060 if(lastPaths.get(selection[counter]) == null) { 1061 tempPath[0] = selection[counter]; 1062 rows = rowMapper.getRowsForPaths(tempPath); 1063 if(rows != null && rows[0] != -1 && !bitSet.get(rows[0])) { 1064 validCount++; 1065 if(min == -1) 1066 min = rows[0]; 1067 else 1068 min = Math.min(min, rows[0]); 1069 bitSet.set(rows[0]); 1070 } 1071 } 1072 } 1073 lastPaths.clear(); 1074 /* Make sure they are contiguous. */ 1075 if(validCount > 1) { 1076 for(counter = min + validCount - 1; counter >= min; 1077 counter--) 1078 if(!bitSet.get(counter)) 1079 return false; 1080 } 1081 } 1082 return true; 1083 } 1084 1085 /** 1086 * Notifies listeners of a change in path. changePaths should contain 1087 * instances of PathPlaceHolder. 1088 * 1089 * @deprecated As of JDK version 1.7 1090 * 1091 * @param changedPaths the vector of the changed paths 1092 * @param oldLeadSelection the old selection path 1093 */ 1094 @Deprecated 1095 protected void notifyPathChange(Vector<?> changedPaths, 1096 TreePath oldLeadSelection) { 1097 int cPathCount = changedPaths.size(); 1098 boolean[] newness = new boolean[cPathCount]; 1099 TreePath[] paths = new TreePath[cPathCount]; 1100 PathPlaceHolder placeholder; 1101 1102 for(int counter = 0; counter < cPathCount; counter++) { 1103 placeholder = (PathPlaceHolder) changedPaths.elementAt(counter); 1104 newness[counter] = placeholder.isNew; 1105 paths[counter] = placeholder.path; 1106 } 1107 1108 TreeSelectionEvent event = new TreeSelectionEvent 1109 (this, paths, newness, oldLeadSelection, leadPath); 1110 1111 fireValueChanged(event); 1112 } 1113 1114 /** 1115 * Updates the leadIndex instance variable. 1116 */ 1117 protected void updateLeadIndex() { 1118 if(leadPath != null) { 1119 if(selection == null) { 1120 leadPath = null; 1121 leadIndex = leadRow = -1; 1122 } 1123 else { 1124 leadRow = leadIndex = -1; 1125 for(int counter = selection.length - 1; counter >= 0; 1126 counter--) { 1127 // Can use == here since we know leadPath came from 1128 // selection 1129 if(selection[counter] == leadPath) { 1130 leadIndex = counter; 1131 break; 1132 } 1133 } 1134 } 1135 } 1136 else { 1137 leadIndex = -1; 1138 } 1139 } 1140 1141 /** 1142 * This method is obsolete and its implementation is now a noop. It's 1143 * still called by setSelectionPaths and addSelectionPaths, but only 1144 * for backwards compatibility. 1145 */ 1146 protected void insureUniqueness() { 1147 } 1148 1149 1150 /** 1151 * Returns a string that displays and identifies this 1152 * object's properties. 1153 * 1154 * @return a String representation of this object 1155 */ 1156 public String toString() { 1157 int selCount = getSelectionCount(); 1158 StringBuilder sb = new StringBuilder(); 1159 int[] rows; 1160 1161 if(rowMapper != null) 1162 rows = rowMapper.getRowsForPaths(selection); 1163 else 1164 rows = null; 1165 sb.append(getClass().getName() + " " + hashCode() + " [ "); 1166 for(int counter = 0; counter < selCount; counter++) { 1167 if(rows != null) 1168 sb.append(selection[counter].toString() + "@" + 1169 Integer.toString(rows[counter])+ " "); 1170 else 1171 sb.append(selection[counter].toString() + " "); 1172 } 1173 sb.append("]"); 1174 return sb.toString(); 1175 } 1176 1177 /** 1178 * Returns a clone of this object with the same selection. 1179 * This method does not duplicate 1180 * selection listeners and property listeners. 1181 * 1182 * @exception CloneNotSupportedException never thrown by instances of 1183 * this class 1184 */ 1185 public Object clone() throws CloneNotSupportedException { 1186 DefaultTreeSelectionModel clone = (DefaultTreeSelectionModel) 1187 super.clone(); 1188 1189 clone.changeSupport = null; 1190 if(selection != null) { 1191 int selLength = selection.length; 1192 1193 clone.selection = new TreePath[selLength]; 1194 System.arraycopy(selection, 0, clone.selection, 0, selLength); 1195 } 1196 clone.listenerList = new EventListenerList(); 1197 clone.listSelectionModel = (DefaultListSelectionModel) 1198 listSelectionModel.clone(); 1199 clone.uniquePaths = new Hashtable<TreePath, Boolean>(); 1200 clone.lastPaths = new Hashtable<TreePath, Boolean>(); 1201 clone.tempPaths = new TreePath[1]; 1202 return clone; 1203 } 1204 1205 // Serialization support. 1206 private void writeObject(ObjectOutputStream s) throws IOException { 1207 Object[] tValues; 1208 1209 s.defaultWriteObject(); 1210 // Save the rowMapper, if it implements Serializable 1211 if(rowMapper != null && rowMapper instanceof Serializable) { 1212 tValues = new Object[2]; 1213 tValues[0] = "rowMapper"; 1214 tValues[1] = rowMapper; 1215 } 1216 else 1217 tValues = new Object[0]; 1218 s.writeObject(tValues); 1219 } 1220 1221 1222 private void readObject(ObjectInputStream s) 1223 throws IOException, ClassNotFoundException { 1224 ObjectInputStream.GetField f = s.readFields(); 1225 1226 changeSupport = (SwingPropertyChangeSupport) f.get("changeSupport", null); 1227 selection = (TreePath[]) f.get("selection", null); 1228 EventListenerList newListenerList = (EventListenerList) f.get("listenerList", null); 1229 if (newListenerList == null) { 1230 throw new InvalidObjectException("Null listenerList"); 1231 } 1232 listenerList = newListenerList; 1233 listSelectionModel = (DefaultListSelectionModel) f.get("listSelectionModel", null); 1234 selectionMode = validateSelectionMode(f.get("selectionMode", 0)); 1235 leadPath = (TreePath) f.get("leadPath", null); 1236 leadIndex = f.get("leadIndex", 0); 1237 leadRow = f.get("leadRow", 0); 1238 @SuppressWarnings("unchecked") 1239 Hashtable<TreePath, Boolean> newUniquePaths = 1240 (Hashtable<TreePath, Boolean>) f.get("uniquePaths", null); 1241 uniquePaths = newUniquePaths; 1242 @SuppressWarnings("unchecked") 1243 Hashtable<TreePath, Boolean> newLastPaths = 1244 (Hashtable<TreePath, Boolean>) f.get("lastPaths", null); 1245 lastPaths = newLastPaths; 1246 tempPaths = (TreePath[]) f.get("tempPaths", null); 1247 1248 Object[] tValues; 1249 tValues = (Object[])s.readObject(); 1250 1251 if (tValues.length > 0 && tValues[0].equals("rowMapper")) { 1252 RowMapper newRowMapper = (RowMapper) tValues[1]; 1253 if (newRowMapper == null) { 1254 throw new InvalidObjectException("Null newRowMapper"); 1255 } 1256 rowMapper = newRowMapper; 1257 } 1258 } 1259 } 1260 1261 /** 1262 * Holds a path and whether or not it is new. 1263 */ 1264 class PathPlaceHolder { 1265 protected boolean isNew; 1266 protected TreePath path; 1267 1268 PathPlaceHolder(TreePath path, boolean isNew) { 1269 this.path = path; 1270 this.isNew = isNew; 1271 } 1272 }