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 55KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806
  1. /*
  2. * Copyright 2000-2014 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.ui;
  17. import java.io.Serializable;
  18. import java.lang.reflect.Method;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.HashMap;
  22. import java.util.HashSet;
  23. import java.util.Iterator;
  24. import java.util.LinkedHashSet;
  25. import java.util.LinkedList;
  26. import java.util.Map;
  27. import java.util.Set;
  28. import java.util.Stack;
  29. import java.util.StringTokenizer;
  30. import com.vaadin.data.Container;
  31. import com.vaadin.data.Item;
  32. import com.vaadin.data.util.ContainerHierarchicalWrapper;
  33. import com.vaadin.data.util.HierarchicalContainer;
  34. import com.vaadin.event.Action;
  35. import com.vaadin.event.Action.Handler;
  36. import com.vaadin.event.DataBoundTransferable;
  37. import com.vaadin.event.ItemClickEvent;
  38. import com.vaadin.event.ItemClickEvent.ItemClickListener;
  39. import com.vaadin.event.ItemClickEvent.ItemClickNotifier;
  40. import com.vaadin.event.Transferable;
  41. import com.vaadin.event.dd.DragAndDropEvent;
  42. import com.vaadin.event.dd.DragSource;
  43. import com.vaadin.event.dd.DropHandler;
  44. import com.vaadin.event.dd.DropTarget;
  45. import com.vaadin.event.dd.TargetDetails;
  46. import com.vaadin.event.dd.acceptcriteria.ClientSideCriterion;
  47. import com.vaadin.event.dd.acceptcriteria.ServerSideCriterion;
  48. import com.vaadin.event.dd.acceptcriteria.TargetDetailIs;
  49. import com.vaadin.server.KeyMapper;
  50. import com.vaadin.server.PaintException;
  51. import com.vaadin.server.PaintTarget;
  52. import com.vaadin.server.Resource;
  53. import com.vaadin.shared.MouseEventDetails;
  54. import com.vaadin.shared.ui.MultiSelectMode;
  55. import com.vaadin.shared.ui.dd.VerticalDropLocation;
  56. import com.vaadin.shared.ui.tree.TreeConstants;
  57. import com.vaadin.util.ReflectTools;
  58. /**
  59. * Tree component. A Tree can be used to select an item (or multiple items) from
  60. * a hierarchical set of items.
  61. *
  62. * @author Vaadin Ltd.
  63. * @since 3.0
  64. */
  65. @SuppressWarnings({ "serial", "deprecation" })
  66. public class Tree extends AbstractSelect implements Container.Hierarchical,
  67. Action.Container, ItemClickNotifier, DragSource, DropTarget {
  68. /* Private members */
  69. private static final String NULL_ALT_EXCEPTION_MESSAGE = "Parameter 'altText' needs to be non null";
  70. /**
  71. * Item icons alt texts.
  72. */
  73. private final HashMap<Object, String> itemIconAlts = new HashMap<Object, String>();
  74. /**
  75. * Set of expanded nodes.
  76. */
  77. private HashSet<Object> expanded = new HashSet<Object>();
  78. /**
  79. * List of action handlers.
  80. */
  81. private LinkedList<Action.Handler> actionHandlers = null;
  82. /**
  83. * Action mapper.
  84. */
  85. private KeyMapper<Action> actionMapper = null;
  86. /**
  87. * Is the tree selectable on the client side.
  88. */
  89. private boolean selectable = true;
  90. /**
  91. * Flag to indicate sub-tree loading
  92. */
  93. private boolean partialUpdate = false;
  94. /**
  95. * Holds a itemId which was recently expanded
  96. */
  97. private Object expandedItemId;
  98. /**
  99. * a flag which indicates initial paint. After this flag set true partial
  100. * updates are allowed.
  101. */
  102. private boolean initialPaint = true;
  103. /**
  104. * Item tooltip generator
  105. */
  106. private ItemDescriptionGenerator itemDescriptionGenerator;
  107. /**
  108. * Supported drag modes for Tree.
  109. */
  110. public enum TreeDragMode {
  111. /**
  112. * When drag mode is NONE, dragging from Tree is not supported. Browsers
  113. * may still support selecting text/icons from Tree which can initiate
  114. * HTML 5 style drag and drop operation.
  115. */
  116. NONE,
  117. /**
  118. * When drag mode is NODE, users can initiate drag from Tree nodes that
  119. * represent {@link Item}s in from the backed {@link Container}.
  120. */
  121. NODE
  122. // , SUBTREE
  123. }
  124. private TreeDragMode dragMode = TreeDragMode.NONE;
  125. private MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT;
  126. /* Tree constructors */
  127. /**
  128. * Creates a new empty tree.
  129. */
  130. public Tree() {
  131. this(null);
  132. }
  133. /**
  134. * Creates a new empty tree with caption.
  135. *
  136. * @param caption
  137. */
  138. public Tree(String caption) {
  139. this(caption, new HierarchicalContainer());
  140. }
  141. /**
  142. * Creates a new tree with caption and connect it to a Container.
  143. *
  144. * @param caption
  145. * @param dataSource
  146. */
  147. public Tree(String caption, Container dataSource) {
  148. super(caption, dataSource);
  149. }
  150. @Override
  151. public void setItemIcon(Object itemId, Resource icon) {
  152. setItemIcon(itemId, icon, "");
  153. }
  154. /**
  155. * Sets the icon for an item.
  156. *
  157. * @param itemId
  158. * the id of the item to be assigned an icon.
  159. * @param icon
  160. * the icon to use or null.
  161. *
  162. * @param altText
  163. * the alternative text for the icon
  164. */
  165. public void setItemIcon(Object itemId, Resource icon, String altText) {
  166. if (itemId != null) {
  167. super.setItemIcon(itemId, icon);
  168. if (icon == null) {
  169. itemIconAlts.remove(itemId);
  170. } else if (altText == null) {
  171. throw new IllegalArgumentException(NULL_ALT_EXCEPTION_MESSAGE);
  172. } else {
  173. itemIconAlts.put(itemId, altText);
  174. }
  175. markAsDirty();
  176. }
  177. }
  178. /**
  179. * Set the alternate text for an item.
  180. *
  181. * Used when the item has an icon.
  182. *
  183. * @param itemId
  184. * the id of the item to be assigned an icon.
  185. * @param altText
  186. * the alternative text for the icon
  187. */
  188. public void setItemIconAlternateText(Object itemId, String altText) {
  189. if (itemId != null) {
  190. if (altText == null) {
  191. throw new IllegalArgumentException(NULL_ALT_EXCEPTION_MESSAGE);
  192. } else {
  193. itemIconAlts.put(itemId, altText);
  194. }
  195. }
  196. }
  197. /**
  198. * Return the alternate text of an icon in a tree item.
  199. *
  200. * @param itemId
  201. * Object with the ID of the item
  202. * @return String with the alternate text of the icon, or null when no icon
  203. * was set
  204. */
  205. public String getItemIconAlternateText(Object itemId) {
  206. String storedAlt = itemIconAlts.get(itemId);
  207. return storedAlt == null ? "" : storedAlt;
  208. }
  209. /* Expanding and collapsing */
  210. /**
  211. * Check is an item is expanded
  212. *
  213. * @param itemId
  214. * the item id.
  215. * @return true iff the item is expanded.
  216. */
  217. public boolean isExpanded(Object itemId) {
  218. return expanded.contains(itemId);
  219. }
  220. /**
  221. * Expands an item.
  222. *
  223. * @param itemId
  224. * the item id.
  225. * @return True iff the expand operation succeeded
  226. */
  227. public boolean expandItem(Object itemId) {
  228. boolean success = expandItem(itemId, true);
  229. markAsDirty();
  230. return success;
  231. }
  232. /**
  233. * Expands an item.
  234. *
  235. * @param itemId
  236. * the item id.
  237. * @param sendChildTree
  238. * flag to indicate if client needs subtree or not (may be
  239. * cached)
  240. * @return True if the expand operation succeeded
  241. */
  242. private boolean expandItem(Object itemId, boolean sendChildTree) {
  243. // Succeeds if the node is already expanded
  244. if (isExpanded(itemId)) {
  245. return true;
  246. }
  247. // Nodes that can not have children are not expandable
  248. if (!areChildrenAllowed(itemId)) {
  249. return false;
  250. }
  251. // Expands
  252. expanded.add(itemId);
  253. expandedItemId = itemId;
  254. if (initialPaint) {
  255. markAsDirty();
  256. } else if (sendChildTree) {
  257. requestPartialRepaint();
  258. }
  259. fireExpandEvent(itemId);
  260. return true;
  261. }
  262. @Override
  263. public void markAsDirty() {
  264. super.markAsDirty();
  265. partialUpdate = false;
  266. }
  267. private void requestPartialRepaint() {
  268. super.markAsDirty();
  269. partialUpdate = true;
  270. }
  271. /**
  272. * Expands the items recursively
  273. *
  274. * Expands all the children recursively starting from an item. Operation
  275. * succeeds only if all expandable items are expanded.
  276. *
  277. * @param startItemId
  278. * @return True iff the expand operation succeeded
  279. */
  280. public boolean expandItemsRecursively(Object startItemId) {
  281. boolean result = true;
  282. // Initial stack
  283. final Stack<Object> todo = new Stack<Object>();
  284. todo.add(startItemId);
  285. // Expands recursively
  286. while (!todo.isEmpty()) {
  287. final Object id = todo.pop();
  288. if (areChildrenAllowed(id) && !expandItem(id, false)) {
  289. result = false;
  290. }
  291. if (hasChildren(id)) {
  292. todo.addAll(getChildren(id));
  293. }
  294. }
  295. markAsDirty();
  296. return result;
  297. }
  298. /**
  299. * Collapses an item.
  300. *
  301. * @param itemId
  302. * the item id.
  303. * @return True iff the collapse operation succeeded
  304. */
  305. public boolean collapseItem(Object itemId) {
  306. // Succeeds if the node is already collapsed
  307. if (!isExpanded(itemId)) {
  308. return true;
  309. }
  310. // Collapse
  311. expanded.remove(itemId);
  312. markAsDirty();
  313. fireCollapseEvent(itemId);
  314. return true;
  315. }
  316. /**
  317. * Collapses the items recursively.
  318. *
  319. * Collapse all the children recursively starting from an item. Operation
  320. * succeeds only if all expandable items are collapsed.
  321. *
  322. * @param startItemId
  323. * @return True iff the collapse operation succeeded
  324. */
  325. public boolean collapseItemsRecursively(Object startItemId) {
  326. boolean result = true;
  327. // Initial stack
  328. final Stack<Object> todo = new Stack<Object>();
  329. todo.add(startItemId);
  330. // Collapse recursively
  331. while (!todo.isEmpty()) {
  332. final Object id = todo.pop();
  333. if (areChildrenAllowed(id) && !collapseItem(id)) {
  334. result = false;
  335. }
  336. if (hasChildren(id)) {
  337. todo.addAll(getChildren(id));
  338. }
  339. }
  340. return result;
  341. }
  342. /**
  343. * Returns the current selectable state. Selectable determines if the a node
  344. * can be selected on the client side. Selectable does not affect
  345. * {@link #setValue(Object)} or {@link #select(Object)}.
  346. *
  347. * <p>
  348. * The tree is selectable by default.
  349. * </p>
  350. *
  351. * @return the current selectable state.
  352. */
  353. public boolean isSelectable() {
  354. return selectable;
  355. }
  356. /**
  357. * Sets the selectable state. Selectable determines if the a node can be
  358. * selected on the client side. Selectable does not affect
  359. * {@link #setValue(Object)} or {@link #select(Object)}.
  360. *
  361. * <p>
  362. * The tree is selectable by default.
  363. * </p>
  364. *
  365. * @param selectable
  366. * The new selectable state.
  367. */
  368. public void setSelectable(boolean selectable) {
  369. if (this.selectable != selectable) {
  370. this.selectable = selectable;
  371. markAsDirty();
  372. }
  373. }
  374. /**
  375. * Sets the behavior of the multiselect mode
  376. *
  377. * @param mode
  378. * The mode to set
  379. */
  380. public void setMultiselectMode(MultiSelectMode mode) {
  381. if (multiSelectMode != mode && mode != null) {
  382. multiSelectMode = mode;
  383. markAsDirty();
  384. }
  385. }
  386. /**
  387. * Returns the mode the multiselect is in. The mode controls how
  388. * multiselection can be done.
  389. *
  390. * @return The mode
  391. */
  392. public MultiSelectMode getMultiselectMode() {
  393. return multiSelectMode;
  394. }
  395. /* Component API */
  396. /*
  397. * (non-Javadoc)
  398. *
  399. * @see com.vaadin.ui.AbstractSelect#changeVariables(java.lang.Object,
  400. * java.util.Map)
  401. */
  402. @Override
  403. public void changeVariables(Object source, Map<String, Object> variables) {
  404. if (variables.containsKey("clickedKey")) {
  405. String key = (String) variables.get("clickedKey");
  406. Object id = itemIdMapper.get(key);
  407. MouseEventDetails details = MouseEventDetails
  408. .deSerialize((String) variables.get("clickEvent"));
  409. Item item = getItem(id);
  410. if (item != null) {
  411. fireEvent(new ItemClickEvent(this, item, id, null, details));
  412. }
  413. }
  414. if (!isSelectable() && variables.containsKey("selected")) {
  415. // Not-selectable is a special case, AbstractSelect does not support
  416. // TODO could be optimized.
  417. variables = new HashMap<String, Object>(variables);
  418. variables.remove("selected");
  419. }
  420. // Collapses the nodes
  421. if (variables.containsKey("collapse")) {
  422. final String[] keys = (String[]) variables.get("collapse");
  423. for (int i = 0; i < keys.length; i++) {
  424. final Object id = itemIdMapper.get(keys[i]);
  425. if (id != null && isExpanded(id)) {
  426. expanded.remove(id);
  427. if (expandedItemId == id) {
  428. expandedItemId = null;
  429. }
  430. fireCollapseEvent(id);
  431. }
  432. }
  433. }
  434. // Expands the nodes
  435. if (variables.containsKey("expand")) {
  436. boolean sendChildTree = false;
  437. if (variables.containsKey("requestChildTree")) {
  438. sendChildTree = true;
  439. }
  440. final String[] keys = (String[]) variables.get("expand");
  441. for (int i = 0; i < keys.length; i++) {
  442. final Object id = itemIdMapper.get(keys[i]);
  443. if (id != null) {
  444. expandItem(id, sendChildTree);
  445. }
  446. }
  447. }
  448. // AbstractSelect cannot handle multiselection so we handle
  449. // it ourself
  450. if (variables.containsKey("selected") && isMultiSelect()
  451. && multiSelectMode == MultiSelectMode.DEFAULT) {
  452. handleSelectedItems(variables);
  453. variables = new HashMap<String, Object>(variables);
  454. variables.remove("selected");
  455. }
  456. // Selections are handled by the select component
  457. super.changeVariables(source, variables);
  458. // Actions
  459. if (variables.containsKey("action")) {
  460. final StringTokenizer st = new StringTokenizer(
  461. (String) variables.get("action"), ",");
  462. if (st.countTokens() == 2) {
  463. final Object itemId = itemIdMapper.get(st.nextToken());
  464. final Action action = actionMapper.get(st.nextToken());
  465. if (action != null && (itemId == null || containsId(itemId))
  466. && actionHandlers != null) {
  467. for (Handler ah : actionHandlers) {
  468. ah.handleAction(action, this, itemId);
  469. }
  470. }
  471. }
  472. }
  473. }
  474. /**
  475. * Handles the selection
  476. *
  477. * @param variables
  478. * The variables sent to the server from the client
  479. */
  480. private void handleSelectedItems(Map<String, Object> variables) {
  481. final String[] ka = (String[]) variables.get("selected");
  482. // Converts the key-array to id-set
  483. final LinkedList<Object> s = new LinkedList<Object>();
  484. for (int i = 0; i < ka.length; i++) {
  485. final Object id = itemIdMapper.get(ka[i]);
  486. if (!isNullSelectionAllowed()
  487. && (id == null || id == getNullSelectionItemId())) {
  488. // skip empty selection if nullselection is not allowed
  489. markAsDirty();
  490. } else if (id != null && containsId(id)) {
  491. s.add(id);
  492. }
  493. }
  494. if (!isNullSelectionAllowed() && s.size() < 1) {
  495. // empty selection not allowed, keep old value
  496. markAsDirty();
  497. return;
  498. }
  499. setValue(s, true);
  500. }
  501. /**
  502. * Paints any needed component-specific things to the given UIDL stream.
  503. *
  504. * @see com.vaadin.ui.AbstractComponent#paintContent(PaintTarget)
  505. */
  506. @Override
  507. public void paintContent(PaintTarget target) throws PaintException {
  508. initialPaint = false;
  509. if (partialUpdate) {
  510. target.addAttribute("partialUpdate", true);
  511. target.addAttribute("rootKey", itemIdMapper.key(expandedItemId));
  512. } else {
  513. getCaptionChangeListener().clear();
  514. // The tab ordering number
  515. if (getTabIndex() > 0) {
  516. target.addAttribute("tabindex", getTabIndex());
  517. }
  518. // Paint tree attributes
  519. if (isSelectable()) {
  520. target.addAttribute("selectmode", (isMultiSelect() ? "multi"
  521. : "single"));
  522. if (isMultiSelect()) {
  523. target.addAttribute("multiselectmode",
  524. multiSelectMode.toString());
  525. }
  526. } else {
  527. target.addAttribute("selectmode", "none");
  528. }
  529. if (isNewItemsAllowed()) {
  530. target.addAttribute("allownewitem", true);
  531. }
  532. if (isNullSelectionAllowed()) {
  533. target.addAttribute("nullselect", true);
  534. }
  535. if (dragMode != TreeDragMode.NONE) {
  536. target.addAttribute("dragMode", dragMode.ordinal());
  537. }
  538. }
  539. // Initialize variables
  540. final Set<Action> actionSet = new LinkedHashSet<Action>();
  541. // rendered selectedKeys
  542. LinkedList<String> selectedKeys = new LinkedList<String>();
  543. final LinkedList<String> expandedKeys = new LinkedList<String>();
  544. // Iterates through hierarchical tree using a stack of iterators
  545. final Stack<Iterator<?>> iteratorStack = new Stack<Iterator<?>>();
  546. Collection<?> ids;
  547. if (partialUpdate) {
  548. ids = getChildren(expandedItemId);
  549. } else {
  550. ids = rootItemIds();
  551. }
  552. if (ids != null) {
  553. iteratorStack.push(ids.iterator());
  554. }
  555. /*
  556. * Body actions - Actions which has the target null and can be invoked
  557. * by right clicking on the Tree body
  558. */
  559. if (actionHandlers != null) {
  560. final ArrayList<String> keys = new ArrayList<String>();
  561. for (Handler ah : actionHandlers) {
  562. // Getting action for the null item, which in this case
  563. // means the body item
  564. final Action[] aa = ah.getActions(null, this);
  565. if (aa != null) {
  566. for (int ai = 0; ai < aa.length; ai++) {
  567. final String akey = actionMapper.key(aa[ai]);
  568. actionSet.add(aa[ai]);
  569. keys.add(akey);
  570. }
  571. }
  572. }
  573. target.addAttribute("alb", keys.toArray());
  574. }
  575. while (!iteratorStack.isEmpty()) {
  576. // Gets the iterator for current tree level
  577. final Iterator<?> i = iteratorStack.peek();
  578. // If the level is finished, back to previous tree level
  579. if (!i.hasNext()) {
  580. // Removes used iterator from the stack
  581. iteratorStack.pop();
  582. // Closes node
  583. if (!iteratorStack.isEmpty()) {
  584. target.endTag("node");
  585. }
  586. }
  587. // Adds the item on current level
  588. else {
  589. final Object itemId = i.next();
  590. // Starts the item / node
  591. final boolean isNode = areChildrenAllowed(itemId);
  592. if (isNode) {
  593. target.startTag("node");
  594. } else {
  595. target.startTag("leaf");
  596. }
  597. if (itemStyleGenerator != null) {
  598. String stylename = itemStyleGenerator
  599. .getStyle(this, itemId);
  600. if (stylename != null) {
  601. target.addAttribute(TreeConstants.ATTRIBUTE_NODE_STYLE,
  602. stylename);
  603. }
  604. }
  605. if (itemDescriptionGenerator != null) {
  606. String description = itemDescriptionGenerator
  607. .generateDescription(this, itemId, null);
  608. if (description != null && !description.equals("")) {
  609. target.addAttribute("descr", description);
  610. }
  611. }
  612. // Adds the attributes
  613. target.addAttribute(TreeConstants.ATTRIBUTE_NODE_CAPTION,
  614. getItemCaption(itemId));
  615. final Resource icon = getItemIcon(itemId);
  616. if (icon != null) {
  617. target.addAttribute(TreeConstants.ATTRIBUTE_NODE_ICON,
  618. getItemIcon(itemId));
  619. target.addAttribute(TreeConstants.ATTRIBUTE_NODE_ICON_ALT,
  620. getItemIconAlternateText(itemId));
  621. }
  622. final String key = itemIdMapper.key(itemId);
  623. target.addAttribute("key", key);
  624. if (isSelected(itemId)) {
  625. target.addAttribute("selected", true);
  626. selectedKeys.add(key);
  627. }
  628. if (areChildrenAllowed(itemId) && isExpanded(itemId)) {
  629. target.addAttribute("expanded", true);
  630. expandedKeys.add(key);
  631. }
  632. // Add caption change listener
  633. getCaptionChangeListener().addNotifierForItem(itemId);
  634. // Actions
  635. if (actionHandlers != null) {
  636. final ArrayList<String> keys = new ArrayList<String>();
  637. final Iterator<Action.Handler> ahi = actionHandlers
  638. .iterator();
  639. while (ahi.hasNext()) {
  640. final Action[] aa = ahi.next().getActions(itemId, this);
  641. if (aa != null) {
  642. for (int ai = 0; ai < aa.length; ai++) {
  643. final String akey = actionMapper.key(aa[ai]);
  644. actionSet.add(aa[ai]);
  645. keys.add(akey);
  646. }
  647. }
  648. }
  649. target.addAttribute("al", keys.toArray());
  650. }
  651. // Adds the children if expanded, or close the tag
  652. if (isExpanded(itemId) && hasChildren(itemId)
  653. && areChildrenAllowed(itemId)) {
  654. iteratorStack.push(getChildren(itemId).iterator());
  655. } else {
  656. if (isNode) {
  657. target.endTag("node");
  658. } else {
  659. target.endTag("leaf");
  660. }
  661. }
  662. }
  663. }
  664. // Actions
  665. if (!actionSet.isEmpty()) {
  666. target.addVariable(this, "action", "");
  667. target.startTag("actions");
  668. final Iterator<Action> i = actionSet.iterator();
  669. while (i.hasNext()) {
  670. final Action a = i.next();
  671. target.startTag("action");
  672. if (a.getCaption() != null) {
  673. target.addAttribute(TreeConstants.ATTRIBUTE_ACTION_CAPTION,
  674. a.getCaption());
  675. }
  676. if (a.getIcon() != null) {
  677. target.addAttribute(TreeConstants.ATTRIBUTE_ACTION_ICON,
  678. a.getIcon());
  679. }
  680. target.addAttribute("key", actionMapper.key(a));
  681. target.endTag("action");
  682. }
  683. target.endTag("actions");
  684. }
  685. if (partialUpdate) {
  686. partialUpdate = false;
  687. } else {
  688. // Selected
  689. target.addVariable(this, "selected",
  690. selectedKeys.toArray(new String[selectedKeys.size()]));
  691. // Expand and collapse
  692. target.addVariable(this, "expand", new String[] {});
  693. target.addVariable(this, "collapse", new String[] {});
  694. // New items
  695. target.addVariable(this, "newitem", new String[] {});
  696. if (dropHandler != null) {
  697. dropHandler.getAcceptCriterion().paint(target);
  698. }
  699. }
  700. }
  701. /* Container.Hierarchical API */
  702. /**
  703. * Tests if the Item with given ID can have any children.
  704. *
  705. * @see com.vaadin.data.Container.Hierarchical#areChildrenAllowed(Object)
  706. */
  707. @Override
  708. public boolean areChildrenAllowed(Object itemId) {
  709. return ((Container.Hierarchical) items).areChildrenAllowed(itemId);
  710. }
  711. /**
  712. * Gets the IDs of all Items that are children of the specified Item.
  713. *
  714. * @see com.vaadin.data.Container.Hierarchical#getChildren(Object)
  715. */
  716. @Override
  717. public Collection<?> getChildren(Object itemId) {
  718. return ((Container.Hierarchical) items).getChildren(itemId);
  719. }
  720. /**
  721. * Gets the ID of the parent Item of the specified Item.
  722. *
  723. * @see com.vaadin.data.Container.Hierarchical#getParent(Object)
  724. */
  725. @Override
  726. public Object getParent(Object itemId) {
  727. return ((Container.Hierarchical) items).getParent(itemId);
  728. }
  729. /**
  730. * Tests if the Item specified with <code>itemId</code> has child Items.
  731. *
  732. * @see com.vaadin.data.Container.Hierarchical#hasChildren(Object)
  733. */
  734. @Override
  735. public boolean hasChildren(Object itemId) {
  736. return ((Container.Hierarchical) items).hasChildren(itemId);
  737. }
  738. /**
  739. * Tests if the Item specified with <code>itemId</code> is a root Item.
  740. *
  741. * @see com.vaadin.data.Container.Hierarchical#isRoot(Object)
  742. */
  743. @Override
  744. public boolean isRoot(Object itemId) {
  745. return ((Container.Hierarchical) items).isRoot(itemId);
  746. }
  747. /**
  748. * Gets the IDs of all Items in the container that don't have a parent.
  749. *
  750. * @see com.vaadin.data.Container.Hierarchical#rootItemIds()
  751. */
  752. @Override
  753. public Collection<?> rootItemIds() {
  754. return ((Container.Hierarchical) items).rootItemIds();
  755. }
  756. /**
  757. * Sets the given Item's capability to have children.
  758. *
  759. * @see com.vaadin.data.Container.Hierarchical#setChildrenAllowed(Object,
  760. * boolean)
  761. */
  762. @Override
  763. public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed) {
  764. final boolean success = ((Container.Hierarchical) items)
  765. .setChildrenAllowed(itemId, areChildrenAllowed);
  766. if (success) {
  767. markAsDirty();
  768. }
  769. return success;
  770. }
  771. /*
  772. * (non-Javadoc)
  773. *
  774. * @see com.vaadin.data.Container.Hierarchical#setParent(java.lang.Object ,
  775. * java.lang.Object)
  776. */
  777. @Override
  778. public boolean setParent(Object itemId, Object newParentId) {
  779. final boolean success = ((Container.Hierarchical) items).setParent(
  780. itemId, newParentId);
  781. if (success) {
  782. markAsDirty();
  783. }
  784. return success;
  785. }
  786. /* Overriding select behavior */
  787. /**
  788. * Sets the Container that serves as the data source of the viewer.
  789. *
  790. * @see com.vaadin.data.Container.Viewer#setContainerDataSource(Container)
  791. */
  792. @Override
  793. public void setContainerDataSource(Container newDataSource) {
  794. if (newDataSource == null) {
  795. newDataSource = new HierarchicalContainer();
  796. }
  797. // Assure that the data source is ordered by making unordered
  798. // containers ordered by wrapping them
  799. if (Container.Hierarchical.class.isAssignableFrom(newDataSource
  800. .getClass())) {
  801. super.setContainerDataSource(newDataSource);
  802. } else {
  803. super.setContainerDataSource(new ContainerHierarchicalWrapper(
  804. newDataSource));
  805. }
  806. /*
  807. * Ensure previous expanded items are cleaned up if they don't exist in
  808. * the new container
  809. */
  810. if (expanded != null) {
  811. /*
  812. * We need to check that the expanded-field is not null since
  813. * setContainerDataSource() is called from the parent constructor
  814. * (AbstractSelect()) and at that time the expanded field is not yet
  815. * initialized.
  816. */
  817. cleanupExpandedItems();
  818. }
  819. }
  820. @Override
  821. public void containerItemSetChange(
  822. com.vaadin.data.Container.ItemSetChangeEvent event) {
  823. super.containerItemSetChange(event);
  824. if (getContainerDataSource() instanceof Filterable) {
  825. boolean hasFilters = !((Filterable) getContainerDataSource())
  826. .getContainerFilters().isEmpty();
  827. if (!hasFilters) {
  828. /*
  829. * If Container is not filtered then the itemsetchange is caused
  830. * by either adding or removing items to the container. To
  831. * prevent a memory leak we should cleanup the expanded list
  832. * from items which was removed.
  833. *
  834. * However, there will still be a leak if the container is
  835. * filtered to show only a subset of the items in the tree and
  836. * later unfiltered items are removed from the container. In
  837. * that case references to the unfiltered item ids will remain
  838. * in the expanded list until the Tree instance is removed and
  839. * the list is destroyed, or the container data source is
  840. * replaced/updated. To force the removal of the removed items
  841. * the application developer needs to a) remove the container
  842. * filters temporarly or b) re-apply the container datasource
  843. * using setContainerDataSource(getContainerDataSource())
  844. */
  845. cleanupExpandedItems();
  846. }
  847. }
  848. }
  849. /* Expand event and listener */
  850. /**
  851. * Event to fired when a node is expanded. ExapandEvent is fired when a node
  852. * is to be expanded. it can me used to dynamically fill the sub-nodes of
  853. * the node.
  854. *
  855. * @author Vaadin Ltd.
  856. * @since 3.0
  857. */
  858. public static class ExpandEvent extends Component.Event {
  859. private final Object expandedItemId;
  860. /**
  861. * New instance of options change event
  862. *
  863. * @param source
  864. * the Source of the event.
  865. * @param expandedItemId
  866. */
  867. public ExpandEvent(Component source, Object expandedItemId) {
  868. super(source);
  869. this.expandedItemId = expandedItemId;
  870. }
  871. /**
  872. * Node where the event occurred.
  873. *
  874. * @return the Source of the event.
  875. */
  876. public Object getItemId() {
  877. return expandedItemId;
  878. }
  879. }
  880. /**
  881. * Expand event listener.
  882. *
  883. * @author Vaadin Ltd.
  884. * @since 3.0
  885. */
  886. public interface ExpandListener extends Serializable {
  887. public static final Method EXPAND_METHOD = ReflectTools.findMethod(
  888. ExpandListener.class, "nodeExpand", ExpandEvent.class);
  889. /**
  890. * A node has been expanded.
  891. *
  892. * @param event
  893. * the Expand event.
  894. */
  895. public void nodeExpand(ExpandEvent event);
  896. }
  897. /**
  898. * Adds the expand listener.
  899. *
  900. * @param listener
  901. * the Listener to be added.
  902. */
  903. public void addExpandListener(ExpandListener listener) {
  904. addListener(ExpandEvent.class, listener, ExpandListener.EXPAND_METHOD);
  905. }
  906. /**
  907. * @deprecated As of 7.0, replaced by
  908. * {@link #addExpandListener(ExpandListener)}
  909. **/
  910. @Deprecated
  911. public void addListener(ExpandListener listener) {
  912. addExpandListener(listener);
  913. }
  914. /**
  915. * Removes the expand listener.
  916. *
  917. * @param listener
  918. * the Listener to be removed.
  919. */
  920. public void removeExpandListener(ExpandListener listener) {
  921. removeListener(ExpandEvent.class, listener,
  922. ExpandListener.EXPAND_METHOD);
  923. }
  924. /**
  925. * @deprecated As of 7.0, replaced by
  926. * {@link #removeExpandListener(ExpandListener)}
  927. **/
  928. @Deprecated
  929. public void removeListener(ExpandListener listener) {
  930. removeExpandListener(listener);
  931. }
  932. /**
  933. * Emits the expand event.
  934. *
  935. * @param itemId
  936. * the item id.
  937. */
  938. protected void fireExpandEvent(Object itemId) {
  939. fireEvent(new ExpandEvent(this, itemId));
  940. }
  941. /* Collapse event */
  942. /**
  943. * Collapse event
  944. *
  945. * @author Vaadin Ltd.
  946. * @since 3.0
  947. */
  948. public static class CollapseEvent extends Component.Event {
  949. private final Object collapsedItemId;
  950. /**
  951. * New instance of options change event.
  952. *
  953. * @param source
  954. * the Source of the event.
  955. * @param collapsedItemId
  956. */
  957. public CollapseEvent(Component source, Object collapsedItemId) {
  958. super(source);
  959. this.collapsedItemId = collapsedItemId;
  960. }
  961. /**
  962. * Gets tge Collapsed Item id.
  963. *
  964. * @return the collapsed item id.
  965. */
  966. public Object getItemId() {
  967. return collapsedItemId;
  968. }
  969. }
  970. /**
  971. * Collapse event listener.
  972. *
  973. * @author Vaadin Ltd.
  974. * @since 3.0
  975. */
  976. public interface CollapseListener extends Serializable {
  977. public static final Method COLLAPSE_METHOD = ReflectTools.findMethod(
  978. CollapseListener.class, "nodeCollapse", CollapseEvent.class);
  979. /**
  980. * A node has been collapsed.
  981. *
  982. * @param event
  983. * the Collapse event.
  984. */
  985. public void nodeCollapse(CollapseEvent event);
  986. }
  987. /**
  988. * Adds the collapse listener.
  989. *
  990. * @param listener
  991. * the Listener to be added.
  992. */
  993. public void addCollapseListener(CollapseListener listener) {
  994. addListener(CollapseEvent.class, listener,
  995. CollapseListener.COLLAPSE_METHOD);
  996. }
  997. /**
  998. * @deprecated As of 7.0, replaced by
  999. * {@link #addCollapseListener(CollapseListener)}
  1000. **/
  1001. @Deprecated
  1002. public void addListener(CollapseListener listener) {
  1003. addCollapseListener(listener);
  1004. }
  1005. /**
  1006. * Removes the collapse listener.
  1007. *
  1008. * @param listener
  1009. * the Listener to be removed.
  1010. */
  1011. public void removeCollapseListener(CollapseListener listener) {
  1012. removeListener(CollapseEvent.class, listener,
  1013. CollapseListener.COLLAPSE_METHOD);
  1014. }
  1015. /**
  1016. * @deprecated As of 7.0, replaced by
  1017. * {@link #removeCollapseListener(CollapseListener)}
  1018. **/
  1019. @Deprecated
  1020. public void removeListener(CollapseListener listener) {
  1021. removeCollapseListener(listener);
  1022. }
  1023. /**
  1024. * Emits collapse event.
  1025. *
  1026. * @param itemId
  1027. * the item id.
  1028. */
  1029. protected void fireCollapseEvent(Object itemId) {
  1030. fireEvent(new CollapseEvent(this, itemId));
  1031. }
  1032. /* Action container */
  1033. /**
  1034. * Adds an action handler.
  1035. *
  1036. * @see com.vaadin.event.Action.Container#addActionHandler(Action.Handler)
  1037. */
  1038. @Override
  1039. public void addActionHandler(Action.Handler actionHandler) {
  1040. if (actionHandler != null) {
  1041. if (actionHandlers == null) {
  1042. actionHandlers = new LinkedList<Action.Handler>();
  1043. actionMapper = new KeyMapper<Action>();
  1044. }
  1045. if (!actionHandlers.contains(actionHandler)) {
  1046. actionHandlers.add(actionHandler);
  1047. markAsDirty();
  1048. }
  1049. }
  1050. }
  1051. /**
  1052. * Removes an action handler.
  1053. *
  1054. * @see com.vaadin.event.Action.Container#removeActionHandler(Action.Handler)
  1055. */
  1056. @Override
  1057. public void removeActionHandler(Action.Handler actionHandler) {
  1058. if (actionHandlers != null && actionHandlers.contains(actionHandler)) {
  1059. actionHandlers.remove(actionHandler);
  1060. if (actionHandlers.isEmpty()) {
  1061. actionHandlers = null;
  1062. actionMapper = null;
  1063. }
  1064. markAsDirty();
  1065. }
  1066. }
  1067. /**
  1068. * Removes all action handlers
  1069. */
  1070. public void removeAllActionHandlers() {
  1071. actionHandlers = null;
  1072. actionMapper = null;
  1073. markAsDirty();
  1074. }
  1075. /**
  1076. * Gets the visible item ids.
  1077. *
  1078. * @see com.vaadin.ui.Select#getVisibleItemIds()
  1079. */
  1080. @Override
  1081. public Collection<?> getVisibleItemIds() {
  1082. final LinkedList<Object> visible = new LinkedList<Object>();
  1083. // Iterates trough hierarchical tree using a stack of iterators
  1084. final Stack<Iterator<?>> iteratorStack = new Stack<Iterator<?>>();
  1085. final Collection<?> ids = rootItemIds();
  1086. if (ids != null) {
  1087. iteratorStack.push(ids.iterator());
  1088. }
  1089. while (!iteratorStack.isEmpty()) {
  1090. // Gets the iterator for current tree level
  1091. final Iterator<?> i = iteratorStack.peek();
  1092. // If the level is finished, back to previous tree level
  1093. if (!i.hasNext()) {
  1094. // Removes used iterator from the stack
  1095. iteratorStack.pop();
  1096. }
  1097. // Adds the item on current level
  1098. else {
  1099. final Object itemId = i.next();
  1100. visible.add(itemId);
  1101. // Adds children if expanded, or close the tag
  1102. if (isExpanded(itemId) && hasChildren(itemId)) {
  1103. iteratorStack.push(getChildren(itemId).iterator());
  1104. }
  1105. }
  1106. }
  1107. return visible;
  1108. }
  1109. /**
  1110. * Tree does not support <code>setNullSelectionItemId</code>.
  1111. *
  1112. * @see com.vaadin.ui.AbstractSelect#setNullSelectionItemId(java.lang.Object)
  1113. */
  1114. @Override
  1115. public void setNullSelectionItemId(Object nullSelectionItemId)
  1116. throws UnsupportedOperationException {
  1117. if (nullSelectionItemId != null) {
  1118. throw new UnsupportedOperationException();
  1119. }
  1120. }
  1121. /**
  1122. * Adding new items is not supported.
  1123. *
  1124. * @throws UnsupportedOperationException
  1125. * if set to true.
  1126. * @see com.vaadin.ui.Select#setNewItemsAllowed(boolean)
  1127. */
  1128. @Override
  1129. public void setNewItemsAllowed(boolean allowNewOptions)
  1130. throws UnsupportedOperationException {
  1131. if (allowNewOptions) {
  1132. throw new UnsupportedOperationException();
  1133. }
  1134. }
  1135. /**
  1136. * Tree does not support lazy options loading mode. Setting this true will
  1137. * throw UnsupportedOperationException.
  1138. *
  1139. * @see com.vaadin.ui.Select#setLazyLoading(boolean)
  1140. */
  1141. public void setLazyLoading(boolean useLazyLoading) {
  1142. if (useLazyLoading) {
  1143. throw new UnsupportedOperationException(
  1144. "Lazy options loading is not supported by Tree.");
  1145. }
  1146. }
  1147. private ItemStyleGenerator itemStyleGenerator;
  1148. private DropHandler dropHandler;
  1149. @Override
  1150. public void addItemClickListener(ItemClickListener listener) {
  1151. addListener(TreeConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
  1152. listener, ItemClickEvent.ITEM_CLICK_METHOD);
  1153. }
  1154. /**
  1155. * @deprecated As of 7.0, replaced by
  1156. * {@link #addItemClickListener(ItemClickListener)}
  1157. **/
  1158. @Override
  1159. @Deprecated
  1160. public void addListener(ItemClickListener listener) {
  1161. addItemClickListener(listener);
  1162. }
  1163. @Override
  1164. public void removeItemClickListener(ItemClickListener listener) {
  1165. removeListener(TreeConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
  1166. listener);
  1167. }
  1168. /**
  1169. * @deprecated As of 7.0, replaced by
  1170. * {@link #removeItemClickListener(ItemClickListener)}
  1171. **/
  1172. @Override
  1173. @Deprecated
  1174. public void removeListener(ItemClickListener listener) {
  1175. removeItemClickListener(listener);
  1176. }
  1177. /**
  1178. * Sets the {@link ItemStyleGenerator} to be used with this tree.
  1179. *
  1180. * @param itemStyleGenerator
  1181. * item style generator or null to remove generator
  1182. */
  1183. public void setItemStyleGenerator(ItemStyleGenerator itemStyleGenerator) {
  1184. if (this.itemStyleGenerator != itemStyleGenerator) {
  1185. this.itemStyleGenerator = itemStyleGenerator;
  1186. markAsDirty();
  1187. }
  1188. }
  1189. /**
  1190. * @return the current {@link ItemStyleGenerator} for this tree. Null if
  1191. * {@link ItemStyleGenerator} is not set.
  1192. */
  1193. public ItemStyleGenerator getItemStyleGenerator() {
  1194. return itemStyleGenerator;
  1195. }
  1196. /**
  1197. * ItemStyleGenerator can be used to add custom styles to tree items. The
  1198. * CSS class name that will be added to the cell content is
  1199. * <tt>v-tree-node-[style name]</tt>.
  1200. */
  1201. public interface ItemStyleGenerator extends Serializable {
  1202. /**
  1203. * Called by Tree when an item is painted.
  1204. *
  1205. * @param source
  1206. * the source Tree
  1207. * @param itemId
  1208. * The itemId of the item to be painted
  1209. * @return The style name to add to this item. (the CSS class name will
  1210. * be v-tree-node-[style name]
  1211. */
  1212. public abstract String getStyle(Tree source, Object itemId);
  1213. }
  1214. // Overriden so javadoc comes from Container.Hierarchical
  1215. @Override
  1216. public boolean removeItem(Object itemId)
  1217. throws UnsupportedOperationException {
  1218. return super.removeItem(itemId);
  1219. }
  1220. @Override
  1221. public DropHandler getDropHandler() {
  1222. return dropHandler;
  1223. }
  1224. public void setDropHandler(DropHandler dropHandler) {
  1225. this.dropHandler = dropHandler;
  1226. }
  1227. /**
  1228. * A {@link TargetDetails} implementation with Tree specific api.
  1229. *
  1230. * @since 6.3
  1231. */
  1232. public class TreeTargetDetails extends AbstractSelectTargetDetails {
  1233. TreeTargetDetails(Map<String, Object> rawVariables) {
  1234. super(rawVariables);
  1235. }
  1236. @Override
  1237. public Tree getTarget() {
  1238. return (Tree) super.getTarget();
  1239. }
  1240. /**
  1241. * If the event is on a node that can not have children (see
  1242. * {@link Tree#areChildrenAllowed(Object)}), this method returns the
  1243. * parent item id of the target item (see {@link #getItemIdOver()} ).
  1244. * The identifier of the parent node is also returned if the cursor is
  1245. * on the top part of node. Else this method returns the same as
  1246. * {@link #getItemIdOver()}.
  1247. * <p>
  1248. * In other words this method returns the identifier of the "folder"
  1249. * into the drag operation is targeted.
  1250. * <p>
  1251. * If the method returns null, the current target is on a root node or
  1252. * on other undefined area over the tree component.
  1253. * <p>
  1254. * The default Tree implementation marks the targetted tree node with
  1255. * CSS classnames v-tree-node-dragfolder and
  1256. * v-tree-node-caption-dragfolder (for the caption element).
  1257. */
  1258. public Object getItemIdInto() {
  1259. Object itemIdOver = getItemIdOver();
  1260. if (areChildrenAllowed(itemIdOver)
  1261. && getDropLocation() == VerticalDropLocation.MIDDLE) {
  1262. return itemIdOver;
  1263. }
  1264. return getParent(itemIdOver);
  1265. }
  1266. /**
  1267. * If drop is targeted into "folder node" (see {@link #getItemIdInto()}
  1268. * ), this method returns the item id of the node after the drag was
  1269. * targeted. This method is useful when implementing drop into specific
  1270. * location (between specific nodes) in tree.
  1271. *
  1272. * @return the id of the item after the user targets the drop or null if
  1273. * "target" is a first item in node list (or the first in root
  1274. * node list)
  1275. */
  1276. public Object getItemIdAfter() {
  1277. Object itemIdOver = getItemIdOver();
  1278. Object itemIdInto2 = getItemIdInto();
  1279. if (itemIdOver.equals(itemIdInto2)) {
  1280. return null;
  1281. }
  1282. VerticalDropLocation dropLocation = getDropLocation();
  1283. if (VerticalDropLocation.TOP == dropLocation) {
  1284. // if on top of the caption area, add before
  1285. Collection<?> children;
  1286. Object itemIdInto = getItemIdInto();
  1287. if (itemIdInto != null) {
  1288. // seek the previous from child list
  1289. children = getChildren(itemIdInto);
  1290. } else {
  1291. children = rootItemIds();
  1292. }
  1293. Object ref = null;
  1294. for (Object object : children) {
  1295. if (object.equals(itemIdOver)) {
  1296. return ref;
  1297. }
  1298. ref = object;
  1299. }
  1300. }
  1301. return itemIdOver;
  1302. }
  1303. }
  1304. /*
  1305. * (non-Javadoc)
  1306. *
  1307. * @see
  1308. * com.vaadin.event.dd.DropTarget#translateDropTargetDetails(java.util.Map)
  1309. */
  1310. @Override
  1311. public TreeTargetDetails translateDropTargetDetails(
  1312. Map<String, Object> clientVariables) {
  1313. return new TreeTargetDetails(clientVariables);
  1314. }
  1315. /**
  1316. * Helper API for {@link TreeDropCriterion}
  1317. *
  1318. * @param itemId
  1319. * @return
  1320. */
  1321. private String key(Object itemId) {
  1322. return itemIdMapper.key(itemId);
  1323. }
  1324. /**
  1325. * Sets the drag mode that controls how Tree behaves as a {@link DragSource}
  1326. * .
  1327. *
  1328. * @param dragMode
  1329. */
  1330. public void setDragMode(TreeDragMode dragMode) {
  1331. this.dragMode = dragMode;
  1332. markAsDirty();
  1333. }
  1334. /**
  1335. * @return the drag mode that controls how Tree behaves as a
  1336. * {@link DragSource}.
  1337. *
  1338. * @see TreeDragMode
  1339. */
  1340. public TreeDragMode getDragMode() {
  1341. return dragMode;
  1342. }
  1343. /**
  1344. * Concrete implementation of {@link DataBoundTransferable} for data
  1345. * transferred from a tree.
  1346. *
  1347. * @see {@link DataBoundTransferable}.
  1348. *
  1349. * @since 6.3
  1350. */
  1351. protected class TreeTransferable extends DataBoundTransferable {
  1352. public TreeTransferable(Component sourceComponent,
  1353. Map<String, Object> rawVariables) {
  1354. super(sourceComponent, rawVariables);
  1355. }
  1356. @Override
  1357. public Object getItemId() {
  1358. return getData("itemId");
  1359. }
  1360. @Override
  1361. public Object getPropertyId() {
  1362. return getItemCaptionPropertyId();
  1363. }
  1364. }
  1365. /*
  1366. * (non-Javadoc)
  1367. *
  1368. * @see com.vaadin.event.dd.DragSource#getTransferable(java.util.Map)
  1369. */
  1370. @Override
  1371. public Transferable getTransferable(Map<String, Object> payload) {
  1372. TreeTransferable transferable = new TreeTransferable(this, payload);
  1373. // updating drag source variables
  1374. Object object = payload.get("itemId");
  1375. if (object != null) {
  1376. transferable.setData("itemId", itemIdMapper.get((String) object));
  1377. }
  1378. return transferable;
  1379. }
  1380. /**
  1381. * Lazy loading accept criterion for Tree. Accepted target nodes are loaded
  1382. * from server once per drag and drop operation. Developer must override one
  1383. * method that decides accepted tree nodes for the whole Tree.
  1384. *
  1385. * <p>
  1386. * Initially pretty much no data is sent to client. On first required
  1387. * criterion check (per drag request) the client side data structure is
  1388. * initialized from server and no subsequent requests requests are needed
  1389. * during that drag and drop operation.
  1390. */
  1391. public static abstract class TreeDropCriterion extends ServerSideCriterion {
  1392. private Tree tree;
  1393. private Set<Object> allowedItemIds;
  1394. /*
  1395. * (non-Javadoc)
  1396. *
  1397. * @see
  1398. * com.vaadin.event.dd.acceptCriteria.ServerSideCriterion#getIdentifier
  1399. * ()
  1400. */
  1401. @Override
  1402. protected String getIdentifier() {
  1403. return TreeDropCriterion.class.getCanonicalName();
  1404. }
  1405. /*
  1406. * (non-Javadoc)
  1407. *
  1408. * @see
  1409. * com.vaadin.event.dd.acceptCriteria.AcceptCriterion#accepts(com.vaadin
  1410. * .event.dd.DragAndDropEvent)
  1411. */
  1412. @Override
  1413. public boolean accept(DragAndDropEvent dragEvent) {
  1414. AbstractSelectTargetDetails dropTargetData = (AbstractSelectTargetDetails) dragEvent
  1415. .getTargetDetails();
  1416. tree = (Tree) dragEvent.getTargetDetails().getTarget();
  1417. allowedItemIds = getAllowedItemIds(dragEvent, tree);
  1418. return allowedItemIds.contains(dropTargetData.getItemIdOver());
  1419. }
  1420. /*
  1421. * (non-Javadoc)
  1422. *
  1423. * @see
  1424. * com.vaadin.event.dd.acceptCriteria.AcceptCriterion#paintResponse(
  1425. * com.vaadin.server.PaintTarget)
  1426. */
  1427. @Override
  1428. public void paintResponse(PaintTarget target) throws PaintException {
  1429. /*
  1430. * send allowed nodes to client so subsequent requests can be
  1431. * avoided
  1432. */
  1433. Object[] array = allowedItemIds.toArray();
  1434. for (int i = 0; i < array.length; i++) {
  1435. String key = tree.key(array[i]);
  1436. array[i] = key;
  1437. }
  1438. target.addAttribute("allowedIds", array);
  1439. }
  1440. protected abstract Set<Object> getAllowedItemIds(
  1441. DragAndDropEvent dragEvent, Tree tree);
  1442. }
  1443. /**
  1444. * A criterion that accepts {@link Transferable} only directly on a tree
  1445. * node that can have children.
  1446. * <p>
  1447. * Class is singleton, use {@link TargetItemAllowsChildren#get()} to get the
  1448. * instance.
  1449. *
  1450. * @see Tree#setChildrenAllowed(Object, boolean)
  1451. *
  1452. * @since 6.3
  1453. */
  1454. public static class TargetItemAllowsChildren extends TargetDetailIs {
  1455. private static TargetItemAllowsChildren instance = new TargetItemAllowsChildren();
  1456. public static TargetItemAllowsChildren get() {
  1457. return instance;
  1458. }
  1459. private TargetItemAllowsChildren() {
  1460. super("itemIdOverIsNode", Boolean.TRUE);
  1461. }
  1462. /*
  1463. * Uses enhanced server side check
  1464. */
  1465. @Override
  1466. public boolean accept(DragAndDropEvent dragEvent) {
  1467. try {
  1468. // must be over tree node and in the middle of it (not top or
  1469. // bottom
  1470. // part)
  1471. TreeTargetDetails eventDetails = (TreeTargetDetails) dragEvent
  1472. .getTargetDetails();
  1473. Object itemIdOver = eventDetails.getItemIdOver();
  1474. if (!eventDetails.getTarget().areChildrenAllowed(itemIdOver)) {
  1475. return false;
  1476. }
  1477. // return true if directly over
  1478. return eventDetails.getDropLocation() == VerticalDropLocation.MIDDLE;
  1479. } catch (Exception e) {
  1480. return false;
  1481. }
  1482. }
  1483. }
  1484. /**
  1485. * An accept criterion that checks the parent node (or parent hierarchy) for
  1486. * the item identifier given in constructor. If the parent is found, content
  1487. * is accepted. Criterion can be used to accepts drags on a specific sub
  1488. * tree only.
  1489. * <p>
  1490. * The root items is also consider to be valid target.
  1491. */
  1492. public class TargetInSubtree extends ClientSideCriterion {
  1493. private Object rootId;
  1494. private int depthToCheck = -1;
  1495. /**
  1496. * Constructs a criteria that accepts the drag if the targeted Item is a
  1497. * descendant of Item identified by given id
  1498. *
  1499. * @param parentItemId
  1500. * the item identifier of the parent node
  1501. */
  1502. public TargetInSubtree(Object parentItemId) {
  1503. rootId = parentItemId;
  1504. }
  1505. /**
  1506. * Constructs a criteria that accepts drops within given level below the
  1507. * subtree root identified by given id.
  1508. *
  1509. * @param rootId
  1510. * the item identifier to be sought for
  1511. * @param depthToCheck
  1512. * the depth that tree is traversed upwards to seek for the
  1513. * parent, -1 means that the whole structure should be
  1514. * checked
  1515. */
  1516. public TargetInSubtree(Object rootId, int depthToCheck) {
  1517. this.rootId = rootId;
  1518. this.depthToCheck = depthToCheck;
  1519. }
  1520. @Override
  1521. public boolean accept(DragAndDropEvent dragEvent) {
  1522. try {
  1523. TreeTargetDetails eventDetails = (TreeTargetDetails) dragEvent
  1524. .getTargetDetails();
  1525. if (eventDetails.getItemIdOver() != null) {
  1526. Object itemId = eventDetails.getItemIdOver();
  1527. int i = 0;
  1528. while (itemId != null
  1529. && (depthToCheck == -1 || i <= depthToCheck)) {
  1530. if (itemId.equals(rootId)) {
  1531. return true;
  1532. }
  1533. itemId = getParent(itemId);
  1534. i++;
  1535. }
  1536. }
  1537. return false;
  1538. } catch (Exception e) {
  1539. return false;
  1540. }
  1541. }
  1542. @Override
  1543. public void paintContent(PaintTarget target) throws PaintException {
  1544. super.paintContent(target);
  1545. target.addAttribute("depth", depthToCheck);
  1546. target.addAttribute("key", key(rootId));
  1547. }
  1548. }
  1549. /**
  1550. * Set the item description generator which generates tooltips for the tree
  1551. * items
  1552. *
  1553. * @param generator
  1554. * The generator to use or null to disable
  1555. */
  1556. public void setItemDescriptionGenerator(ItemDescriptionGenerator generator) {
  1557. if (generator != itemDescriptionGenerator) {
  1558. itemDescriptionGenerator = generator;
  1559. markAsDirty();
  1560. }
  1561. }
  1562. /**
  1563. * Get the item description generator which generates tooltips for tree
  1564. * items
  1565. */
  1566. public ItemDescriptionGenerator getItemDescriptionGenerator() {
  1567. return itemDescriptionGenerator;
  1568. }
  1569. private void cleanupExpandedItems() {
  1570. Set<Object> removedItemIds = new HashSet<Object>();
  1571. for (Object expandedItemId : expanded) {
  1572. if (getItem(expandedItemId) == null) {
  1573. removedItemIds.add(expandedItemId);
  1574. if (this.expandedItemId == expandedItemId) {
  1575. this.expandedItemId = null;
  1576. }
  1577. }
  1578. }
  1579. expanded.removeAll(removedItemIds);
  1580. }
  1581. }