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