You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Tree.java 29KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.ui;
  5. import java.io.Serializable;
  6. import java.lang.reflect.Method;
  7. import java.util.ArrayList;
  8. import java.util.Collection;
  9. import java.util.HashMap;
  10. import java.util.HashSet;
  11. import java.util.Iterator;
  12. import java.util.LinkedHashSet;
  13. import java.util.LinkedList;
  14. import java.util.Map;
  15. import java.util.Set;
  16. import java.util.Stack;
  17. import java.util.StringTokenizer;
  18. import com.vaadin.data.Container;
  19. import com.vaadin.data.Item;
  20. import com.vaadin.data.util.ContainerHierarchicalWrapper;
  21. import com.vaadin.data.util.IndexedContainer;
  22. import com.vaadin.event.Action;
  23. import com.vaadin.event.ItemClickEvent;
  24. import com.vaadin.event.ItemClickEvent.ItemClickListener;
  25. import com.vaadin.event.ItemClickEvent.ItemClickSource;
  26. import com.vaadin.terminal.KeyMapper;
  27. import com.vaadin.terminal.PaintException;
  28. import com.vaadin.terminal.PaintTarget;
  29. import com.vaadin.terminal.Resource;
  30. import com.vaadin.terminal.gwt.client.MouseEventDetails;
  31. import com.vaadin.terminal.gwt.client.ui.VTree;
  32. /**
  33. * Tree component. A Tree can be used to select an item (or multiple items) from
  34. * a hierarchical set of items.
  35. *
  36. * @author IT Mill Ltd.
  37. * @version
  38. * @VERSION@
  39. * @since 3.0
  40. */
  41. @SuppressWarnings("serial")
  42. @ClientWidget(VTree.class)
  43. public class Tree extends AbstractSelect implements Container.Hierarchical,
  44. Action.Container, ItemClickSource {
  45. private static final Method EXPAND_METHOD;
  46. private static final Method COLLAPSE_METHOD;
  47. static {
  48. try {
  49. EXPAND_METHOD = ExpandListener.class.getDeclaredMethod(
  50. "nodeExpand", new Class[] { ExpandEvent.class });
  51. COLLAPSE_METHOD = CollapseListener.class.getDeclaredMethod(
  52. "nodeCollapse", new Class[] { CollapseEvent.class });
  53. } catch (final java.lang.NoSuchMethodException e) {
  54. // This should never happen
  55. throw new java.lang.RuntimeException(
  56. "Internal error finding methods in Tree");
  57. }
  58. }
  59. /* Private members */
  60. /**
  61. * Set of expanded nodes.
  62. */
  63. private final HashSet expanded = new HashSet();
  64. /**
  65. * List of action handlers.
  66. */
  67. private LinkedList<Action.Handler> actionHandlers = null;
  68. /**
  69. * Action mapper.
  70. */
  71. private KeyMapper actionMapper = null;
  72. /**
  73. * Is the tree selectable .
  74. */
  75. private boolean selectable = true;
  76. /**
  77. * Flag to indicate sub-tree loading
  78. */
  79. private boolean partialUpdate = false;
  80. /**
  81. * Holds a itemId which was recently expanded
  82. */
  83. private Object expandedItemId;
  84. /**
  85. * a flag which indicates initial paint. After this flag set true partial
  86. * updates are allowed.
  87. */
  88. private boolean initialPaint = true;
  89. /* Tree constructors */
  90. /**
  91. * Creates a new empty tree.
  92. */
  93. public Tree() {
  94. }
  95. /**
  96. * Creates a new empty tree with caption.
  97. *
  98. * @param caption
  99. */
  100. public Tree(String caption) {
  101. setCaption(caption);
  102. }
  103. /**
  104. * Creates a new tree with caption and connect it to a Container.
  105. *
  106. * @param caption
  107. * @param dataSource
  108. */
  109. public Tree(String caption, Container dataSource) {
  110. setCaption(caption);
  111. setContainerDataSource(dataSource);
  112. }
  113. /* Expanding and collapsing */
  114. /**
  115. * Check is an item is expanded
  116. *
  117. * @param itemId
  118. * the item id.
  119. * @return true iff the item is expanded.
  120. */
  121. public boolean isExpanded(Object itemId) {
  122. return expanded.contains(itemId);
  123. }
  124. /**
  125. * Expands an item.
  126. *
  127. * @param itemId
  128. * the item id.
  129. * @return True iff the expand operation succeeded
  130. */
  131. public boolean expandItem(Object itemId) {
  132. boolean success = expandItem(itemId, true);
  133. requestRepaint();
  134. return success;
  135. }
  136. /**
  137. * Expands an item.
  138. *
  139. * @param itemId
  140. * the item id.
  141. * @param sendChildTree
  142. * flag to indicate if client needs subtree or not (may be
  143. * cached)
  144. * @return True iff the expand operation succeeded
  145. */
  146. private boolean expandItem(Object itemId, boolean sendChildTree) {
  147. // Succeeds if the node is already expanded
  148. if (isExpanded(itemId)) {
  149. return true;
  150. }
  151. // Nodes that can not have children are not expandable
  152. if (!areChildrenAllowed(itemId)) {
  153. return false;
  154. }
  155. // Expands
  156. expanded.add(itemId);
  157. expandedItemId = itemId;
  158. if (initialPaint) {
  159. requestRepaint();
  160. } else if (sendChildTree) {
  161. requestPartialRepaint();
  162. }
  163. fireExpandEvent(itemId);
  164. return true;
  165. }
  166. @Override
  167. public void requestRepaint() {
  168. super.requestRepaint();
  169. partialUpdate = false;
  170. }
  171. private void requestPartialRepaint() {
  172. super.requestRepaint();
  173. partialUpdate = true;
  174. }
  175. /**
  176. * Expands the items recursively
  177. *
  178. * Expands all the children recursively starting from an item. Operation
  179. * succeeds only if all expandable items are expanded.
  180. *
  181. * @param startItemId
  182. * @return True iff the expand operation succeeded
  183. */
  184. public boolean expandItemsRecursively(Object startItemId) {
  185. boolean result = true;
  186. // Initial stack
  187. final Stack todo = new Stack();
  188. todo.add(startItemId);
  189. // Expands recursively
  190. while (!todo.isEmpty()) {
  191. final Object id = todo.pop();
  192. if (areChildrenAllowed(id) && !expandItem(id, false)) {
  193. result = false;
  194. }
  195. if (hasChildren(id)) {
  196. todo.addAll(getChildren(id));
  197. }
  198. }
  199. requestRepaint();
  200. return result;
  201. }
  202. /**
  203. * Collapses an item.
  204. *
  205. * @param itemId
  206. * the item id.
  207. * @return True iff the collapse operation succeeded
  208. */
  209. public boolean collapseItem(Object itemId) {
  210. // Succeeds if the node is already collapsed
  211. if (!isExpanded(itemId)) {
  212. return true;
  213. }
  214. // Collapse
  215. expanded.remove(itemId);
  216. requestRepaint();
  217. fireCollapseEvent(itemId);
  218. return true;
  219. }
  220. /**
  221. * Collapses the items recursively.
  222. *
  223. * Collapse all the children recursively starting from an item. Operation
  224. * succeeds only if all expandable items are collapsed.
  225. *
  226. * @param startItemId
  227. * @return True iff the collapse operation succeeded
  228. */
  229. public boolean collapseItemsRecursively(Object startItemId) {
  230. boolean result = true;
  231. // Initial stack
  232. final Stack todo = new Stack();
  233. todo.add(startItemId);
  234. // Collapse recursively
  235. while (!todo.isEmpty()) {
  236. final Object id = todo.pop();
  237. if (areChildrenAllowed(id) && !collapseItem(id)) {
  238. result = false;
  239. }
  240. if (hasChildren(id)) {
  241. todo.addAll(getChildren(id));
  242. }
  243. }
  244. return result;
  245. }
  246. /**
  247. * Getter for property selectable.
  248. *
  249. * <p>
  250. * The tree is selectable by default.
  251. * </p>
  252. *
  253. * @return the Value of property selectable.
  254. */
  255. public boolean isSelectable() {
  256. return selectable;
  257. }
  258. /**
  259. * Setter for property selectable.
  260. *
  261. * <p>
  262. * The tree is selectable by default.
  263. * </p>
  264. *
  265. * @param selectable
  266. * the New value of property selectable.
  267. */
  268. public void setSelectable(boolean selectable) {
  269. if (this.selectable != selectable) {
  270. this.selectable = selectable;
  271. requestRepaint();
  272. }
  273. }
  274. /* Component API */
  275. /*
  276. * (non-Javadoc)
  277. *
  278. * @see com.vaadin.ui.AbstractSelect#changeVariables(java.lang.Object,
  279. * java.util.Map)
  280. */
  281. @Override
  282. public void changeVariables(Object source, Map variables) {
  283. if (clickListenerCount > 0 && variables.containsKey("clickedKey")) {
  284. String key = (String) variables.get("clickedKey");
  285. Object id = itemIdMapper.get(key);
  286. MouseEventDetails details = MouseEventDetails
  287. .deserialize((String) variables.get("clickEvent"));
  288. Item item = getItem(id);
  289. if (item != null) {
  290. fireEvent(new ItemClickEvent(this, item, id, null, details));
  291. }
  292. }
  293. if (!isSelectable() && variables.containsKey("selected")) {
  294. // Not-selectable is a special case, AbstractSelect does not support
  295. // TODO could be optimized.
  296. variables = new HashMap(variables);
  297. variables.remove("selected");
  298. }
  299. // Collapses the nodes
  300. if (variables.containsKey("collapse")) {
  301. final String[] keys = (String[]) variables.get("collapse");
  302. for (int i = 0; i < keys.length; i++) {
  303. final Object id = itemIdMapper.get(keys[i]);
  304. if (id != null && isExpanded(id)) {
  305. expanded.remove(id);
  306. fireCollapseEvent(id);
  307. }
  308. }
  309. }
  310. // Expands the nodes
  311. if (variables.containsKey("expand")) {
  312. boolean sendChildTree = false;
  313. if (variables.containsKey("requestChildTree")) {
  314. sendChildTree = true;
  315. }
  316. final String[] keys = (String[]) variables.get("expand");
  317. for (int i = 0; i < keys.length; i++) {
  318. final Object id = itemIdMapper.get(keys[i]);
  319. if (id != null) {
  320. expandItem(id, sendChildTree);
  321. }
  322. }
  323. }
  324. // Selections are handled by the select component
  325. super.changeVariables(source, variables);
  326. // Actions
  327. if (variables.containsKey("action")) {
  328. final StringTokenizer st = new StringTokenizer((String) variables
  329. .get("action"), ",");
  330. if (st.countTokens() == 2) {
  331. final Object itemId = itemIdMapper.get(st.nextToken());
  332. final Action action = (Action) actionMapper.get(st.nextToken());
  333. if (action != null && containsId(itemId)
  334. && actionHandlers != null) {
  335. for (final Iterator<Action.Handler> i = actionHandlers
  336. .iterator(); i.hasNext();) {
  337. i.next().handleAction(action, this, itemId);
  338. }
  339. }
  340. }
  341. }
  342. }
  343. /**
  344. * Paints any needed component-specific things to the given UIDL stream.
  345. *
  346. * @see com.vaadin.ui.AbstractComponent#paintContent(PaintTarget)
  347. */
  348. @Override
  349. public void paintContent(PaintTarget target) throws PaintException {
  350. initialPaint = false;
  351. if (partialUpdate) {
  352. target.addAttribute("partialUpdate", true);
  353. target.addAttribute("rootKey", itemIdMapper.key(expandedItemId));
  354. } else {
  355. getCaptionChangeListener().clear();
  356. // The tab ordering number
  357. if (getTabIndex() > 0) {
  358. target.addAttribute("tabindex", getTabIndex());
  359. }
  360. // Paint tree attributes
  361. if (isSelectable()) {
  362. target.addAttribute("selectmode", (isMultiSelect() ? "multi"
  363. : "single"));
  364. } else {
  365. target.addAttribute("selectmode", "none");
  366. }
  367. if (isNewItemsAllowed()) {
  368. target.addAttribute("allownewitem", true);
  369. }
  370. if (isNullSelectionAllowed()) {
  371. target.addAttribute("nullselect", true);
  372. }
  373. if (clickListenerCount > 0) {
  374. target.addAttribute("listenClicks", true);
  375. }
  376. }
  377. // Initialize variables
  378. final Set<Action> actionSet = new LinkedHashSet<Action>();
  379. // rendered selectedKeys
  380. LinkedList<String> selectedKeys = new LinkedList<String>();
  381. final LinkedList<String> expandedKeys = new LinkedList<String>();
  382. // Iterates through hierarchical tree using a stack of iterators
  383. final Stack<Iterator> iteratorStack = new Stack<Iterator>();
  384. Collection ids;
  385. if (partialUpdate) {
  386. ids = getChildren(expandedItemId);
  387. } else {
  388. ids = rootItemIds();
  389. }
  390. if (ids != null) {
  391. iteratorStack.push(ids.iterator());
  392. }
  393. while (!iteratorStack.isEmpty()) {
  394. // Gets the iterator for current tree level
  395. final Iterator i = iteratorStack.peek();
  396. // If the level is finished, back to previous tree level
  397. if (!i.hasNext()) {
  398. // Removes used iterator from the stack
  399. iteratorStack.pop();
  400. // Closes node
  401. if (!iteratorStack.isEmpty()) {
  402. target.endTag("node");
  403. }
  404. }
  405. // Adds the item on current level
  406. else {
  407. final Object itemId = i.next();
  408. // Starts the item / node
  409. final boolean isNode = areChildrenAllowed(itemId);
  410. if (isNode) {
  411. target.startTag("node");
  412. } else {
  413. target.startTag("leaf");
  414. }
  415. // Adds the attributes
  416. target.addAttribute("caption", getItemCaption(itemId));
  417. final Resource icon = getItemIcon(itemId);
  418. if (icon != null) {
  419. target.addAttribute("icon", getItemIcon(itemId));
  420. }
  421. final String key = itemIdMapper.key(itemId);
  422. target.addAttribute("key", key);
  423. if (isSelected(itemId)) {
  424. target.addAttribute("selected", true);
  425. selectedKeys.add(key);
  426. }
  427. if (areChildrenAllowed(itemId) && isExpanded(itemId)) {
  428. target.addAttribute("expanded", true);
  429. expandedKeys.add(key);
  430. }
  431. // Add caption change listener
  432. getCaptionChangeListener().addNotifierForItem(itemId);
  433. // Actions
  434. if (actionHandlers != null) {
  435. final ArrayList<String> keys = new ArrayList<String>();
  436. final Iterator<Action.Handler> ahi = actionHandlers
  437. .iterator();
  438. while (ahi.hasNext()) {
  439. final Action[] aa = ahi.next().getActions(itemId, this);
  440. if (aa != null) {
  441. for (int ai = 0; ai < aa.length; ai++) {
  442. final String akey = actionMapper.key(aa[ai]);
  443. actionSet.add(aa[ai]);
  444. keys.add(akey);
  445. }
  446. }
  447. }
  448. target.addAttribute("al", keys.toArray());
  449. }
  450. // Adds the children if expanded, or close the tag
  451. if (isExpanded(itemId) && hasChildren(itemId)
  452. && areChildrenAllowed(itemId)) {
  453. iteratorStack.push(getChildren(itemId).iterator());
  454. } else {
  455. if (isNode) {
  456. target.endTag("node");
  457. } else {
  458. target.endTag("leaf");
  459. }
  460. }
  461. }
  462. }
  463. // Actions
  464. if (!actionSet.isEmpty()) {
  465. target.addVariable(this, "action", "");
  466. target.startTag("actions");
  467. final Iterator<Action> i = actionSet.iterator();
  468. while (i.hasNext()) {
  469. final Action a = i.next();
  470. target.startTag("action");
  471. if (a.getCaption() != null) {
  472. target.addAttribute("caption", a.getCaption());
  473. }
  474. if (a.getIcon() != null) {
  475. target.addAttribute("icon", a.getIcon());
  476. }
  477. target.addAttribute("key", actionMapper.key(a));
  478. target.endTag("action");
  479. }
  480. target.endTag("actions");
  481. }
  482. if (partialUpdate) {
  483. partialUpdate = false;
  484. } else {
  485. // Selected
  486. target.addVariable(this, "selected", selectedKeys
  487. .toArray(new String[selectedKeys.size()]));
  488. // Expand and collapse
  489. target.addVariable(this, "expand", new String[] {});
  490. target.addVariable(this, "collapse", new String[] {});
  491. // New items
  492. target.addVariable(this, "newitem", new String[] {});
  493. }
  494. }
  495. /* Container.Hierarchical API */
  496. /**
  497. * Tests if the Item with given ID can have any children.
  498. *
  499. * @see com.vaadin.data.Container.Hierarchical#areChildrenAllowed(Object)
  500. */
  501. public boolean areChildrenAllowed(Object itemId) {
  502. return ((Container.Hierarchical) items).areChildrenAllowed(itemId);
  503. }
  504. /**
  505. * Gets the IDs of all Items that are children of the specified Item.
  506. *
  507. * @see com.vaadin.data.Container.Hierarchical#getChildren(Object)
  508. */
  509. public Collection getChildren(Object itemId) {
  510. return ((Container.Hierarchical) items).getChildren(itemId);
  511. }
  512. /**
  513. * Gets the ID of the parent Item of the specified Item.
  514. *
  515. * @see com.vaadin.data.Container.Hierarchical#getParent(Object)
  516. */
  517. public Object getParent(Object itemId) {
  518. return ((Container.Hierarchical) items).getParent(itemId);
  519. }
  520. /**
  521. * Tests if the Item specified with <code>itemId</code> has child Items.
  522. *
  523. * @see com.vaadin.data.Container.Hierarchical#hasChildren(Object)
  524. */
  525. public boolean hasChildren(Object itemId) {
  526. return ((Container.Hierarchical) items).hasChildren(itemId);
  527. }
  528. /**
  529. * Tests if the Item specified with <code>itemId</code> is a root Item.
  530. *
  531. * @see com.vaadin.data.Container.Hierarchical#isRoot(Object)
  532. */
  533. public boolean isRoot(Object itemId) {
  534. return ((Container.Hierarchical) items).isRoot(itemId);
  535. }
  536. /**
  537. * Gets the IDs of all Items in the container that don't have a parent.
  538. *
  539. * @see com.vaadin.data.Container.Hierarchical#rootItemIds()
  540. */
  541. public Collection rootItemIds() {
  542. return ((Container.Hierarchical) items).rootItemIds();
  543. }
  544. /**
  545. * Sets the given Item's capability to have children.
  546. *
  547. * @see com.vaadin.data.Container.Hierarchical#setChildrenAllowed(Object,
  548. * boolean)
  549. */
  550. public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed) {
  551. final boolean success = ((Container.Hierarchical) items)
  552. .setChildrenAllowed(itemId, areChildrenAllowed);
  553. if (success) {
  554. fireValueChange(false);
  555. }
  556. return success;
  557. }
  558. /*
  559. * (non-Javadoc)
  560. *
  561. * @see com.vaadin.data.Container.Hierarchical#setParent(java.lang.Object ,
  562. * java.lang.Object)
  563. */
  564. public boolean setParent(Object itemId, Object newParentId) {
  565. final boolean success = ((Container.Hierarchical) items).setParent(
  566. itemId, newParentId);
  567. if (success) {
  568. requestRepaint();
  569. }
  570. return success;
  571. }
  572. /* Overriding select behavior */
  573. /**
  574. * Sets the Container that serves as the data source of the viewer.
  575. *
  576. * @see com.vaadin.data.Container.Viewer#setContainerDataSource(Container)
  577. */
  578. @Override
  579. public void setContainerDataSource(Container newDataSource) {
  580. if (newDataSource == null) {
  581. // Note: using wrapped IndexedContainer to match constructor (super
  582. // creates an IndexedContainer, which is then wrapped).
  583. newDataSource = new ContainerHierarchicalWrapper(
  584. new IndexedContainer());
  585. }
  586. // Assure that the data source is ordered by making unordered
  587. // containers ordered by wrapping them
  588. if (Container.Hierarchical.class.isAssignableFrom(newDataSource
  589. .getClass())) {
  590. super.setContainerDataSource(newDataSource);
  591. } else {
  592. super.setContainerDataSource(new ContainerHierarchicalWrapper(
  593. newDataSource));
  594. }
  595. }
  596. /* Expand event and listener */
  597. /**
  598. * Event to fired when a node is expanded. ExapandEvent is fired when a node
  599. * is to be expanded. it can me used to dynamically fill the sub-nodes of
  600. * the node.
  601. *
  602. * @author IT Mill Ltd.
  603. * @version
  604. * @VERSION@
  605. * @since 3.0
  606. */
  607. public class ExpandEvent extends Component.Event {
  608. private final Object expandedItemId;
  609. /**
  610. * New instance of options change event
  611. *
  612. * @param source
  613. * the Source of the event.
  614. * @param expandedItemId
  615. */
  616. public ExpandEvent(Component source, Object expandedItemId) {
  617. super(source);
  618. this.expandedItemId = expandedItemId;
  619. }
  620. /**
  621. * Node where the event occurred.
  622. *
  623. * @return the Source of the event.
  624. */
  625. public Object getItemId() {
  626. return expandedItemId;
  627. }
  628. }
  629. /**
  630. * Expand event listener.
  631. *
  632. * @author IT Mill Ltd.
  633. * @version
  634. * @VERSION@
  635. * @since 3.0
  636. */
  637. public interface ExpandListener extends Serializable {
  638. /**
  639. * A node has been expanded.
  640. *
  641. * @param event
  642. * the Expand event.
  643. */
  644. public void nodeExpand(ExpandEvent event);
  645. }
  646. /**
  647. * Adds the expand listener.
  648. *
  649. * @param listener
  650. * the Listener to be added.
  651. */
  652. public void addListener(ExpandListener listener) {
  653. addListener(ExpandEvent.class, listener, EXPAND_METHOD);
  654. }
  655. /**
  656. * Removes the expand listener.
  657. *
  658. * @param listener
  659. * the Listener to be removed.
  660. */
  661. public void removeListener(ExpandListener listener) {
  662. removeListener(ExpandEvent.class, listener, EXPAND_METHOD);
  663. }
  664. /**
  665. * Emits the expand event.
  666. *
  667. * @param itemId
  668. * the item id.
  669. */
  670. protected void fireExpandEvent(Object itemId) {
  671. fireEvent(new ExpandEvent(this, itemId));
  672. }
  673. /* Collapse event */
  674. /**
  675. * Collapse event
  676. *
  677. * @author IT Mill Ltd.
  678. * @version
  679. * @VERSION@
  680. * @since 3.0
  681. */
  682. public class CollapseEvent extends Component.Event {
  683. private final Object collapsedItemId;
  684. /**
  685. * New instance of options change event.
  686. *
  687. * @param source
  688. * the Source of the event.
  689. * @param collapsedItemId
  690. */
  691. public CollapseEvent(Component source, Object collapsedItemId) {
  692. super(source);
  693. this.collapsedItemId = collapsedItemId;
  694. }
  695. /**
  696. * Gets tge Collapsed Item id.
  697. *
  698. * @return the collapsed item id.
  699. */
  700. public Object getItemId() {
  701. return collapsedItemId;
  702. }
  703. }
  704. /**
  705. * Collapse event listener.
  706. *
  707. * @author IT Mill Ltd.
  708. * @version
  709. * @VERSION@
  710. * @since 3.0
  711. */
  712. public interface CollapseListener extends Serializable {
  713. /**
  714. * A node has been collapsed.
  715. *
  716. * @param event
  717. * the Collapse event.
  718. */
  719. public void nodeCollapse(CollapseEvent event);
  720. }
  721. /**
  722. * Adds the collapse listener.
  723. *
  724. * @param listener
  725. * the Listener to be added.
  726. */
  727. public void addListener(CollapseListener listener) {
  728. addListener(CollapseEvent.class, listener, COLLAPSE_METHOD);
  729. }
  730. /**
  731. * Removes the collapse listener.
  732. *
  733. * @param listener
  734. * the Listener to be removed.
  735. */
  736. public void removeListener(CollapseListener listener) {
  737. removeListener(CollapseEvent.class, listener, COLLAPSE_METHOD);
  738. }
  739. /**
  740. * Emits collapse event.
  741. *
  742. * @param itemId
  743. * the item id.
  744. */
  745. protected void fireCollapseEvent(Object itemId) {
  746. fireEvent(new CollapseEvent(this, itemId));
  747. }
  748. /* Action container */
  749. /**
  750. * Adds an action handler.
  751. *
  752. * @see com.vaadin.event.Action.Container#addActionHandler(Action.Handler)
  753. */
  754. public void addActionHandler(Action.Handler actionHandler) {
  755. if (actionHandler != null) {
  756. if (actionHandlers == null) {
  757. actionHandlers = new LinkedList<Action.Handler>();
  758. actionMapper = new KeyMapper();
  759. }
  760. if (!actionHandlers.contains(actionHandler)) {
  761. actionHandlers.add(actionHandler);
  762. requestRepaint();
  763. }
  764. }
  765. }
  766. /**
  767. * Removes an action handler.
  768. *
  769. * @see com.vaadin.event.Action.Container#removeActionHandler(Action.Handler)
  770. */
  771. public void removeActionHandler(Action.Handler actionHandler) {
  772. if (actionHandlers != null && actionHandlers.contains(actionHandler)) {
  773. actionHandlers.remove(actionHandler);
  774. if (actionHandlers.isEmpty()) {
  775. actionHandlers = null;
  776. actionMapper = null;
  777. }
  778. requestRepaint();
  779. }
  780. }
  781. /**
  782. * Gets the visible item ids.
  783. *
  784. * @see com.vaadin.ui.Select#getVisibleItemIds()
  785. */
  786. @Override
  787. public Collection getVisibleItemIds() {
  788. final LinkedList visible = new LinkedList();
  789. // Iterates trough hierarchical tree using a stack of iterators
  790. final Stack<Iterator> iteratorStack = new Stack<Iterator>();
  791. final Collection ids = rootItemIds();
  792. if (ids != null) {
  793. iteratorStack.push(ids.iterator());
  794. }
  795. while (!iteratorStack.isEmpty()) {
  796. // Gets the iterator for current tree level
  797. final Iterator i = iteratorStack.peek();
  798. // If the level is finished, back to previous tree level
  799. if (!i.hasNext()) {
  800. // Removes used iterator from the stack
  801. iteratorStack.pop();
  802. }
  803. // Adds the item on current level
  804. else {
  805. final Object itemId = i.next();
  806. visible.add(itemId);
  807. // Adds children if expanded, or close the tag
  808. if (isExpanded(itemId) && hasChildren(itemId)) {
  809. iteratorStack.push(getChildren(itemId).iterator());
  810. }
  811. }
  812. }
  813. return visible;
  814. }
  815. /**
  816. * Tree does not support <code>setNullSelectionItemId</code>.
  817. *
  818. * @see com.vaadin.ui.AbstractSelect#setNullSelectionItemId(java.lang.Object)
  819. */
  820. @Override
  821. public void setNullSelectionItemId(Object nullSelectionItemId)
  822. throws UnsupportedOperationException {
  823. if (nullSelectionItemId != null) {
  824. throw new UnsupportedOperationException();
  825. }
  826. }
  827. /**
  828. * Adding new items is not supported.
  829. *
  830. * @throws UnsupportedOperationException
  831. * if set to true.
  832. * @see com.vaadin.ui.Select#setNewItemsAllowed(boolean)
  833. */
  834. @Override
  835. public void setNewItemsAllowed(boolean allowNewOptions)
  836. throws UnsupportedOperationException {
  837. if (allowNewOptions) {
  838. throw new UnsupportedOperationException();
  839. }
  840. }
  841. /**
  842. * Focusing to this component is not supported.
  843. *
  844. * @throws UnsupportedOperationException
  845. * if invoked.
  846. * @see com.vaadin.ui.AbstractField#focus()
  847. */
  848. @Override
  849. public void focus() throws UnsupportedOperationException {
  850. throw new UnsupportedOperationException();
  851. }
  852. /**
  853. * Tree does not support lazy options loading mode. Setting this true will
  854. * throw UnsupportedOperationException.
  855. *
  856. * @see com.vaadin.ui.Select#setLazyLoading(boolean)
  857. */
  858. public void setLazyLoading(boolean useLazyLoading) {
  859. if (useLazyLoading) {
  860. throw new UnsupportedOperationException(
  861. "Lazy options loading is not supported by Tree.");
  862. }
  863. }
  864. private int clickListenerCount = 0;
  865. public void addListener(ItemClickListener listener) {
  866. addListener(ItemClickEvent.class, listener,
  867. ItemClickEvent.ITEM_CLICK_METHOD);
  868. clickListenerCount++;
  869. // repaint needed only if click listening became necessary
  870. if (clickListenerCount == 1) {
  871. requestRepaint();
  872. }
  873. }
  874. public void removeListener(ItemClickListener listener) {
  875. removeListener(ItemClickEvent.class, listener,
  876. ItemClickEvent.ITEM_CLICK_METHOD);
  877. clickListenerCount++;
  878. // repaint needed only if click listening is not needed in client
  879. // anymore
  880. if (clickListenerCount == 0) {
  881. requestRepaint();
  882. }
  883. }
  884. }