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