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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038
  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. * Gets the UIDL tag corresponding to the component.
  277. *
  278. * @see com.vaadin.ui.AbstractComponent#getTag()
  279. */
  280. @Override
  281. public String getTag() {
  282. return "tree";
  283. }
  284. /**
  285. * Called when one or more variables handled by the implementing class are
  286. * changed.
  287. *
  288. * @see com.vaadin.terminal.VariableOwner#changeVariables(Object source, Map
  289. * variables)
  290. */
  291. @Override
  292. public void changeVariables(Object source, Map variables) {
  293. if (clickListenerCount > 0 && variables.containsKey("clickedKey")) {
  294. String key = (String) variables.get("clickedKey");
  295. Object id = itemIdMapper.get(key);
  296. MouseEventDetails details = MouseEventDetails
  297. .deSerialize((String) variables.get("clickEvent"));
  298. Item item = getItem(id);
  299. if (item != null) {
  300. fireEvent(new ItemClickEvent(this, item, id, null, details));
  301. }
  302. }
  303. if (!isSelectable() && variables.containsKey("selected")) {
  304. // Not-selectable is a special case, AbstractSelect does not support
  305. // TODO could be optimized.
  306. variables = new HashMap(variables);
  307. variables.remove("selected");
  308. }
  309. // Collapses the nodes
  310. if (variables.containsKey("collapse")) {
  311. final String[] keys = (String[]) variables.get("collapse");
  312. for (int i = 0; i < keys.length; i++) {
  313. final Object id = itemIdMapper.get(keys[i]);
  314. if (id != null && isExpanded(id)) {
  315. expanded.remove(id);
  316. fireCollapseEvent(id);
  317. }
  318. }
  319. }
  320. // Expands the nodes
  321. if (variables.containsKey("expand")) {
  322. boolean sendChildTree = false;
  323. if (variables.containsKey("requestChildTree")) {
  324. sendChildTree = true;
  325. }
  326. final String[] keys = (String[]) variables.get("expand");
  327. for (int i = 0; i < keys.length; i++) {
  328. final Object id = itemIdMapper.get(keys[i]);
  329. if (id != null) {
  330. expandItem(id, sendChildTree);
  331. }
  332. }
  333. }
  334. // Selections are handled by the select component
  335. super.changeVariables(source, variables);
  336. // Actions
  337. if (variables.containsKey("action")) {
  338. final StringTokenizer st = new StringTokenizer((String) variables
  339. .get("action"), ",");
  340. if (st.countTokens() == 2) {
  341. final Object itemId = itemIdMapper.get(st.nextToken());
  342. final Action action = (Action) actionMapper.get(st.nextToken());
  343. if (action != null && containsId(itemId)
  344. && actionHandlers != null) {
  345. for (final Iterator<Action.Handler> i = actionHandlers
  346. .iterator(); i.hasNext();) {
  347. i.next().handleAction(action, this, itemId);
  348. }
  349. }
  350. }
  351. }
  352. }
  353. /**
  354. * Paints any needed component-specific things to the given UIDL stream.
  355. *
  356. * @see com.vaadin.ui.AbstractComponent#paintContent(PaintTarget)
  357. */
  358. @Override
  359. public void paintContent(PaintTarget target) throws PaintException {
  360. initialPaint = false;
  361. if (partialUpdate) {
  362. target.addAttribute("partialUpdate", true);
  363. target.addAttribute("rootKey", itemIdMapper.key(expandedItemId));
  364. } else {
  365. getCaptionChangeListener().clear();
  366. // The tab ordering number
  367. if (getTabIndex() > 0) {
  368. target.addAttribute("tabindex", getTabIndex());
  369. }
  370. // Paint tree attributes
  371. if (isSelectable()) {
  372. target.addAttribute("selectmode", (isMultiSelect() ? "multi"
  373. : "single"));
  374. } else {
  375. target.addAttribute("selectmode", "none");
  376. }
  377. if (isNewItemsAllowed()) {
  378. target.addAttribute("allownewitem", true);
  379. }
  380. if (isNullSelectionAllowed()) {
  381. target.addAttribute("nullselect", true);
  382. }
  383. if (clickListenerCount > 0) {
  384. target.addAttribute("listenClicks", true);
  385. }
  386. }
  387. // Initialize variables
  388. final Set<Action> actionSet = new LinkedHashSet<Action>();
  389. // rendered selectedKeys
  390. LinkedList<String> selectedKeys = new LinkedList<String>();
  391. final LinkedList<String> expandedKeys = new LinkedList<String>();
  392. // Iterates through hierarchical tree using a stack of iterators
  393. final Stack<Iterator> iteratorStack = new Stack<Iterator>();
  394. Collection ids;
  395. if (partialUpdate) {
  396. ids = getChildren(expandedItemId);
  397. } else {
  398. ids = rootItemIds();
  399. }
  400. if (ids != null) {
  401. iteratorStack.push(ids.iterator());
  402. }
  403. while (!iteratorStack.isEmpty()) {
  404. // Gets the iterator for current tree level
  405. final Iterator i = iteratorStack.peek();
  406. // If the level is finished, back to previous tree level
  407. if (!i.hasNext()) {
  408. // Removes used iterator from the stack
  409. iteratorStack.pop();
  410. // Closes node
  411. if (!iteratorStack.isEmpty()) {
  412. target.endTag("node");
  413. }
  414. }
  415. // Adds the item on current level
  416. else {
  417. final Object itemId = i.next();
  418. // Starts the item / node
  419. final boolean isNode = areChildrenAllowed(itemId);
  420. if (isNode) {
  421. target.startTag("node");
  422. } else {
  423. target.startTag("leaf");
  424. }
  425. // Adds the attributes
  426. target.addAttribute("caption", getItemCaption(itemId));
  427. final Resource icon = getItemIcon(itemId);
  428. if (icon != null) {
  429. target.addAttribute("icon", getItemIcon(itemId));
  430. }
  431. final String key = itemIdMapper.key(itemId);
  432. target.addAttribute("key", key);
  433. if (isSelected(itemId)) {
  434. target.addAttribute("selected", true);
  435. selectedKeys.add(key);
  436. }
  437. if (areChildrenAllowed(itemId) && isExpanded(itemId)) {
  438. target.addAttribute("expanded", true);
  439. expandedKeys.add(key);
  440. }
  441. // Add caption change listener
  442. getCaptionChangeListener().addNotifierForItem(itemId);
  443. // Actions
  444. if (actionHandlers != null) {
  445. final ArrayList<String> keys = new ArrayList<String>();
  446. final Iterator<Action.Handler> ahi = actionHandlers
  447. .iterator();
  448. while (ahi.hasNext()) {
  449. final Action[] aa = ahi.next().getActions(itemId, this);
  450. if (aa != null) {
  451. for (int ai = 0; ai < aa.length; ai++) {
  452. final String akey = actionMapper.key(aa[ai]);
  453. actionSet.add(aa[ai]);
  454. keys.add(akey);
  455. }
  456. }
  457. }
  458. target.addAttribute("al", keys.toArray());
  459. }
  460. // Adds the children if expanded, or close the tag
  461. if (isExpanded(itemId) && hasChildren(itemId)
  462. && areChildrenAllowed(itemId)) {
  463. iteratorStack.push(getChildren(itemId).iterator());
  464. } else {
  465. if (isNode) {
  466. target.endTag("node");
  467. } else {
  468. target.endTag("leaf");
  469. }
  470. }
  471. }
  472. }
  473. // Actions
  474. if (!actionSet.isEmpty()) {
  475. target.addVariable(this, "action", "");
  476. target.startTag("actions");
  477. final Iterator<Action> i = actionSet.iterator();
  478. while (i.hasNext()) {
  479. final Action a = i.next();
  480. target.startTag("action");
  481. if (a.getCaption() != null) {
  482. target.addAttribute("caption", a.getCaption());
  483. }
  484. if (a.getIcon() != null) {
  485. target.addAttribute("icon", a.getIcon());
  486. }
  487. target.addAttribute("key", actionMapper.key(a));
  488. target.endTag("action");
  489. }
  490. target.endTag("actions");
  491. }
  492. if (partialUpdate) {
  493. partialUpdate = false;
  494. } else {
  495. // Selected
  496. target.addVariable(this, "selected", selectedKeys
  497. .toArray(new String[selectedKeys.size()]));
  498. // Expand and collapse
  499. target.addVariable(this, "expand", new String[] {});
  500. target.addVariable(this, "collapse", new String[] {});
  501. // New items
  502. target.addVariable(this, "newitem", new String[] {});
  503. }
  504. }
  505. /* Container.Hierarchical API */
  506. /**
  507. * Tests if the Item with given ID can have any children.
  508. *
  509. * @see com.vaadin.data.Container.Hierarchical#areChildrenAllowed(Object)
  510. */
  511. public boolean areChildrenAllowed(Object itemId) {
  512. return ((Container.Hierarchical) items).areChildrenAllowed(itemId);
  513. }
  514. /**
  515. * Gets the IDs of all Items that are children of the specified Item.
  516. *
  517. * @see com.vaadin.data.Container.Hierarchical#getChildren(Object)
  518. */
  519. public Collection getChildren(Object itemId) {
  520. return ((Container.Hierarchical) items).getChildren(itemId);
  521. }
  522. /**
  523. * Gets the ID of the parent Item of the specified Item.
  524. *
  525. * @see com.vaadin.data.Container.Hierarchical#getParent(Object)
  526. */
  527. public Object getParent(Object itemId) {
  528. return ((Container.Hierarchical) items).getParent(itemId);
  529. }
  530. /**
  531. * Tests if the Item specified with <code>itemId</code> has child Items.
  532. *
  533. * @see com.vaadin.data.Container.Hierarchical#hasChildren(Object)
  534. */
  535. public boolean hasChildren(Object itemId) {
  536. return ((Container.Hierarchical) items).hasChildren(itemId);
  537. }
  538. /**
  539. * Tests if the Item specified with <code>itemId</code> is a root Item.
  540. *
  541. * @see com.vaadin.data.Container.Hierarchical#isRoot(Object)
  542. */
  543. public boolean isRoot(Object itemId) {
  544. return ((Container.Hierarchical) items).isRoot(itemId);
  545. }
  546. /**
  547. * Gets the IDs of all Items in the container that don't have a parent.
  548. *
  549. * @see com.vaadin.data.Container.Hierarchical#rootItemIds()
  550. */
  551. public Collection rootItemIds() {
  552. return ((Container.Hierarchical) items).rootItemIds();
  553. }
  554. /**
  555. * Sets the given Item's capability to have children.
  556. *
  557. * @see com.vaadin.data.Container.Hierarchical#setChildrenAllowed(Object,
  558. * boolean)
  559. */
  560. public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed) {
  561. final boolean success = ((Container.Hierarchical) items)
  562. .setChildrenAllowed(itemId, areChildrenAllowed);
  563. if (success) {
  564. fireValueChange(false);
  565. }
  566. return success;
  567. }
  568. /*
  569. * (non-Javadoc)
  570. *
  571. * @see com.vaadin.data.Container.Hierarchical#setParent(java.lang.Object ,
  572. * java.lang.Object)
  573. */
  574. public boolean setParent(Object itemId, Object newParentId) {
  575. final boolean success = ((Container.Hierarchical) items).setParent(
  576. itemId, newParentId);
  577. if (success) {
  578. requestRepaint();
  579. }
  580. return success;
  581. }
  582. /* Overriding select behavior */
  583. /**
  584. * Sets the Container that serves as the data source of the viewer.
  585. *
  586. * @see com.vaadin.data.Container.Viewer#setContainerDataSource(Container)
  587. */
  588. @Override
  589. public void setContainerDataSource(Container newDataSource) {
  590. if (newDataSource == null) {
  591. // Note: using wrapped IndexedContainer to match constructor (super
  592. // creates an IndexedContainer, which is then wrapped).
  593. newDataSource = new ContainerHierarchicalWrapper(
  594. new IndexedContainer());
  595. }
  596. // Assure that the data source is ordered by making unordered
  597. // containers ordered by wrapping them
  598. if (Container.Hierarchical.class.isAssignableFrom(newDataSource
  599. .getClass())) {
  600. super.setContainerDataSource(newDataSource);
  601. } else {
  602. super.setContainerDataSource(new ContainerHierarchicalWrapper(
  603. newDataSource));
  604. }
  605. }
  606. /* Expand event and listener */
  607. /**
  608. * Event to fired when a node is expanded. ExapandEvent is fired when a node
  609. * is to be expanded. it can me used to dynamically fill the sub-nodes of
  610. * the node.
  611. *
  612. * @author IT Mill Ltd.
  613. * @version
  614. * @VERSION@
  615. * @since 3.0
  616. */
  617. public class ExpandEvent extends Component.Event {
  618. private final Object expandedItemId;
  619. /**
  620. * New instance of options change event
  621. *
  622. * @param source
  623. * the Source of the event.
  624. * @param expandedItemId
  625. */
  626. public ExpandEvent(Component source, Object expandedItemId) {
  627. super(source);
  628. this.expandedItemId = expandedItemId;
  629. }
  630. /**
  631. * Node where the event occurred.
  632. *
  633. * @return the Source of the event.
  634. */
  635. public Object getItemId() {
  636. return expandedItemId;
  637. }
  638. }
  639. /**
  640. * Expand event listener.
  641. *
  642. * @author IT Mill Ltd.
  643. * @version
  644. * @VERSION@
  645. * @since 3.0
  646. */
  647. public interface ExpandListener extends Serializable {
  648. /**
  649. * A node has been expanded.
  650. *
  651. * @param event
  652. * the Expand event.
  653. */
  654. public void nodeExpand(ExpandEvent event);
  655. }
  656. /**
  657. * Adds the expand listener.
  658. *
  659. * @param listener
  660. * the Listener to be added.
  661. */
  662. public void addListener(ExpandListener listener) {
  663. addListener(ExpandEvent.class, listener, EXPAND_METHOD);
  664. }
  665. /**
  666. * Removes the expand listener.
  667. *
  668. * @param listener
  669. * the Listener to be removed.
  670. */
  671. public void removeListener(ExpandListener listener) {
  672. removeListener(ExpandEvent.class, listener, EXPAND_METHOD);
  673. }
  674. /**
  675. * Emits the expand event.
  676. *
  677. * @param itemId
  678. * the item id.
  679. */
  680. protected void fireExpandEvent(Object itemId) {
  681. fireEvent(new ExpandEvent(this, itemId));
  682. }
  683. /* Collapse event */
  684. /**
  685. * Collapse event
  686. *
  687. * @author IT Mill Ltd.
  688. * @version
  689. * @VERSION@
  690. * @since 3.0
  691. */
  692. public class CollapseEvent extends Component.Event {
  693. private final Object collapsedItemId;
  694. /**
  695. * New instance of options change event.
  696. *
  697. * @param source
  698. * the Source of the event.
  699. * @param collapsedItemId
  700. */
  701. public CollapseEvent(Component source, Object collapsedItemId) {
  702. super(source);
  703. this.collapsedItemId = collapsedItemId;
  704. }
  705. /**
  706. * Gets tge Collapsed Item id.
  707. *
  708. * @return the collapsed item id.
  709. */
  710. public Object getItemId() {
  711. return collapsedItemId;
  712. }
  713. }
  714. /**
  715. * Collapse event listener.
  716. *
  717. * @author IT Mill Ltd.
  718. * @version
  719. * @VERSION@
  720. * @since 3.0
  721. */
  722. public interface CollapseListener extends Serializable {
  723. /**
  724. * A node has been collapsed.
  725. *
  726. * @param event
  727. * the Collapse event.
  728. */
  729. public void nodeCollapse(CollapseEvent event);
  730. }
  731. /**
  732. * Adds the collapse listener.
  733. *
  734. * @param listener
  735. * the Listener to be added.
  736. */
  737. public void addListener(CollapseListener listener) {
  738. addListener(CollapseEvent.class, listener, COLLAPSE_METHOD);
  739. }
  740. /**
  741. * Removes the collapse listener.
  742. *
  743. * @param listener
  744. * the Listener to be removed.
  745. */
  746. public void removeListener(CollapseListener listener) {
  747. removeListener(CollapseEvent.class, listener, COLLAPSE_METHOD);
  748. }
  749. /**
  750. * Emits collapse event.
  751. *
  752. * @param itemId
  753. * the item id.
  754. */
  755. protected void fireCollapseEvent(Object itemId) {
  756. fireEvent(new CollapseEvent(this, itemId));
  757. }
  758. /* Action container */
  759. /**
  760. * Adds an action handler.
  761. *
  762. * @see com.vaadin.event.Action.Container#addActionHandler(Action.Handler)
  763. */
  764. public void addActionHandler(Action.Handler actionHandler) {
  765. if (actionHandler != null) {
  766. if (actionHandlers == null) {
  767. actionHandlers = new LinkedList<Action.Handler>();
  768. actionMapper = new KeyMapper();
  769. }
  770. if (!actionHandlers.contains(actionHandler)) {
  771. actionHandlers.add(actionHandler);
  772. requestRepaint();
  773. }
  774. }
  775. }
  776. /**
  777. * Removes an action handler.
  778. *
  779. * @see com.vaadin.event.Action.Container#removeActionHandler(Action.Handler)
  780. */
  781. public void removeActionHandler(Action.Handler actionHandler) {
  782. if (actionHandlers != null && actionHandlers.contains(actionHandler)) {
  783. actionHandlers.remove(actionHandler);
  784. if (actionHandlers.isEmpty()) {
  785. actionHandlers = null;
  786. actionMapper = null;
  787. }
  788. requestRepaint();
  789. }
  790. }
  791. /**
  792. * Gets the visible item ids.
  793. *
  794. * @see com.vaadin.ui.Select#getVisibleItemIds()
  795. */
  796. @Override
  797. public Collection getVisibleItemIds() {
  798. final LinkedList visible = new LinkedList();
  799. // Iterates trough hierarchical tree using a stack of iterators
  800. final Stack<Iterator> iteratorStack = new Stack<Iterator>();
  801. final Collection ids = rootItemIds();
  802. if (ids != null) {
  803. iteratorStack.push(ids.iterator());
  804. }
  805. while (!iteratorStack.isEmpty()) {
  806. // Gets the iterator for current tree level
  807. final Iterator i = iteratorStack.peek();
  808. // If the level is finished, back to previous tree level
  809. if (!i.hasNext()) {
  810. // Removes used iterator from the stack
  811. iteratorStack.pop();
  812. }
  813. // Adds the item on current level
  814. else {
  815. final Object itemId = i.next();
  816. visible.add(itemId);
  817. // Adds children if expanded, or close the tag
  818. if (isExpanded(itemId) && hasChildren(itemId)) {
  819. iteratorStack.push(getChildren(itemId).iterator());
  820. }
  821. }
  822. }
  823. return visible;
  824. }
  825. /**
  826. * Tree does not support <code>setNullSelectionItemId</code>.
  827. *
  828. * @see com.vaadin.ui.AbstractSelect#setNullSelectionItemId(java.lang.Object)
  829. */
  830. @Override
  831. public void setNullSelectionItemId(Object nullSelectionItemId)
  832. throws UnsupportedOperationException {
  833. if (nullSelectionItemId != null) {
  834. throw new UnsupportedOperationException();
  835. }
  836. }
  837. /**
  838. * Adding new items is not supported.
  839. *
  840. * @throws UnsupportedOperationException
  841. * if set to true.
  842. * @see com.vaadin.ui.Select#setNewItemsAllowed(boolean)
  843. */
  844. @Override
  845. public void setNewItemsAllowed(boolean allowNewOptions)
  846. throws UnsupportedOperationException {
  847. if (allowNewOptions) {
  848. throw new UnsupportedOperationException();
  849. }
  850. }
  851. /**
  852. * Focusing to this component is not supported.
  853. *
  854. * @throws UnsupportedOperationException
  855. * if invoked.
  856. * @see com.vaadin.ui.AbstractField#focus()
  857. */
  858. @Override
  859. public void focus() throws UnsupportedOperationException {
  860. throw new UnsupportedOperationException();
  861. }
  862. /**
  863. * Tree does not support lazy options loading mode. Setting this true will
  864. * throw UnsupportedOperationException.
  865. *
  866. * @see com.vaadin.ui.Select#setLazyLoading(boolean)
  867. */
  868. public void setLazyLoading(boolean useLazyLoading) {
  869. if (useLazyLoading) {
  870. throw new UnsupportedOperationException(
  871. "Lazy options loading is not supported by Tree.");
  872. }
  873. }
  874. private int clickListenerCount = 0;
  875. public void addListener(ItemClickListener listener) {
  876. addListener(ItemClickEvent.class, listener,
  877. ItemClickEvent.ITEM_CLICK_METHOD);
  878. clickListenerCount++;
  879. // repaint needed only if click listening became necessary
  880. if (clickListenerCount == 1) {
  881. requestRepaint();
  882. }
  883. }
  884. public void removeListener(ItemClickListener listener) {
  885. removeListener(ItemClickEvent.class, listener,
  886. ItemClickEvent.ITEM_CLICK_METHOD);
  887. clickListenerCount++;
  888. // repaint needed only if click listening is not needed in client
  889. // anymore
  890. if (clickListenerCount == 0) {
  891. requestRepaint();
  892. }
  893. }
  894. }