1 /* 2 * Copyright (c) 1997, 2014, 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 transient protected 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 listenerType the type of listeners requested; this parameter 668 * should specify an interface that descends from 669 * <code>java.util.EventListener</code> 670 * @return an array of all objects registered as 671 * <code><em>Foo</em>Listener</code>s on this component, 672 * or an empty array if no such 673 * listeners have been added 674 * @exception ClassCastException if <code>listenerType</code> 675 * doesn't specify a class or interface that implements 676 * <code>java.util.EventListener</code> 677 * 678 * @see #getTreeSelectionListeners 679 * @see #getPropertyChangeListeners 680 * 681 * @since 1.3 682 */ 683 public <T extends EventListener> T[] getListeners(Class<T> listenerType) { 684 return listenerList.getListeners(listenerType); 685 } 686 687 /** 688 * Returns the selection in terms of rows. There is not 689 * necessarily a one-to-one mapping between the {@code TreePath}s 690 * returned from {@code getSelectionPaths} and this method. In 691 * particular, if a {@code TreePath} is not viewable (the {@code 692 * RowMapper} returns {@code -1} for the row corresponding to the 693 * {@code TreePath}), then the corresponding row is not included 694 * in the returned array. For example, if the selection consists 695 * of two paths, {@code A} and {@code B}, with {@code A} at row 696 * {@code 10}, and {@code B} not currently viewable, then this method 697 * returns an array with the single entry {@code 10}. 698 * 699 * @return the selection in terms of rows 700 */ 701 public int[] getSelectionRows() { 702 // This is currently rather expensive. Needs 703 // to be better support from ListSelectionModel to speed this up. 704 if (rowMapper != null && selection != null && selection.length > 0) { 705 int[] rows = rowMapper.getRowsForPaths(selection); 706 707 if (rows != null) { 708 int invisCount = 0; 709 710 for (int counter = rows.length - 1; counter >= 0; counter--) { 711 if (rows[counter] == -1) { 712 invisCount++; 713 } 714 } 715 if (invisCount > 0) { 716 if (invisCount == rows.length) { 717 rows = null; 718 } 719 else { 720 int[] tempRows = new int[rows.length - invisCount]; 721 722 for (int counter = rows.length - 1, visCounter = 0; 723 counter >= 0; counter--) { 724 if (rows[counter] != -1) { 725 tempRows[visCounter++] = rows[counter]; 726 } 727 } 728 rows = tempRows; 729 } 730 } 731 } 732 return rows; 733 } 734 return new int[0]; 735 } 736 737 /** 738 * Returns the smallest value obtained from the RowMapper for the 739 * current set of selected TreePaths. If nothing is selected, 740 * or there is no RowMapper, this will return -1. 741 */ 742 public int getMinSelectionRow() { 743 return listSelectionModel.getMinSelectionIndex(); 744 } 745 746 /** 747 * Returns the largest value obtained from the RowMapper for the 748 * current set of selected TreePaths. If nothing is selected, 749 * or there is no RowMapper, this will return -1. 750 */ 751 public int getMaxSelectionRow() { 752 return listSelectionModel.getMaxSelectionIndex(); 753 } 754 755 /** 756 * Returns true if the row identified by <code>row</code> is selected. 757 */ 758 public boolean isRowSelected(int row) { 759 return listSelectionModel.isSelectedIndex(row); 760 } 761 762 /** 763 * Updates this object's mapping from TreePath to rows. This should 764 * be invoked when the mapping from TreePaths to integers has changed 765 * (for example, a node has been expanded). 766 * <p>You do not normally have to call this, JTree and its associated 767 * Listeners will invoke this for you. If you are implementing your own 768 * View class, then you will have to invoke this. 769 * <p>This will invoke <code>insureRowContinuity</code> to make sure 770 * the currently selected TreePaths are still valid based on the 771 * selection mode. 772 */ 773 public void resetRowSelection() { 774 listSelectionModel.clearSelection(); 775 if(selection != null && rowMapper != null) { 776 int aRow; 777 int validCount = 0; 778 int[] rows = rowMapper.getRowsForPaths(selection); 779 780 for(int counter = 0, maxCounter = selection.length; 781 counter < maxCounter; counter++) { 782 aRow = rows[counter]; 783 if(aRow != -1) { 784 listSelectionModel.addSelectionInterval(aRow, aRow); 785 } 786 } 787 if(leadIndex != -1 && rows != null) { 788 leadRow = rows[leadIndex]; 789 } 790 else if (leadPath != null) { 791 // Lead selection path doesn't have to be in the selection. 792 tempPaths[0] = leadPath; 793 rows = rowMapper.getRowsForPaths(tempPaths); 794 leadRow = (rows != null) ? rows[0] : -1; 795 } 796 else { 797 leadRow = -1; 798 } 799 insureRowContinuity(); 800 801 } 802 else 803 leadRow = -1; 804 } 805 806 /** 807 * Returns the lead selection index. That is the last index that was 808 * added. 809 */ 810 public int getLeadSelectionRow() { 811 return leadRow; 812 } 813 814 /** 815 * Returns the last path that was added. This may differ from the 816 * leadSelectionPath property maintained by the JTree. 817 */ 818 public TreePath getLeadSelectionPath() { 819 return leadPath; 820 } 821 822 /** 823 * Adds a PropertyChangeListener to the listener list. 824 * The listener is registered for all properties. 825 * <p> 826 * A PropertyChangeEvent will get fired when the selection mode 827 * changes. 828 * 829 * @param listener the PropertyChangeListener to be added 830 */ 831 public synchronized void addPropertyChangeListener( 832 PropertyChangeListener listener) { 833 if (changeSupport == null) { 834 changeSupport = new SwingPropertyChangeSupport(this); 835 } 836 changeSupport.addPropertyChangeListener(listener); 837 } 838 839 /** 840 * Removes a PropertyChangeListener from the listener list. 841 * This removes a PropertyChangeListener that was registered 842 * for all properties. 843 * 844 * @param listener the PropertyChangeListener to be removed 845 */ 846 847 public synchronized void removePropertyChangeListener( 848 PropertyChangeListener listener) { 849 if (changeSupport == null) { 850 return; 851 } 852 changeSupport.removePropertyChangeListener(listener); 853 } 854 855 /** 856 * Returns an array of all the property change listeners 857 * registered on this <code>DefaultTreeSelectionModel</code>. 858 * 859 * @return all of this model's <code>PropertyChangeListener</code>s 860 * or an empty 861 * array if no property change listeners are currently registered 862 * 863 * @see #addPropertyChangeListener 864 * @see #removePropertyChangeListener 865 * 866 * @since 1.4 867 */ 868 public PropertyChangeListener[] getPropertyChangeListeners() { 869 if (changeSupport == null) { 870 return new PropertyChangeListener[0]; 871 } 872 return changeSupport.getPropertyChangeListeners(); 873 } 874 875 /** 876 * Makes sure the currently selected <code>TreePath</code>s are valid 877 * for the current selection mode. 878 * If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code> 879 * and a <code>RowMapper</code> exists, this will make sure all 880 * the rows are contiguous, that is, when sorted all the rows are 881 * in order with no gaps. 882 * If the selection isn't contiguous, the selection is 883 * reset to contain the first set, when sorted, of contiguous rows. 884 * <p> 885 * If the selection mode is <code>SINGLE_TREE_SELECTION</code> and 886 * more than one TreePath is selected, the selection is reset to 887 * contain the first path currently selected. 888 */ 889 protected void insureRowContinuity() { 890 if(selectionMode == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION && 891 selection != null && rowMapper != null) { 892 DefaultListSelectionModel lModel = listSelectionModel; 893 int min = lModel.getMinSelectionIndex(); 894 895 if(min != -1) { 896 for(int counter = min, 897 maxCounter = lModel.getMaxSelectionIndex(); 898 counter <= maxCounter; counter++) { 899 if(!lModel.isSelectedIndex(counter)) { 900 if(counter == min) { 901 clearSelection(); 902 } 903 else { 904 TreePath[] newSel = new TreePath[counter - min]; 905 int selectionIndex[] = rowMapper.getRowsForPaths(selection); 906 // find the actual selection pathes corresponded to the 907 // rows of the new selection 908 for (int i = 0; i < selectionIndex.length; i++) { 909 if (selectionIndex[i]<counter) { 910 newSel[selectionIndex[i]-min] = selection[i]; 911 } 912 } 913 setSelectionPaths(newSel); 914 break; 915 } 916 } 917 } 918 } 919 } 920 else if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION && 921 selection != null && selection.length > 1) { 922 setSelectionPath(selection[0]); 923 } 924 } 925 926 /** 927 * Returns true if the paths are contiguous, 928 * or this object has no RowMapper. 929 * 930 * @param paths array of paths to check 931 * @return whether the paths are contiguous, or this object has no RowMapper 932 */ 933 protected boolean arePathsContiguous(TreePath[] paths) { 934 if(rowMapper == null || paths.length < 2) 935 return true; 936 else { 937 BitSet bitSet = new BitSet(32); 938 int anIndex, counter, min; 939 int pathCount = paths.length; 940 int validCount = 0; 941 TreePath[] tempPath = new TreePath[1]; 942 943 tempPath[0] = paths[0]; 944 min = rowMapper.getRowsForPaths(tempPath)[0]; 945 for(counter = 0; counter < pathCount; counter++) { 946 if(paths[counter] != null) { 947 tempPath[0] = paths[counter]; 948 int[] rows = rowMapper.getRowsForPaths(tempPath); 949 if (rows == null) { 950 return false; 951 } 952 anIndex = rows[0]; 953 if(anIndex == -1 || anIndex < (min - pathCount) || 954 anIndex > (min + pathCount)) 955 return false; 956 if(anIndex < min) 957 min = anIndex; 958 if(!bitSet.get(anIndex)) { 959 bitSet.set(anIndex); 960 validCount++; 961 } 962 } 963 } 964 int maxCounter = validCount + min; 965 966 for(counter = min; counter < maxCounter; counter++) 967 if(!bitSet.get(counter)) 968 return false; 969 } 970 return true; 971 } 972 973 /** 974 * Used to test if a particular set of <code>TreePath</code>s can 975 * be added. This will return true if <code>paths</code> is null (or 976 * empty), or this object has no RowMapper, or nothing is currently selected, 977 * or the selection mode is <code>DISCONTIGUOUS_TREE_SELECTION</code>, or 978 * adding the paths to the current selection still results in a 979 * contiguous set of <code>TreePath</code>s. 980 * 981 * @param paths array of {@code TreePaths} to check 982 * @return whether the particular set of {@code TreePaths} can be added 983 */ 984 protected boolean canPathsBeAdded(TreePath[] paths) { 985 if(paths == null || paths.length == 0 || rowMapper == null || 986 selection == null || selectionMode == 987 TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) 988 return true; 989 else { 990 BitSet bitSet = new BitSet(); 991 DefaultListSelectionModel lModel = listSelectionModel; 992 int anIndex; 993 int counter; 994 int min = lModel.getMinSelectionIndex(); 995 int max = lModel.getMaxSelectionIndex(); 996 TreePath[] tempPath = new TreePath[1]; 997 998 if(min != -1) { 999 for(counter = min; counter <= max; counter++) { 1000 if(lModel.isSelectedIndex(counter)) 1001 bitSet.set(counter); 1002 } 1003 } 1004 else { 1005 tempPath[0] = paths[0]; 1006 min = max = rowMapper.getRowsForPaths(tempPath)[0]; 1007 } 1008 for(counter = paths.length - 1; counter >= 0; counter--) { 1009 if(paths[counter] != null) { 1010 tempPath[0] = paths[counter]; 1011 int[] rows = rowMapper.getRowsForPaths(tempPath); 1012 if (rows == null) { 1013 return false; 1014 } 1015 anIndex = rows[0]; 1016 min = Math.min(anIndex, min); 1017 max = Math.max(anIndex, max); 1018 if(anIndex == -1) 1019 return false; 1020 bitSet.set(anIndex); 1021 } 1022 } 1023 for(counter = min; counter <= max; counter++) 1024 if(!bitSet.get(counter)) 1025 return false; 1026 } 1027 return true; 1028 } 1029 1030 /** 1031 * Returns true if the paths can be removed without breaking the 1032 * continuity of the model. 1033 * This is rather expensive. 1034 * 1035 * @param paths array of {@code TreePath} to check 1036 * @return whether the paths can be removed without breaking the 1037 * continuity of the model 1038 */ 1039 protected boolean canPathsBeRemoved(TreePath[] paths) { 1040 if(rowMapper == null || selection == null || 1041 selectionMode == TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) 1042 return true; 1043 else { 1044 BitSet bitSet = new BitSet(); 1045 int counter; 1046 int pathCount = paths.length; 1047 int anIndex; 1048 int min = -1; 1049 int validCount = 0; 1050 TreePath[] tempPath = new TreePath[1]; 1051 int[] rows; 1052 1053 /* Determine the rows for the removed entries. */ 1054 lastPaths.clear(); 1055 for (counter = 0; counter < pathCount; counter++) { 1056 if (paths[counter] != null) { 1057 lastPaths.put(paths[counter], Boolean.TRUE); 1058 } 1059 } 1060 for(counter = selection.length - 1; counter >= 0; counter--) { 1061 if(lastPaths.get(selection[counter]) == null) { 1062 tempPath[0] = selection[counter]; 1063 rows = rowMapper.getRowsForPaths(tempPath); 1064 if(rows != null && rows[0] != -1 && !bitSet.get(rows[0])) { 1065 validCount++; 1066 if(min == -1) 1067 min = rows[0]; 1068 else 1069 min = Math.min(min, rows[0]); 1070 bitSet.set(rows[0]); 1071 } 1072 } 1073 } 1074 lastPaths.clear(); 1075 /* Make sure they are contiguous. */ 1076 if(validCount > 1) { 1077 for(counter = min + validCount - 1; counter >= min; 1078 counter--) 1079 if(!bitSet.get(counter)) 1080 return false; 1081 } 1082 } 1083 return true; 1084 } 1085 1086 /** 1087 * Notifies listeners of a change in path. changePaths should contain 1088 * instances of PathPlaceHolder. 1089 * 1090 * @deprecated As of JDK version 1.7 1091 * 1092 * @param changedPaths the vector of the changed paths 1093 * @param oldLeadSelection the old selection path 1094 */ 1095 @Deprecated 1096 protected void notifyPathChange(Vector<?> changedPaths, 1097 TreePath oldLeadSelection) { 1098 int cPathCount = changedPaths.size(); 1099 boolean[] newness = new boolean[cPathCount]; 1100 TreePath[] paths = new TreePath[cPathCount]; 1101 PathPlaceHolder placeholder; 1102 1103 for(int counter = 0; counter < cPathCount; counter++) { 1104 placeholder = (PathPlaceHolder) changedPaths.elementAt(counter); 1105 newness[counter] = placeholder.isNew; 1106 paths[counter] = placeholder.path; 1107 } 1108 1109 TreeSelectionEvent event = new TreeSelectionEvent 1110 (this, paths, newness, oldLeadSelection, leadPath); 1111 1112 fireValueChanged(event); 1113 } 1114 1115 /** 1116 * Updates the leadIndex instance variable. 1117 */ 1118 protected void updateLeadIndex() { 1119 if(leadPath != null) { 1120 if(selection == null) { 1121 leadPath = null; 1122 leadIndex = leadRow = -1; 1123 } 1124 else { 1125 leadRow = leadIndex = -1; 1126 for(int counter = selection.length - 1; counter >= 0; 1127 counter--) { 1128 // Can use == here since we know leadPath came from 1129 // selection 1130 if(selection[counter] == leadPath) { 1131 leadIndex = counter; 1132 break; 1133 } 1134 } 1135 } 1136 } 1137 else { 1138 leadIndex = -1; 1139 } 1140 } 1141 1142 /** 1143 * This method is obsolete and its implementation is now a noop. It's 1144 * still called by setSelectionPaths and addSelectionPaths, but only 1145 * for backwards compatibility. 1146 */ 1147 protected void insureUniqueness() { 1148 } 1149 1150 1151 /** 1152 * Returns a string that displays and identifies this 1153 * object's properties. 1154 * 1155 * @return a String representation of this object 1156 */ 1157 public String toString() { 1158 int selCount = getSelectionCount(); 1159 StringBuilder sb = new StringBuilder(); 1160 int[] rows; 1161 1162 if(rowMapper != null) 1163 rows = rowMapper.getRowsForPaths(selection); 1164 else 1165 rows = null; 1166 sb.append(getClass().getName()).append(' ').append(hashCode()).append(" [ "); 1167 for(int counter = 0; counter < selCount; counter++) { 1168 if(rows != null) 1169 sb.append(selection[counter]).append('@') 1170 .append(rows[counter]).append(' '); 1171 else 1172 sb.append(selection[counter]).append(' '); 1173 } 1174 sb.append(']'); 1175 return sb.toString(); 1176 } 1177 1178 /** 1179 * Returns a clone of this object with the same selection. 1180 * This method does not duplicate 1181 * selection listeners and property listeners. 1182 * 1183 * @exception CloneNotSupportedException never thrown by instances of 1184 * this class 1185 */ 1186 public Object clone() throws CloneNotSupportedException { 1187 DefaultTreeSelectionModel clone = (DefaultTreeSelectionModel) 1188 super.clone(); 1189 1190 clone.changeSupport = null; 1191 if(selection != null) { 1192 int selLength = selection.length; 1193 1194 clone.selection = new TreePath[selLength]; 1195 System.arraycopy(selection, 0, clone.selection, 0, selLength); 1196 } 1197 clone.listenerList = new EventListenerList(); 1198 clone.listSelectionModel = (DefaultListSelectionModel) 1199 listSelectionModel.clone(); 1200 clone.uniquePaths = new Hashtable<TreePath, Boolean>(); 1201 clone.lastPaths = new Hashtable<TreePath, Boolean>(); 1202 clone.tempPaths = new TreePath[1]; 1203 return clone; 1204 } 1205 1206 // Serialization support. 1207 private void writeObject(ObjectOutputStream s) throws IOException { 1208 Object[] tValues; 1209 1210 s.defaultWriteObject(); 1211 // Save the rowMapper, if it implements Serializable 1212 if(rowMapper != null && rowMapper instanceof Serializable) { 1213 tValues = new Object[2]; 1214 tValues[0] = "rowMapper"; 1215 tValues[1] = rowMapper; 1216 } 1217 else 1218 tValues = new Object[0]; 1219 s.writeObject(tValues); 1220 } 1221 1222 1223 private void readObject(ObjectInputStream s) 1224 throws IOException, ClassNotFoundException { 1225 ObjectInputStream.GetField f = s.readFields(); 1226 1227 changeSupport = (SwingPropertyChangeSupport) f.get("changeSupport", null); 1228 selection = (TreePath[]) f.get("selection", null); 1229 EventListenerList newListenerList = (EventListenerList) f.get("listenerList", null); 1230 if (newListenerList == null) { 1231 throw new InvalidObjectException("Null listenerList"); 1232 } 1233 listenerList = newListenerList; 1234 listSelectionModel = (DefaultListSelectionModel) f.get("listSelectionModel", null); 1235 selectionMode = validateSelectionMode(f.get("selectionMode", 0)); 1236 leadPath = (TreePath) f.get("leadPath", null); 1237 leadIndex = f.get("leadIndex", 0); 1238 leadRow = f.get("leadRow", 0); 1239 @SuppressWarnings("unchecked") 1240 Hashtable<TreePath, Boolean> newUniquePaths = 1241 (Hashtable<TreePath, Boolean>) f.get("uniquePaths", null); 1242 uniquePaths = newUniquePaths; 1243 @SuppressWarnings("unchecked") 1244 Hashtable<TreePath, Boolean> newLastPaths = 1245 (Hashtable<TreePath, Boolean>) f.get("lastPaths", null); 1246 lastPaths = newLastPaths; 1247 tempPaths = (TreePath[]) f.get("tempPaths", null); 1248 1249 Object[] tValues; 1250 tValues = (Object[])s.readObject(); 1251 1252 if (tValues.length > 0 && tValues[0].equals("rowMapper")) { 1253 RowMapper newRowMapper = (RowMapper) tValues[1]; 1254 if (newRowMapper == null) { 1255 throw new InvalidObjectException("Null newRowMapper"); 1256 } 1257 rowMapper = newRowMapper; 1258 } 1259 } 1260 } 1261 1262 /** 1263 * Holds a path and whether or not it is new. 1264 */ 1265 class PathPlaceHolder { 1266 protected boolean isNew; 1267 protected TreePath path; 1268 1269 PathPlaceHolder(TreePath path, boolean isNew) { 1270 this.path = path; 1271 this.isNew = isNew; 1272 } 1273 }