/plugins/SideKick/tags/SideKick-1.2/sidekick/FilteredTreeModel.java

# · Java · 497 lines · 338 code · 63 blank · 96 comment · 62 complexity · c06debc49e50b12b6d9af0700299c484 MD5 · raw file

  1. package sidekick;
  2. /**
  3. * FilteredTreeModel.java
  4. *
  5. * Modified by Matt Gilbert on 2008/02/26 from the source distribution of LimeWire.
  6. *
  7. * This program is free software; you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation; either version 2 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, write to the Free Software
  19. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  20. */
  21. import java.util.ArrayList;
  22. import java.util.HashSet;
  23. import java.util.List;
  24. import java.util.Set;
  25. import java.util.SortedMap;
  26. import java.util.TreeMap;
  27. import javax.swing.event.TreeModelEvent;
  28. import javax.swing.event.TreeModelListener;
  29. import javax.swing.tree.DefaultTreeModel;
  30. import javax.swing.tree.TreeModel;
  31. import javax.swing.tree.TreeNode;
  32. import javax.swing.tree.TreePath;
  33. /**
  34. * This class provides a filtered view on an underlying {@link TreeModel}. Nodes
  35. * may be associated with keywords that can be searched for hiding all nodes
  36. * that do not match the search term.
  37. */
  38. public class FilteredTreeModel implements TreeModel
  39. {
  40. private boolean ignoreCase;
  41. private FilteredTreeModelListener listener;
  42. private List<TreeModelListener> listeners = new ArrayList<TreeModelListener>();
  43. /** The underlying data model. */
  44. private TreeModel model;
  45. private ParentProvider parentProvider;
  46. /** Maps search keywords to lists of matching nodes. */
  47. private TreeMap<String, List<Object>> searchMap = new TreeMap<String, List<Object>>();
  48. /**
  49. * Currently visible nodes. If <code>null</code>, all nodes are visible.
  50. */
  51. private Set<Object> visibleNodes;
  52. /**
  53. * Constructs a filtering tree model.
  54. *
  55. * @param model
  56. * the underlying data model
  57. * @param ignoreCase
  58. * if true, filtering is case insensitive
  59. */
  60. public FilteredTreeModel(DefaultTreeModel model, boolean ignoreCase)
  61. {
  62. this(model, ignoreCase, new TreeNodeParentProvider());
  63. }
  64. /**
  65. * Constructs a filtering tree model.
  66. *
  67. * @param model
  68. * the underlying data model
  69. * @param ignoreCase
  70. * if true, filtering is case insensitive
  71. * @param parentProvider
  72. * used to retrieve parents of nodes
  73. */
  74. public FilteredTreeModel(TreeModel model, boolean ignoreCase, ParentProvider parentProvider)
  75. {
  76. this.ignoreCase = ignoreCase;
  77. this.listener = new FilteredTreeModelListener();
  78. setModel(model, parentProvider);
  79. }
  80. /**
  81. * Associates <code>node</code> with a search <code>key</code>.
  82. */
  83. public void addSearchKey(Object node, String key)
  84. {
  85. key = normalize(key);
  86. List<Object> value = searchMap.get(key);
  87. if (value == null)
  88. {
  89. value = new ArrayList<Object>(1);
  90. searchMap.put(key, value);
  91. }
  92. value.add(node);
  93. }
  94. public void addTreeModelListener(TreeModelListener l)
  95. {
  96. listeners.add(l);
  97. }
  98. /**
  99. * Makes all nodes in the tree visible.
  100. */
  101. public void clearFilter()
  102. {
  103. filterByText(null);
  104. }
  105. protected SortedMap<String, List<Object>> getMatches(String text)
  106. {
  107. SortedMap<String, List<Object>> map = new TreeMap<String, List<Object>>();
  108. for (String s : searchMap.keySet())
  109. {
  110. if (s.indexOf(text) != -1)
  111. map.put(s, searchMap.get(s));
  112. }
  113. return map;
  114. }
  115. /**
  116. * Hides nodes from the tree that do not match <code>text</code>.
  117. *
  118. * @param text
  119. * search text
  120. */
  121. public void filterByText(String text)
  122. {
  123. text = normalize(text);
  124. if (text == null || text.length() == 0)
  125. {
  126. visibleNodes = null;
  127. }
  128. else
  129. {
  130. visibleNodes = new HashSet<Object>();
  131. String[] keywords = text.split(" ");
  132. for (int i = 0; i < keywords.length; i++)
  133. {
  134. SortedMap<String, List<Object>> nodeListByKey = getMatches(text);
  135. if (i == 0)
  136. {
  137. for (List<Object> nodes : nodeListByKey.values())
  138. {
  139. visibleNodes.addAll(nodes);
  140. }
  141. }
  142. else
  143. {
  144. Set<Object> allNew = new HashSet<Object>();
  145. for (List<Object> nodes : nodeListByKey.values())
  146. {
  147. allNew.addAll(nodes);
  148. }
  149. visibleNodes.retainAll(allNew);
  150. }
  151. }
  152. ensureParentsVisible();
  153. }
  154. TreeModelEvent event = new TreeModelEvent(this, new Object[] { model.getRoot() });
  155. for (TreeModelListener listener : listeners)
  156. {
  157. listener.treeStructureChanged(event);
  158. }
  159. }
  160. public Object getChild(Object parent, int index)
  161. {
  162. if (visibleNodes == null)
  163. {
  164. return model.getChild(parent, index);
  165. }
  166. int visibleIndex = 0;
  167. for (int i = 0, count = model.getChildCount(parent); i < count; i++)
  168. {
  169. Object node = model.getChild(parent, i);
  170. if (visibleNodes.contains(node) && index == visibleIndex++)
  171. {
  172. return node;
  173. }
  174. }
  175. throw new ArrayIndexOutOfBoundsException();
  176. }
  177. public int getChildCount(Object parent)
  178. {
  179. if (visibleNodes == null)
  180. {
  181. return model.getChildCount(parent);
  182. }
  183. int visibleCount = 0;
  184. for (int i = 0, count = model.getChildCount(parent); i < count; i++)
  185. {
  186. if (visibleNodes.contains(model.getChild(parent, i)))
  187. {
  188. visibleCount++;
  189. }
  190. }
  191. return visibleCount;
  192. }
  193. public int getIndexOfChild(Object parent, Object child)
  194. {
  195. if (visibleNodes == null)
  196. {
  197. return model.getIndexOfChild(parent, child);
  198. }
  199. int visibleIndex = 0;
  200. for (int i = 0, count = model.getChildCount(parent); i < count; i++)
  201. {
  202. Object node = model.getChild(parent, i);
  203. if (visibleNodes.contains(node))
  204. {
  205. if (node == child)
  206. {
  207. return visibleIndex;
  208. }
  209. visibleIndex++;
  210. }
  211. }
  212. return -1;
  213. }
  214. /**
  215. * Returns the underlying data model.
  216. */
  217. public TreeModel getModel()
  218. {
  219. return model;
  220. }
  221. public Object getRoot()
  222. {
  223. return model.getRoot();
  224. }
  225. public boolean isLeaf(Object node)
  226. {
  227. return model.isLeaf(node);
  228. }
  229. public boolean isVisible(Object node)
  230. {
  231. return visibleNodes == null || visibleNodes.contains(node);
  232. }
  233. private String normalize(String text)
  234. {
  235. if (text != null)
  236. {
  237. if (ignoreCase)
  238. {
  239. text = text.toLowerCase();
  240. }
  241. }
  242. return text;
  243. }
  244. public void reload()
  245. {
  246. TreeModelEvent event = new TreeModelEvent(this, new Object[] { model.getRoot() });
  247. for (TreeModelListener listener : listeners)
  248. {
  249. listener.treeStructureChanged(event);
  250. }
  251. }
  252. public void removeSearchKey(Object node, String key)
  253. {
  254. key = normalize(key);
  255. List<Object> value = searchMap.get(key);
  256. if (value != null)
  257. {
  258. value.remove(node);
  259. if (value.isEmpty())
  260. {
  261. searchMap.remove(key);
  262. }
  263. }
  264. }
  265. public void removeTreeModelListener(TreeModelListener l)
  266. {
  267. listeners.remove(l);
  268. }
  269. /**
  270. * Sets the underlying data model.
  271. *
  272. * @param model
  273. * data model
  274. */
  275. public void setModel(DefaultTreeModel model)
  276. {
  277. setModel(model, new TreeNodeParentProvider());
  278. }
  279. /**
  280. * Sets the underlying data model.
  281. *
  282. * @param model
  283. * data model
  284. * @param parentProvider
  285. * used to retrieve parents of nodes
  286. */
  287. public void setModel(TreeModel model, ParentProvider parentProvider)
  288. {
  289. if (model == null || parentProvider == null)
  290. {
  291. throw new IllegalArgumentException();
  292. }
  293. if (this.model != null)
  294. {
  295. this.model.removeTreeModelListener(listener);
  296. }
  297. this.model = model;
  298. this.parentProvider = parentProvider;
  299. this.model.addTreeModelListener(listener);
  300. searchMap.clear();
  301. reset();
  302. }
  303. /**
  304. * Sets all nodes visible.
  305. */
  306. public void reset()
  307. {
  308. this.visibleNodes = null;
  309. reload();
  310. }
  311. /**
  312. * Sets all parents of the visible nodes visible.
  313. */
  314. private void ensureParentsVisible()
  315. {
  316. Set<Object> parentNodes = new HashSet<Object>();
  317. for (Object node : visibleNodes)
  318. {
  319. Object parentNode = parentProvider.getParent(node);
  320. while (parentNode != null)
  321. {
  322. parentNodes.add(parentNode);
  323. parentNode = parentProvider.getParent(parentNode);
  324. }
  325. }
  326. visibleNodes.addAll(parentNodes);
  327. }
  328. public void valueForPathChanged(TreePath path, Object newValue)
  329. {
  330. model.valueForPathChanged(path, newValue);
  331. }
  332. /**
  333. * Forwards events from the underlying data model to listeners.
  334. */
  335. private class FilteredTreeModelListener implements TreeModelListener
  336. {
  337. public TreeModelEvent refactorEvent(TreeModelEvent event)
  338. {
  339. if (visibleNodes != null)
  340. {
  341. List<Object> children = new ArrayList<Object>(
  342. event.getChildren().length);
  343. List<Integer> indicieList = new ArrayList<Integer>(event
  344. .getChildIndices().length);
  345. for (Object node : event.getChildren())
  346. {
  347. visibleNodes.add(node);
  348. }
  349. Object parent = event.getTreePath().getLastPathComponent();
  350. for (Object node : event.getChildren())
  351. {
  352. children.add(node);
  353. indicieList.add(getIndexOfChild(parent, node));
  354. }
  355. int[] indicies = new int[indicieList.size()];
  356. for (int i = 0; i < indicies.length; i++)
  357. {
  358. indicies[i] = indicieList.get(i);
  359. }
  360. event = new TreeModelEvent(event.getSource(), event.getTreePath(),
  361. indicies, children.toArray(new Object[0]));
  362. }
  363. return event;
  364. }
  365. public void treeNodesChanged(TreeModelEvent event)
  366. {
  367. if (!isVisible(event.getTreePath().getLastPathComponent()))
  368. {
  369. return;
  370. }
  371. event = refactorEvent(event);
  372. for (TreeModelListener listener : listeners)
  373. {
  374. listener.treeNodesChanged(event);
  375. }
  376. }
  377. public void treeNodesInserted(TreeModelEvent event)
  378. {
  379. if (!isVisible(event.getTreePath().getLastPathComponent()))
  380. {
  381. return;
  382. }
  383. event = refactorEvent(event);
  384. for (TreeModelListener listener : listeners)
  385. {
  386. listener.treeNodesInserted(event);
  387. }
  388. }
  389. public void treeNodesRemoved(TreeModelEvent event)
  390. {
  391. if (!isVisible(event.getTreePath().getLastPathComponent()))
  392. {
  393. return;
  394. }
  395. for (TreeModelListener listener : listeners)
  396. {
  397. listener.treeStructureChanged(event);
  398. }
  399. }
  400. public void treeStructureChanged(TreeModelEvent event)
  401. {
  402. if (!isVisible(event.getTreePath().getLastPathComponent()))
  403. {
  404. return;
  405. }
  406. for (TreeModelListener listener : listeners)
  407. {
  408. listener.treeStructureChanged(event);
  409. }
  410. }
  411. }
  412. /**
  413. * Implements <code>TreeNodeParentProvider</code> for tree models that
  414. * use {@link TreeNode} objects such as {@link DefaultTreeModel}.
  415. */
  416. public static class TreeNodeParentProvider implements ParentProvider
  417. {
  418. public Object getParent(Object node)
  419. {
  420. return ((TreeNode) node).getParent();
  421. }
  422. }
  423. /** Interface to retrieve parent nodes. */
  424. public interface ParentProvider
  425. {
  426. /**
  427. * Returns the parent of <code>node</code>.
  428. *
  429. * @return null, if <code>node</code> does not have a parent;
  430. * the parent, otherwise
  431. */
  432. Object getParent(Object node);
  433. }
  434. }