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.

VTree.java 78KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.ArrayList;
  6. import java.util.HashMap;
  7. import java.util.HashSet;
  8. import java.util.Iterator;
  9. import java.util.LinkedList;
  10. import java.util.List;
  11. import java.util.Set;
  12. import com.google.gwt.core.client.JavaScriptObject;
  13. import com.google.gwt.core.client.Scheduler;
  14. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  15. import com.google.gwt.dom.client.NativeEvent;
  16. import com.google.gwt.dom.client.Node;
  17. import com.google.gwt.event.dom.client.BlurEvent;
  18. import com.google.gwt.event.dom.client.BlurHandler;
  19. import com.google.gwt.event.dom.client.ContextMenuEvent;
  20. import com.google.gwt.event.dom.client.ContextMenuHandler;
  21. import com.google.gwt.event.dom.client.FocusEvent;
  22. import com.google.gwt.event.dom.client.FocusHandler;
  23. import com.google.gwt.event.dom.client.KeyCodes;
  24. import com.google.gwt.event.dom.client.KeyDownEvent;
  25. import com.google.gwt.event.dom.client.KeyDownHandler;
  26. import com.google.gwt.event.dom.client.KeyPressEvent;
  27. import com.google.gwt.event.dom.client.KeyPressHandler;
  28. import com.google.gwt.user.client.Command;
  29. import com.google.gwt.user.client.DOM;
  30. import com.google.gwt.user.client.Element;
  31. import com.google.gwt.user.client.Event;
  32. import com.google.gwt.user.client.Window;
  33. import com.google.gwt.user.client.ui.FlowPanel;
  34. import com.google.gwt.user.client.ui.SimplePanel;
  35. import com.google.gwt.user.client.ui.UIObject;
  36. import com.google.gwt.user.client.ui.Widget;
  37. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  38. import com.vaadin.terminal.gwt.client.BrowserInfo;
  39. import com.vaadin.terminal.gwt.client.MouseEventDetails;
  40. import com.vaadin.terminal.gwt.client.Paintable;
  41. import com.vaadin.terminal.gwt.client.TooltipInfo;
  42. import com.vaadin.terminal.gwt.client.UIDL;
  43. import com.vaadin.terminal.gwt.client.Util;
  44. import com.vaadin.terminal.gwt.client.VTooltip;
  45. import com.vaadin.terminal.gwt.client.ui.dd.DDUtil;
  46. import com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler;
  47. import com.vaadin.terminal.gwt.client.ui.dd.VAcceptCallback;
  48. import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager;
  49. import com.vaadin.terminal.gwt.client.ui.dd.VDragEvent;
  50. import com.vaadin.terminal.gwt.client.ui.dd.VDropHandler;
  51. import com.vaadin.terminal.gwt.client.ui.dd.VHasDropHandler;
  52. import com.vaadin.terminal.gwt.client.ui.dd.VTransferable;
  53. import com.vaadin.terminal.gwt.client.ui.dd.VerticalDropLocation;
  54. /**
  55. *
  56. */
  57. public class VTree extends FocusElementPanel implements Paintable,
  58. VHasDropHandler, FocusHandler, BlurHandler, KeyPressHandler,
  59. KeyDownHandler, SubPartAware, ActionOwner {
  60. public static final String CLASSNAME = "v-tree";
  61. public static final String ITEM_CLICK_EVENT_ID = "itemClick";
  62. public static final int MULTISELECT_MODE_DEFAULT = 0;
  63. public static final int MULTISELECT_MODE_SIMPLE = 1;
  64. private static final int CHARCODE_SPACE = 32;
  65. private final FlowPanel body = new FlowPanel();
  66. private Set<String> selectedIds = new HashSet<String>();
  67. private ApplicationConnection client;
  68. private String paintableId;
  69. private boolean selectable;
  70. private boolean isMultiselect;
  71. private String currentMouseOverKey;
  72. private TreeNode lastSelection;
  73. private TreeNode focusedNode;
  74. private int multiSelectMode = MULTISELECT_MODE_DEFAULT;
  75. private final HashMap<String, TreeNode> keyToNode = new HashMap<String, TreeNode>();
  76. /**
  77. * This map contains captions and icon urls for actions like: * "33_c" ->
  78. * "Edit" * "33_i" -> "http://dom.com/edit.png"
  79. */
  80. private final HashMap<String, String> actionMap = new HashMap<String, String>();
  81. private boolean immediate;
  82. private boolean isNullSelectionAllowed = true;
  83. private boolean disabled = false;
  84. private boolean readonly;
  85. private boolean rendering;
  86. private VAbstractDropHandler dropHandler;
  87. private int dragMode;
  88. private boolean selectionHasChanged = false;
  89. private String[] bodyActionKeys;
  90. public VLazyExecutor iconLoaded = new VLazyExecutor(50,
  91. new ScheduledCommand() {
  92. public void execute() {
  93. Util.notifyParentOfSizeChange(VTree.this, true);
  94. }
  95. });
  96. public VTree() {
  97. super();
  98. setStyleName(CLASSNAME);
  99. add(body);
  100. addFocusHandler(this);
  101. addBlurHandler(this);
  102. /*
  103. * Listen to context menu events on the empty space in the tree
  104. */
  105. sinkEvents(Event.ONCONTEXTMENU);
  106. addDomHandler(new ContextMenuHandler() {
  107. public void onContextMenu(ContextMenuEvent event) {
  108. handleBodyContextMenu(event);
  109. }
  110. }, ContextMenuEvent.getType());
  111. /*
  112. * Firefox auto-repeat works correctly only if we use a key press
  113. * handler, other browsers handle it correctly when using a key down
  114. * handler
  115. */
  116. if (BrowserInfo.get().isGecko() || BrowserInfo.get().isOpera()) {
  117. addKeyPressHandler(this);
  118. } else {
  119. addKeyDownHandler(this);
  120. }
  121. /*
  122. * We need to use the sinkEvents method to catch the keyUp events so we
  123. * can cache a single shift. KeyUpHandler cannot do this. At the same
  124. * time we catch the mouse down and up events so we can apply the text
  125. * selection patch in IE
  126. */
  127. sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONKEYUP);
  128. /*
  129. * Re-set the tab index to make sure that the FocusElementPanel's
  130. * (super) focus element gets the tab index and not the element
  131. * containing the tree.
  132. */
  133. setTabIndex(0);
  134. }
  135. /*
  136. * (non-Javadoc)
  137. *
  138. * @see
  139. * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user
  140. * .client.Event)
  141. */
  142. @Override
  143. public void onBrowserEvent(Event event) {
  144. super.onBrowserEvent(event);
  145. if (event.getTypeInt() == Event.ONMOUSEDOWN) {
  146. // Prevent default text selection in IE
  147. if (BrowserInfo.get().isIE()) {
  148. ((Element) event.getEventTarget().cast()).setPropertyJSO(
  149. "onselectstart", applyDisableTextSelectionIEHack());
  150. }
  151. } else if (event.getTypeInt() == Event.ONMOUSEUP) {
  152. // Remove IE text selection hack
  153. if (BrowserInfo.get().isIE()) {
  154. ((Element) event.getEventTarget().cast()).setPropertyJSO(
  155. "onselectstart", null);
  156. }
  157. } else if (event.getTypeInt() == Event.ONKEYUP) {
  158. if (selectionHasChanged) {
  159. if (event.getKeyCode() == getNavigationDownKey()
  160. && !event.getShiftKey()) {
  161. sendSelectionToServer();
  162. event.preventDefault();
  163. } else if (event.getKeyCode() == getNavigationUpKey()
  164. && !event.getShiftKey()) {
  165. sendSelectionToServer();
  166. event.preventDefault();
  167. } else if (event.getKeyCode() == KeyCodes.KEY_SHIFT) {
  168. sendSelectionToServer();
  169. event.preventDefault();
  170. } else if (event.getKeyCode() == getNavigationSelectKey()) {
  171. sendSelectionToServer();
  172. event.preventDefault();
  173. }
  174. }
  175. }
  176. }
  177. private void updateActionMap(UIDL c) {
  178. final Iterator<?> it = c.getChildIterator();
  179. while (it.hasNext()) {
  180. final UIDL action = (UIDL) it.next();
  181. final String key = action.getStringAttribute("key");
  182. final String caption = action.getStringAttribute("caption");
  183. actionMap.put(key + "_c", caption);
  184. if (action.hasAttribute("icon")) {
  185. // TODO need some uri handling ??
  186. actionMap.put(key + "_i", client.translateVaadinUri(action
  187. .getStringAttribute("icon")));
  188. } else {
  189. actionMap.remove(key + "_i");
  190. }
  191. }
  192. }
  193. public String getActionCaption(String actionKey) {
  194. return actionMap.get(actionKey + "_c");
  195. }
  196. public String getActionIcon(String actionKey) {
  197. return actionMap.get(actionKey + "_i");
  198. }
  199. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  200. // Ensure correct implementation and let container manage caption
  201. if (client.updateComponent(this, uidl, true)) {
  202. return;
  203. }
  204. rendering = true;
  205. this.client = client;
  206. if (uidl.hasAttribute("partialUpdate")) {
  207. handleUpdate(uidl);
  208. rendering = false;
  209. return;
  210. }
  211. paintableId = uidl.getId();
  212. immediate = uidl.hasAttribute("immediate");
  213. disabled = uidl.getBooleanAttribute("disabled");
  214. readonly = uidl.getBooleanAttribute("readonly");
  215. dragMode = uidl.hasAttribute("dragMode") ? uidl
  216. .getIntAttribute("dragMode") : 0;
  217. isNullSelectionAllowed = uidl.getBooleanAttribute("nullselect");
  218. if (uidl.hasAttribute("alb")) {
  219. bodyActionKeys = uidl.getStringArrayAttribute("alb");
  220. }
  221. body.clear();
  222. // clear out any references to nodes that no longer are attached
  223. keyToNode.clear();
  224. TreeNode childTree = null;
  225. UIDL childUidl = null;
  226. for (final Iterator<?> i = uidl.getChildIterator(); i.hasNext();) {
  227. childUidl = (UIDL) i.next();
  228. if ("actions".equals(childUidl.getTag())) {
  229. updateActionMap(childUidl);
  230. continue;
  231. } else if ("-ac".equals(childUidl.getTag())) {
  232. updateDropHandler(childUidl);
  233. continue;
  234. }
  235. childTree = new TreeNode();
  236. if (childTree.ie6compatnode != null) {
  237. body.add(childTree);
  238. }
  239. childTree.updateFromUIDL(childUidl, client);
  240. if (childTree.ie6compatnode == null) {
  241. body.add(childTree);
  242. }
  243. childTree.addStyleDependentName("root");
  244. childTree.childNodeContainer.addStyleDependentName("root");
  245. }
  246. if (childTree != null && childUidl != null) {
  247. boolean leaf = !childUidl.getTag().equals("node");
  248. childTree.addStyleDependentName(leaf ? "leaf-last" : "last");
  249. childTree.childNodeContainer.addStyleDependentName("last");
  250. }
  251. final String selectMode = uidl.getStringAttribute("selectmode");
  252. selectable = !"none".equals(selectMode);
  253. isMultiselect = "multi".equals(selectMode);
  254. if (isMultiselect) {
  255. multiSelectMode = uidl.getIntAttribute("multiselectmode");
  256. }
  257. selectedIds = uidl.getStringArrayVariableAsSet("selected");
  258. // Update lastSelection and focusedNode to point to *actual* nodes again
  259. // after the old ones have been cleared from the body. This fixes focus
  260. // and keyboard navigation issues as described in #7057 and other
  261. // tickets.
  262. if (lastSelection != null) {
  263. lastSelection = keyToNode.get(lastSelection.key);
  264. }
  265. if (focusedNode != null) {
  266. setFocusedNode(keyToNode.get(focusedNode.key));
  267. }
  268. if (lastSelection == null && focusedNode == null
  269. && !selectedIds.isEmpty()) {
  270. setFocusedNode(keyToNode.get(selectedIds.iterator().next()));
  271. focusedNode.setFocused(false);
  272. }
  273. rendering = false;
  274. }
  275. /**
  276. * Returns the first root node of the tree or null if there are no root
  277. * nodes.
  278. *
  279. * @return The first root {@link TreeNode}
  280. */
  281. protected TreeNode getFirstRootNode() {
  282. if (body.getWidgetCount() == 0) {
  283. return null;
  284. }
  285. return (TreeNode) body.getWidget(0);
  286. }
  287. /**
  288. * Returns the last root node of the tree or null if there are no root
  289. * nodes.
  290. *
  291. * @return The last root {@link TreeNode}
  292. */
  293. protected TreeNode getLastRootNode() {
  294. if (body.getWidgetCount() == 0) {
  295. return null;
  296. }
  297. return (TreeNode) body.getWidget(body.getWidgetCount() - 1);
  298. }
  299. /**
  300. * Returns a list of all root nodes in the Tree in the order they appear in
  301. * the tree.
  302. *
  303. * @return A list of all root {@link TreeNode}s.
  304. */
  305. protected List<TreeNode> getRootNodes() {
  306. ArrayList<TreeNode> rootNodes = new ArrayList<TreeNode>();
  307. for (int i = 0; i < body.getWidgetCount(); i++) {
  308. rootNodes.add((TreeNode) body.getWidget(i));
  309. }
  310. return rootNodes;
  311. }
  312. private void updateTreeRelatedDragData(VDragEvent drag) {
  313. currentMouseOverKey = findCurrentMouseOverKey(drag.getElementOver());
  314. drag.getDropDetails().put("itemIdOver", currentMouseOverKey);
  315. if (currentMouseOverKey != null) {
  316. TreeNode treeNode = keyToNode.get(currentMouseOverKey);
  317. VerticalDropLocation detail = treeNode.getDropDetail(drag
  318. .getCurrentGwtEvent());
  319. Boolean overTreeNode = null;
  320. if (treeNode != null && !treeNode.isLeaf()
  321. && detail == VerticalDropLocation.MIDDLE) {
  322. overTreeNode = true;
  323. }
  324. drag.getDropDetails().put("itemIdOverIsNode", overTreeNode);
  325. drag.getDropDetails().put("detail", detail);
  326. } else {
  327. drag.getDropDetails().put("itemIdOverIsNode", null);
  328. drag.getDropDetails().put("detail", null);
  329. }
  330. }
  331. private String findCurrentMouseOverKey(Element elementOver) {
  332. TreeNode treeNode = Util.findWidget(elementOver, TreeNode.class);
  333. return treeNode == null ? null : treeNode.key;
  334. }
  335. private void updateDropHandler(UIDL childUidl) {
  336. if (dropHandler == null) {
  337. dropHandler = new VAbstractDropHandler() {
  338. @Override
  339. public void dragEnter(VDragEvent drag) {
  340. }
  341. @Override
  342. protected void dragAccepted(final VDragEvent drag) {
  343. }
  344. @Override
  345. public void dragOver(final VDragEvent currentDrag) {
  346. final Object oldIdOver = currentDrag.getDropDetails().get(
  347. "itemIdOver");
  348. final VerticalDropLocation oldDetail = (VerticalDropLocation) currentDrag
  349. .getDropDetails().get("detail");
  350. updateTreeRelatedDragData(currentDrag);
  351. final VerticalDropLocation detail = (VerticalDropLocation) currentDrag
  352. .getDropDetails().get("detail");
  353. boolean nodeHasChanged = (currentMouseOverKey != null && currentMouseOverKey != oldIdOver)
  354. || (currentMouseOverKey == null && oldIdOver != null);
  355. boolean detailHasChanded = (detail != null && detail != oldDetail)
  356. || (detail == null && oldDetail != null);
  357. if (nodeHasChanged || detailHasChanded) {
  358. final String newKey = currentMouseOverKey;
  359. TreeNode treeNode = keyToNode.get(oldIdOver);
  360. if (treeNode != null) {
  361. // clear old styles
  362. treeNode.emphasis(null);
  363. }
  364. if (newKey != null) {
  365. validate(new VAcceptCallback() {
  366. public void accepted(VDragEvent event) {
  367. VerticalDropLocation curDetail = (VerticalDropLocation) event
  368. .getDropDetails().get("detail");
  369. if (curDetail == detail
  370. && newKey.equals(currentMouseOverKey)) {
  371. keyToNode.get(newKey).emphasis(detail);
  372. }
  373. /*
  374. * Else drag is already on a different
  375. * node-detail pair, new criteria check is
  376. * going on
  377. */
  378. }
  379. }, currentDrag);
  380. }
  381. }
  382. }
  383. @Override
  384. public void dragLeave(VDragEvent drag) {
  385. cleanUp();
  386. }
  387. private void cleanUp() {
  388. if (currentMouseOverKey != null) {
  389. keyToNode.get(currentMouseOverKey).emphasis(null);
  390. currentMouseOverKey = null;
  391. }
  392. }
  393. @Override
  394. public boolean drop(VDragEvent drag) {
  395. cleanUp();
  396. return super.drop(drag);
  397. }
  398. @Override
  399. public Paintable getPaintable() {
  400. return VTree.this;
  401. }
  402. public ApplicationConnection getApplicationConnection() {
  403. return client;
  404. }
  405. };
  406. }
  407. dropHandler.updateAcceptRules(childUidl);
  408. }
  409. private void handleUpdate(UIDL uidl) {
  410. final TreeNode rootNode = keyToNode.get(uidl
  411. .getStringAttribute("rootKey"));
  412. if (rootNode != null) {
  413. if (!rootNode.getState()) {
  414. // expanding node happened server side
  415. rootNode.setState(true, false);
  416. }
  417. rootNode.renderChildNodes(uidl.getChildIterator());
  418. }
  419. }
  420. public void setSelected(TreeNode treeNode, boolean selected) {
  421. if (selected) {
  422. if (!isMultiselect) {
  423. while (selectedIds.size() > 0) {
  424. final String id = selectedIds.iterator().next();
  425. final TreeNode oldSelection = keyToNode.get(id);
  426. if (oldSelection != null) {
  427. // can be null if the node is not visible (parent
  428. // collapsed)
  429. oldSelection.setSelected(false);
  430. }
  431. selectedIds.remove(id);
  432. }
  433. }
  434. treeNode.setSelected(true);
  435. selectedIds.add(treeNode.key);
  436. } else {
  437. if (!isNullSelectionAllowed) {
  438. if (!isMultiselect || selectedIds.size() == 1) {
  439. return;
  440. }
  441. }
  442. selectedIds.remove(treeNode.key);
  443. treeNode.setSelected(false);
  444. }
  445. sendSelectionToServer();
  446. }
  447. /**
  448. * Sends the selection to the server
  449. */
  450. private void sendSelectionToServer() {
  451. Command command = new Command() {
  452. public void execute() {
  453. client.updateVariable(paintableId, "selected",
  454. selectedIds.toArray(new String[selectedIds.size()]),
  455. immediate);
  456. selectionHasChanged = false;
  457. }
  458. };
  459. /*
  460. * Delaying the sending of the selection in webkit to ensure the
  461. * selection is always sent when the tree has focus and after click
  462. * events have been processed. This is due to the focusing
  463. * implementation in FocusImplSafari which uses timeouts when focusing
  464. * and blurring.
  465. */
  466. if (BrowserInfo.get().isWebkit()) {
  467. Scheduler.get().scheduleDeferred(command);
  468. } else {
  469. command.execute();
  470. }
  471. }
  472. /**
  473. * Is a node selected in the tree
  474. *
  475. * @param treeNode
  476. * The node to check
  477. * @return
  478. */
  479. public boolean isSelected(TreeNode treeNode) {
  480. return selectedIds.contains(treeNode.key);
  481. }
  482. public class TreeNode extends SimplePanel implements ActionOwner {
  483. public static final String CLASSNAME = "v-tree-node";
  484. public static final String CLASSNAME_FOCUSED = CLASSNAME + "-focused";
  485. public String key;
  486. private String[] actionKeys = null;
  487. private boolean childrenLoaded;
  488. private Element nodeCaptionDiv;
  489. protected Element nodeCaptionSpan;
  490. private FlowPanel childNodeContainer;
  491. private boolean open;
  492. private Icon icon;
  493. private Element ie6compatnode;
  494. private Event mouseDownEvent;
  495. private int cachedHeight = -1;
  496. private boolean focused = false;
  497. /**
  498. * Track onload events as IE6 sends two
  499. */
  500. private boolean onloadHandled = false;
  501. public TreeNode() {
  502. constructDom();
  503. sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS
  504. | Event.TOUCHEVENTS | Event.ONCONTEXTMENU);
  505. }
  506. public VerticalDropLocation getDropDetail(NativeEvent currentGwtEvent) {
  507. if (cachedHeight < 0) {
  508. /*
  509. * Height is cached to avoid flickering (drop hints may change
  510. * the reported offsetheight -> would change the drop detail)
  511. */
  512. cachedHeight = nodeCaptionDiv.getOffsetHeight();
  513. }
  514. VerticalDropLocation verticalDropLocation = DDUtil
  515. .getVerticalDropLocation(nodeCaptionDiv, cachedHeight,
  516. currentGwtEvent, 0.15);
  517. return verticalDropLocation;
  518. }
  519. protected void emphasis(VerticalDropLocation detail) {
  520. String base = "v-tree-node-drag-";
  521. UIObject.setStyleName(getElement(), base + "top",
  522. VerticalDropLocation.TOP == detail);
  523. UIObject.setStyleName(getElement(), base + "bottom",
  524. VerticalDropLocation.BOTTOM == detail);
  525. UIObject.setStyleName(getElement(), base + "center",
  526. VerticalDropLocation.MIDDLE == detail);
  527. base = "v-tree-node-caption-drag-";
  528. UIObject.setStyleName(nodeCaptionDiv, base + "top",
  529. VerticalDropLocation.TOP == detail);
  530. UIObject.setStyleName(nodeCaptionDiv, base + "bottom",
  531. VerticalDropLocation.BOTTOM == detail);
  532. UIObject.setStyleName(nodeCaptionDiv, base + "center",
  533. VerticalDropLocation.MIDDLE == detail);
  534. // also add classname to "folder node" into which the drag is
  535. // targeted
  536. TreeNode folder = null;
  537. /* Possible parent of this TreeNode will be stored here */
  538. TreeNode parentFolder = getParentNode();
  539. // TODO fix my bugs
  540. if (isLeaf()) {
  541. folder = parentFolder;
  542. // note, parent folder may be null if this is root node => no
  543. // folder target exists
  544. } else {
  545. if (detail == VerticalDropLocation.TOP) {
  546. folder = parentFolder;
  547. } else {
  548. folder = this;
  549. }
  550. // ensure we remove the dragfolder classname from the previous
  551. // folder node
  552. setDragFolderStyleName(this, false);
  553. setDragFolderStyleName(parentFolder, false);
  554. }
  555. if (folder != null) {
  556. setDragFolderStyleName(folder, detail != null);
  557. }
  558. }
  559. private TreeNode getParentNode() {
  560. Widget parent2 = getParent().getParent();
  561. if (parent2 instanceof TreeNode) {
  562. return (TreeNode) parent2;
  563. }
  564. return null;
  565. }
  566. private void setDragFolderStyleName(TreeNode folder, boolean add) {
  567. if (folder != null) {
  568. UIObject.setStyleName(folder.getElement(),
  569. "v-tree-node-dragfolder", add);
  570. UIObject.setStyleName(folder.nodeCaptionDiv,
  571. "v-tree-node-caption-dragfolder", add);
  572. }
  573. }
  574. /**
  575. * Handles mouse selection
  576. *
  577. * @param ctrl
  578. * Was the ctrl-key pressed
  579. * @param shift
  580. * Was the shift-key pressed
  581. * @return Returns true if event was handled, else false
  582. */
  583. private boolean handleClickSelection(final boolean ctrl,
  584. final boolean shift) {
  585. // always when clicking an item, focus it
  586. setFocusedNode(this, false);
  587. if (!isIE6OrOpera()) {
  588. /*
  589. * Ensure that the tree's focus element also gains focus
  590. * (TreeNodes focus is faked using FocusElementPanel in browsers
  591. * other than IE6 and Opera).
  592. */
  593. focus();
  594. }
  595. ScheduledCommand command = new ScheduledCommand() {
  596. public void execute() {
  597. if (multiSelectMode == MULTISELECT_MODE_SIMPLE
  598. || !isMultiselect) {
  599. toggleSelection();
  600. lastSelection = TreeNode.this;
  601. } else if (multiSelectMode == MULTISELECT_MODE_DEFAULT) {
  602. // Handle ctrl+click
  603. if (isMultiselect && ctrl && !shift) {
  604. toggleSelection();
  605. lastSelection = TreeNode.this;
  606. // Handle shift+click
  607. } else if (isMultiselect && !ctrl && shift) {
  608. deselectAll();
  609. selectNodeRange(lastSelection.key, key);
  610. sendSelectionToServer();
  611. // Handle ctrl+shift click
  612. } else if (isMultiselect && ctrl && shift) {
  613. selectNodeRange(lastSelection.key, key);
  614. // Handle click
  615. } else {
  616. // TODO should happen only if this alone not yet
  617. // selected,
  618. // now sending excess server calls
  619. deselectAll();
  620. toggleSelection();
  621. lastSelection = TreeNode.this;
  622. }
  623. }
  624. }
  625. };
  626. if (BrowserInfo.get().isWebkit() && !treeHasFocus) {
  627. /*
  628. * Safari may need to wait for focus. See FocusImplSafari.
  629. */
  630. // VConsole.log("Deferring click handling to let webkit gain focus...");
  631. Scheduler.get().scheduleDeferred(command);
  632. } else {
  633. command.execute();
  634. }
  635. return true;
  636. }
  637. /*
  638. * (non-Javadoc)
  639. *
  640. * @see
  641. * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
  642. * .user.client.Event)
  643. */
  644. @Override
  645. public void onBrowserEvent(Event event) {
  646. super.onBrowserEvent(event);
  647. final int type = DOM.eventGetType(event);
  648. final Element target = DOM.eventGetTarget(event);
  649. if (type == Event.ONLOAD && target == icon.getElement()) {
  650. if (onloadHandled) {
  651. return;
  652. }
  653. if (BrowserInfo.get().isIE6()) {
  654. fixWidth();
  655. }
  656. iconLoaded.trigger();
  657. onloadHandled = true;
  658. }
  659. if (disabled) {
  660. return;
  661. }
  662. if (target == nodeCaptionSpan) {
  663. client.handleTooltipEvent(event, VTree.this, key);
  664. }
  665. final boolean inCaption = target == nodeCaptionSpan
  666. || (icon != null && target == icon.getElement());
  667. if (inCaption
  668. && client
  669. .hasEventListeners(VTree.this, ITEM_CLICK_EVENT_ID)
  670. && (type == Event.ONDBLCLICK || type == Event.ONMOUSEUP)) {
  671. fireClick(event);
  672. }
  673. if (type == Event.ONCLICK) {
  674. if (getElement() == target || ie6compatnode == target) {
  675. // state change
  676. toggleState();
  677. } else if (!readonly && inCaption) {
  678. if (selectable) {
  679. // caption click = selection change && possible click
  680. // event
  681. if (handleClickSelection(
  682. event.getCtrlKey() || event.getMetaKey(),
  683. event.getShiftKey())) {
  684. event.preventDefault();
  685. }
  686. } else {
  687. // Not selectable, only focus the node.
  688. setFocusedNode(this);
  689. }
  690. }
  691. event.stopPropagation();
  692. } else if (type == Event.ONCONTEXTMENU) {
  693. showContextMenu(event);
  694. }
  695. if (dragMode != 0 || dropHandler != null) {
  696. if (type == Event.ONMOUSEDOWN || type == Event.ONTOUCHSTART) {
  697. if (nodeCaptionDiv.isOrHasChild((Node) event
  698. .getEventTarget().cast())) {
  699. if (dragMode > 0
  700. && (type == Event.ONTOUCHSTART || event
  701. .getButton() == NativeEvent.BUTTON_LEFT)) {
  702. mouseDownEvent = event; // save event for possible
  703. // dd operation
  704. if (type == Event.ONMOUSEDOWN) {
  705. event.preventDefault(); // prevent text
  706. // selection
  707. } else {
  708. /*
  709. * FIXME We prevent touch start event to be used
  710. * as a scroll start event. Note that we cannot
  711. * easily distinguish whether the user wants to
  712. * drag or scroll. The same issue is in table
  713. * that has scrollable area and has drag and
  714. * drop enable. Some kind of timer might be used
  715. * to resolve the issue.
  716. */
  717. event.stopPropagation();
  718. }
  719. }
  720. }
  721. } else if (type == Event.ONMOUSEMOVE
  722. || type == Event.ONMOUSEOUT
  723. || type == Event.ONTOUCHMOVE) {
  724. if (mouseDownEvent != null) {
  725. // start actual drag on slight move when mouse is down
  726. VTransferable t = new VTransferable();
  727. t.setDragSource(VTree.this);
  728. t.setData("itemId", key);
  729. VDragEvent drag = VDragAndDropManager.get().startDrag(
  730. t, mouseDownEvent, true);
  731. drag.createDragImage(nodeCaptionDiv, true);
  732. event.stopPropagation();
  733. mouseDownEvent = null;
  734. }
  735. } else if (type == Event.ONMOUSEUP) {
  736. mouseDownEvent = null;
  737. }
  738. if (type == Event.ONMOUSEOVER) {
  739. mouseDownEvent = null;
  740. currentMouseOverKey = key;
  741. event.stopPropagation();
  742. }
  743. } else if (type == Event.ONMOUSEDOWN
  744. && event.getButton() == NativeEvent.BUTTON_LEFT) {
  745. event.preventDefault(); // text selection
  746. }
  747. }
  748. private void fireClick(final Event evt) {
  749. /*
  750. * Ensure we have focus in tree before sending variables. Otherwise
  751. * previously modified field may contain dirty variables.
  752. */
  753. if (!treeHasFocus) {
  754. if (isIE6OrOpera()) {
  755. if (focusedNode == null) {
  756. getNodeByKey(key).setFocused(true);
  757. } else {
  758. focusedNode.setFocused(true);
  759. }
  760. } else {
  761. focus();
  762. }
  763. }
  764. final MouseEventDetails details = new MouseEventDetails(evt);
  765. ScheduledCommand command = new ScheduledCommand() {
  766. public void execute() {
  767. // Determine if we should send the event immediately to the
  768. // server. We do not want to send the event if there is a
  769. // selection event happening after this. In all other cases
  770. // we want to send it immediately.
  771. boolean sendClickEventNow = true;
  772. if (details.getButton() == NativeEvent.BUTTON_LEFT
  773. && immediate && selectable) {
  774. // Probably a selection that will cause a value change
  775. // event to be sent
  776. sendClickEventNow = false;
  777. // The exception is that user clicked on the
  778. // currently selected row and null selection is not
  779. // allowed == no selection event
  780. if (isSelected() && selectedIds.size() == 1
  781. && !isNullSelectionAllowed) {
  782. sendClickEventNow = true;
  783. }
  784. }
  785. client.updateVariable(paintableId, "clickedKey", key, false);
  786. client.updateVariable(paintableId, "clickEvent",
  787. details.toString(), sendClickEventNow);
  788. }
  789. };
  790. if (treeHasFocus) {
  791. command.execute();
  792. } else {
  793. /*
  794. * Webkits need a deferring due to FocusImplSafari uses timeout
  795. */
  796. Scheduler.get().scheduleDeferred(command);
  797. }
  798. }
  799. private void toggleSelection() {
  800. if (selectable) {
  801. VTree.this.setSelected(this, !isSelected());
  802. }
  803. }
  804. private void toggleState() {
  805. setState(!getState(), true);
  806. }
  807. protected void constructDom() {
  808. addStyleName(CLASSNAME);
  809. // workaround for a very weird IE6 issue #1245
  810. if (BrowserInfo.get().isIE6()) {
  811. ie6compatnode = DOM.createDiv();
  812. setStyleName(ie6compatnode, CLASSNAME + "-ie6compatnode");
  813. DOM.setInnerText(ie6compatnode, " ");
  814. DOM.appendChild(getElement(), ie6compatnode);
  815. DOM.sinkEvents(ie6compatnode, Event.ONCLICK);
  816. }
  817. nodeCaptionDiv = DOM.createDiv();
  818. DOM.setElementProperty(nodeCaptionDiv, "className", CLASSNAME
  819. + "-caption");
  820. Element wrapper = DOM.createDiv();
  821. nodeCaptionSpan = DOM.createSpan();
  822. DOM.sinkEvents(nodeCaptionSpan, VTooltip.TOOLTIP_EVENTS);
  823. DOM.appendChild(getElement(), nodeCaptionDiv);
  824. DOM.appendChild(nodeCaptionDiv, wrapper);
  825. DOM.appendChild(wrapper, nodeCaptionSpan);
  826. if (isIE6OrOpera()) {
  827. /*
  828. * Focus the caption div of the node to get keyboard navigation
  829. * to work without scrolling up or down when focusing a node.
  830. */
  831. nodeCaptionDiv.setTabIndex(-1);
  832. }
  833. childNodeContainer = new FlowPanel();
  834. childNodeContainer.setStyleName(CLASSNAME + "-children");
  835. setWidget(childNodeContainer);
  836. }
  837. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  838. setText(uidl.getStringAttribute("caption"));
  839. key = uidl.getStringAttribute("key");
  840. keyToNode.put(key, this);
  841. if (uidl.hasAttribute("al")) {
  842. actionKeys = uidl.getStringArrayAttribute("al");
  843. }
  844. if (uidl.getTag().equals("node")) {
  845. if (uidl.getChildCount() == 0) {
  846. childNodeContainer.setVisible(false);
  847. } else {
  848. renderChildNodes(uidl.getChildIterator());
  849. childrenLoaded = true;
  850. }
  851. } else {
  852. addStyleName(CLASSNAME + "-leaf");
  853. }
  854. if (uidl.hasAttribute("style")) {
  855. addStyleName(CLASSNAME + "-" + uidl.getStringAttribute("style"));
  856. Widget.setStyleName(nodeCaptionDiv, CLASSNAME + "-caption-"
  857. + uidl.getStringAttribute("style"), true);
  858. childNodeContainer.addStyleName(CLASSNAME + "-children-"
  859. + uidl.getStringAttribute("style"));
  860. }
  861. String description = uidl.getStringAttribute("descr");
  862. if (description != null && client != null) {
  863. // Set tooltip
  864. TooltipInfo info = new TooltipInfo(description);
  865. client.registerTooltip(VTree.this, key, info);
  866. } else {
  867. // Remove possible previous tooltip
  868. client.registerTooltip(VTree.this, key, null);
  869. }
  870. if (uidl.getBooleanAttribute("expanded") && !getState()) {
  871. setState(true, false);
  872. }
  873. if (uidl.getBooleanAttribute("selected")) {
  874. setSelected(true);
  875. // ensure that identifier is in selectedIds array (this may be a
  876. // partial update)
  877. selectedIds.add(key);
  878. }
  879. if (uidl.hasAttribute("icon")) {
  880. if (icon == null) {
  881. onloadHandled = false;
  882. icon = new Icon(client);
  883. DOM.insertBefore(DOM.getFirstChild(nodeCaptionDiv),
  884. icon.getElement(), nodeCaptionSpan);
  885. }
  886. icon.setUri(uidl.getStringAttribute("icon"));
  887. } else {
  888. if (icon != null) {
  889. DOM.removeChild(DOM.getFirstChild(nodeCaptionDiv),
  890. icon.getElement());
  891. icon = null;
  892. }
  893. }
  894. if (BrowserInfo.get().isIE6() && isAttached()) {
  895. fixWidth();
  896. }
  897. }
  898. public boolean isLeaf() {
  899. return getStyleName().contains("leaf");
  900. }
  901. private void setState(boolean state, boolean notifyServer) {
  902. if (open == state) {
  903. return;
  904. }
  905. if (state) {
  906. if (!childrenLoaded && notifyServer) {
  907. client.updateVariable(paintableId, "requestChildTree",
  908. true, false);
  909. }
  910. if (notifyServer) {
  911. client.updateVariable(paintableId, "expand",
  912. new String[] { key }, true);
  913. }
  914. addStyleName(CLASSNAME + "-expanded");
  915. childNodeContainer.setVisible(true);
  916. } else {
  917. removeStyleName(CLASSNAME + "-expanded");
  918. childNodeContainer.setVisible(false);
  919. if (notifyServer) {
  920. client.updateVariable(paintableId, "collapse",
  921. new String[] { key }, true);
  922. }
  923. }
  924. open = state;
  925. if (!rendering) {
  926. Util.notifyParentOfSizeChange(VTree.this, false);
  927. }
  928. }
  929. private boolean getState() {
  930. return open;
  931. }
  932. private void setText(String text) {
  933. DOM.setInnerText(nodeCaptionSpan, text);
  934. }
  935. private void renderChildNodes(Iterator<?> i) {
  936. childNodeContainer.clear();
  937. childNodeContainer.setVisible(true);
  938. while (i.hasNext()) {
  939. final UIDL childUidl = (UIDL) i.next();
  940. // actions are in bit weird place, don't mix them with children,
  941. // but current node's actions
  942. if ("actions".equals(childUidl.getTag())) {
  943. updateActionMap(childUidl);
  944. continue;
  945. }
  946. final TreeNode childTree = new TreeNode();
  947. if (ie6compatnode != null) {
  948. childNodeContainer.add(childTree);
  949. }
  950. childTree.updateFromUIDL(childUidl, client);
  951. if (ie6compatnode == null) {
  952. childNodeContainer.add(childTree);
  953. }
  954. if (!i.hasNext()) {
  955. childTree
  956. .addStyleDependentName(childTree.isLeaf() ? "leaf-last"
  957. : "last");
  958. childTree.childNodeContainer.addStyleDependentName("last");
  959. }
  960. }
  961. childrenLoaded = true;
  962. }
  963. public boolean isChildrenLoaded() {
  964. return childrenLoaded;
  965. }
  966. /**
  967. * Returns the children of the node
  968. *
  969. * @return A set of tree nodes
  970. */
  971. public List<TreeNode> getChildren() {
  972. List<TreeNode> nodes = new LinkedList<TreeNode>();
  973. if (!isLeaf() && isChildrenLoaded()) {
  974. Iterator<Widget> iter = childNodeContainer.iterator();
  975. while (iter.hasNext()) {
  976. TreeNode node = (TreeNode) iter.next();
  977. nodes.add(node);
  978. }
  979. }
  980. return nodes;
  981. }
  982. public Action[] getActions() {
  983. if (actionKeys == null) {
  984. return new Action[] {};
  985. }
  986. final Action[] actions = new Action[actionKeys.length];
  987. for (int i = 0; i < actions.length; i++) {
  988. final String actionKey = actionKeys[i];
  989. final TreeAction a = new TreeAction(this, String.valueOf(key),
  990. actionKey);
  991. a.setCaption(getActionCaption(actionKey));
  992. a.setIconUrl(getActionIcon(actionKey));
  993. actions[i] = a;
  994. }
  995. return actions;
  996. }
  997. public ApplicationConnection getClient() {
  998. return client;
  999. }
  1000. public String getPaintableId() {
  1001. return paintableId;
  1002. }
  1003. /**
  1004. * Adds/removes Vaadin specific style name. This method ought to be
  1005. * called only from VTree.
  1006. *
  1007. * @param selected
  1008. */
  1009. protected void setSelected(boolean selected) {
  1010. // add style name to caption dom structure only, not to subtree
  1011. setStyleName(nodeCaptionDiv, "v-tree-node-selected", selected);
  1012. }
  1013. protected boolean isSelected() {
  1014. return VTree.this.isSelected(this);
  1015. }
  1016. /**
  1017. * Travels up the hierarchy looking for this node
  1018. *
  1019. * @param child
  1020. * The child which grandparent this is or is not
  1021. * @return True if this is a grandparent of the child node
  1022. */
  1023. public boolean isGrandParentOf(TreeNode child) {
  1024. TreeNode currentNode = child;
  1025. boolean isGrandParent = false;
  1026. while (currentNode != null) {
  1027. currentNode = currentNode.getParentNode();
  1028. if (currentNode == this) {
  1029. isGrandParent = true;
  1030. break;
  1031. }
  1032. }
  1033. return isGrandParent;
  1034. }
  1035. public boolean isSibling(TreeNode node) {
  1036. return node.getParentNode() == getParentNode();
  1037. }
  1038. public void showContextMenu(Event event) {
  1039. if (!readonly && !disabled) {
  1040. if (actionKeys != null) {
  1041. int left = event.getClientX();
  1042. int top = event.getClientY();
  1043. top += Window.getScrollTop();
  1044. left += Window.getScrollLeft();
  1045. client.getContextMenu().showAt(this, left, top);
  1046. }
  1047. event.stopPropagation();
  1048. event.preventDefault();
  1049. }
  1050. }
  1051. /*
  1052. * We need to fix the width of TreeNodes so that the float in
  1053. * ie6compatNode does not wrap (see ticket #1245)
  1054. */
  1055. private void fixWidth() {
  1056. nodeCaptionDiv.getStyle().setProperty("styleFloat", "left");
  1057. nodeCaptionDiv.getStyle().setProperty("display", "inline");
  1058. nodeCaptionDiv.getStyle().setProperty("marginLeft", "0");
  1059. final int captionWidth = ie6compatnode.getOffsetWidth()
  1060. + nodeCaptionDiv.getOffsetWidth();
  1061. setWidth(captionWidth + "px");
  1062. }
  1063. /*
  1064. * (non-Javadoc)
  1065. *
  1066. * @see com.google.gwt.user.client.ui.Widget#onAttach()
  1067. */
  1068. @Override
  1069. public void onAttach() {
  1070. super.onAttach();
  1071. if (ie6compatnode != null) {
  1072. fixWidth();
  1073. }
  1074. }
  1075. /*
  1076. * (non-Javadoc)
  1077. *
  1078. * @see com.google.gwt.user.client.ui.Widget#onDetach()
  1079. */
  1080. @Override
  1081. protected void onDetach() {
  1082. super.onDetach();
  1083. client.getContextMenu().ensureHidden(this);
  1084. }
  1085. /*
  1086. * (non-Javadoc)
  1087. *
  1088. * @see com.google.gwt.user.client.ui.UIObject#toString()
  1089. */
  1090. @Override
  1091. public String toString() {
  1092. return nodeCaptionSpan.getInnerText();
  1093. }
  1094. /**
  1095. * Is the node focused?
  1096. *
  1097. * @param focused
  1098. * True if focused, false if not
  1099. */
  1100. public void setFocused(boolean focused) {
  1101. if (!this.focused && focused) {
  1102. nodeCaptionDiv.addClassName(CLASSNAME_FOCUSED);
  1103. if (BrowserInfo.get().isIE6()) {
  1104. ie6compatnode.addClassName(CLASSNAME_FOCUSED);
  1105. }
  1106. this.focused = focused;
  1107. if (isIE6OrOpera()) {
  1108. nodeCaptionDiv.focus();
  1109. }
  1110. treeHasFocus = true;
  1111. } else if (this.focused && !focused) {
  1112. nodeCaptionDiv.removeClassName(CLASSNAME_FOCUSED);
  1113. if (BrowserInfo.get().isIE6()) {
  1114. ie6compatnode.removeClassName(CLASSNAME_FOCUSED);
  1115. }
  1116. this.focused = focused;
  1117. treeHasFocus = false;
  1118. }
  1119. }
  1120. /**
  1121. * Scrolls the caption into view
  1122. */
  1123. public void scrollIntoView() {
  1124. Util.scrollIntoViewVertically(nodeCaptionDiv);
  1125. }
  1126. }
  1127. public VDropHandler getDropHandler() {
  1128. return dropHandler;
  1129. }
  1130. public TreeNode getNodeByKey(String key) {
  1131. return keyToNode.get(key);
  1132. }
  1133. /**
  1134. * Deselects all items in the tree
  1135. */
  1136. public void deselectAll() {
  1137. for (String key : selectedIds) {
  1138. TreeNode node = keyToNode.get(key);
  1139. if (node != null) {
  1140. node.setSelected(false);
  1141. }
  1142. }
  1143. selectedIds.clear();
  1144. selectionHasChanged = true;
  1145. }
  1146. /**
  1147. * Selects a range of nodes
  1148. *
  1149. * @param startNodeKey
  1150. * The start node key
  1151. * @param endNodeKey
  1152. * The end node key
  1153. */
  1154. private void selectNodeRange(String startNodeKey, String endNodeKey) {
  1155. TreeNode startNode = keyToNode.get(startNodeKey);
  1156. TreeNode endNode = keyToNode.get(endNodeKey);
  1157. // The nodes have the same parent
  1158. if (startNode.getParent() == endNode.getParent()) {
  1159. doSiblingSelection(startNode, endNode);
  1160. // The start node is a grandparent of the end node
  1161. } else if (startNode.isGrandParentOf(endNode)) {
  1162. doRelationSelection(startNode, endNode);
  1163. // The end node is a grandparent of the start node
  1164. } else if (endNode.isGrandParentOf(startNode)) {
  1165. doRelationSelection(endNode, startNode);
  1166. } else {
  1167. doNoRelationSelection(startNode, endNode);
  1168. }
  1169. }
  1170. /**
  1171. * Selects a node and deselect all other nodes
  1172. *
  1173. * @param node
  1174. * The node to select
  1175. */
  1176. private void selectNode(TreeNode node, boolean deselectPrevious) {
  1177. if (deselectPrevious) {
  1178. deselectAll();
  1179. }
  1180. if (node != null) {
  1181. node.setSelected(true);
  1182. selectedIds.add(node.key);
  1183. lastSelection = node;
  1184. }
  1185. selectionHasChanged = true;
  1186. }
  1187. /**
  1188. * Deselects a node
  1189. *
  1190. * @param node
  1191. * The node to deselect
  1192. */
  1193. private void deselectNode(TreeNode node) {
  1194. node.setSelected(false);
  1195. selectedIds.remove(node.key);
  1196. selectionHasChanged = true;
  1197. }
  1198. /**
  1199. * Selects all the open children to a node
  1200. *
  1201. * @param node
  1202. * The parent node
  1203. */
  1204. private void selectAllChildren(TreeNode node, boolean includeRootNode) {
  1205. if (includeRootNode) {
  1206. node.setSelected(true);
  1207. selectedIds.add(node.key);
  1208. }
  1209. for (TreeNode child : node.getChildren()) {
  1210. if (!child.isLeaf() && child.getState()) {
  1211. selectAllChildren(child, true);
  1212. } else {
  1213. child.setSelected(true);
  1214. selectedIds.add(child.key);
  1215. }
  1216. }
  1217. selectionHasChanged = true;
  1218. }
  1219. /**
  1220. * Selects all children until a stop child is reached
  1221. *
  1222. * @param root
  1223. * The root not to start from
  1224. * @param stopNode
  1225. * The node to finish with
  1226. * @param includeRootNode
  1227. * Should the root node be selected
  1228. * @param includeStopNode
  1229. * Should the stop node be selected
  1230. *
  1231. * @return Returns false if the stop child was found, else true if all
  1232. * children was selected
  1233. */
  1234. private boolean selectAllChildrenUntil(TreeNode root, TreeNode stopNode,
  1235. boolean includeRootNode, boolean includeStopNode) {
  1236. if (includeRootNode) {
  1237. root.setSelected(true);
  1238. selectedIds.add(root.key);
  1239. }
  1240. if (root.getState() && root != stopNode) {
  1241. for (TreeNode child : root.getChildren()) {
  1242. if (!child.isLeaf() && child.getState() && child != stopNode) {
  1243. if (!selectAllChildrenUntil(child, stopNode, true,
  1244. includeStopNode)) {
  1245. return false;
  1246. }
  1247. } else if (child == stopNode) {
  1248. if (includeStopNode) {
  1249. child.setSelected(true);
  1250. selectedIds.add(child.key);
  1251. }
  1252. return false;
  1253. } else if (child.isLeaf()) {
  1254. child.setSelected(true);
  1255. selectedIds.add(child.key);
  1256. }
  1257. }
  1258. }
  1259. selectionHasChanged = true;
  1260. return true;
  1261. }
  1262. /**
  1263. * Select a range between two nodes which have no relation to each other
  1264. *
  1265. * @param startNode
  1266. * The start node to start the selection from
  1267. * @param endNode
  1268. * The end node to end the selection to
  1269. */
  1270. private void doNoRelationSelection(TreeNode startNode, TreeNode endNode) {
  1271. TreeNode commonParent = getCommonGrandParent(startNode, endNode);
  1272. TreeNode startBranch = null, endBranch = null;
  1273. // Find the children of the common parent
  1274. List<TreeNode> children;
  1275. if (commonParent != null) {
  1276. children = commonParent.getChildren();
  1277. } else {
  1278. children = getRootNodes();
  1279. }
  1280. // Find the start and end branches
  1281. for (TreeNode node : children) {
  1282. if (nodeIsInBranch(startNode, node)) {
  1283. startBranch = node;
  1284. }
  1285. if (nodeIsInBranch(endNode, node)) {
  1286. endBranch = node;
  1287. }
  1288. }
  1289. // Swap nodes if necessary
  1290. if (children.indexOf(startBranch) > children.indexOf(endBranch)) {
  1291. TreeNode temp = startBranch;
  1292. startBranch = endBranch;
  1293. endBranch = temp;
  1294. temp = startNode;
  1295. startNode = endNode;
  1296. endNode = temp;
  1297. }
  1298. // Select all children under the start node
  1299. selectAllChildren(startNode, true);
  1300. TreeNode startParent = startNode.getParentNode();
  1301. TreeNode currentNode = startNode;
  1302. while (startParent != null && startParent != commonParent) {
  1303. List<TreeNode> startChildren = startParent.getChildren();
  1304. for (int i = startChildren.indexOf(currentNode) + 1; i < startChildren
  1305. .size(); i++) {
  1306. selectAllChildren(startChildren.get(i), true);
  1307. }
  1308. currentNode = startParent;
  1309. startParent = startParent.getParentNode();
  1310. }
  1311. // Select nodes until the end node is reached
  1312. for (int i = children.indexOf(startBranch) + 1; i <= children
  1313. .indexOf(endBranch); i++) {
  1314. selectAllChildrenUntil(children.get(i), endNode, true, true);
  1315. }
  1316. // Ensure end node was selected
  1317. endNode.setSelected(true);
  1318. selectedIds.add(endNode.key);
  1319. selectionHasChanged = true;
  1320. }
  1321. /**
  1322. * Examines the children of the branch node and returns true if a node is in
  1323. * that branch
  1324. *
  1325. * @param node
  1326. * The node to search for
  1327. * @param branch
  1328. * The branch to search in
  1329. * @return True if found, false if not found
  1330. */
  1331. private boolean nodeIsInBranch(TreeNode node, TreeNode branch) {
  1332. if (node == branch) {
  1333. return true;
  1334. }
  1335. for (TreeNode child : branch.getChildren()) {
  1336. if (child == node) {
  1337. return true;
  1338. }
  1339. if (!child.isLeaf() && child.getState()) {
  1340. if (nodeIsInBranch(node, child)) {
  1341. return true;
  1342. }
  1343. }
  1344. }
  1345. return false;
  1346. }
  1347. /**
  1348. * Selects a range of items which are in direct relation with each other.<br/>
  1349. * NOTE: The start node <b>MUST</b> be before the end node!
  1350. *
  1351. * @param startNode
  1352. *
  1353. * @param endNode
  1354. */
  1355. private void doRelationSelection(TreeNode startNode, TreeNode endNode) {
  1356. TreeNode currentNode = endNode;
  1357. while (currentNode != startNode) {
  1358. currentNode.setSelected(true);
  1359. selectedIds.add(currentNode.key);
  1360. // Traverse children above the selection
  1361. List<TreeNode> subChildren = currentNode.getParentNode()
  1362. .getChildren();
  1363. if (subChildren.size() > 1) {
  1364. selectNodeRange(subChildren.iterator().next().key,
  1365. currentNode.key);
  1366. } else if (subChildren.size() == 1) {
  1367. TreeNode n = subChildren.get(0);
  1368. n.setSelected(true);
  1369. selectedIds.add(n.key);
  1370. }
  1371. currentNode = currentNode.getParentNode();
  1372. }
  1373. startNode.setSelected(true);
  1374. selectedIds.add(startNode.key);
  1375. selectionHasChanged = true;
  1376. }
  1377. /**
  1378. * Selects a range of items which have the same parent.
  1379. *
  1380. * @param startNode
  1381. * The start node
  1382. * @param endNode
  1383. * The end node
  1384. */
  1385. private void doSiblingSelection(TreeNode startNode, TreeNode endNode) {
  1386. TreeNode parent = startNode.getParentNode();
  1387. List<TreeNode> children;
  1388. if (parent == null) {
  1389. // Topmost parent
  1390. children = getRootNodes();
  1391. } else {
  1392. children = parent.getChildren();
  1393. }
  1394. // Swap start and end point if needed
  1395. if (children.indexOf(startNode) > children.indexOf(endNode)) {
  1396. TreeNode temp = startNode;
  1397. startNode = endNode;
  1398. endNode = temp;
  1399. }
  1400. Iterator<TreeNode> childIter = children.iterator();
  1401. boolean startFound = false;
  1402. while (childIter.hasNext()) {
  1403. TreeNode node = childIter.next();
  1404. if (node == startNode) {
  1405. startFound = true;
  1406. }
  1407. if (startFound && node != endNode && node.getState()) {
  1408. selectAllChildren(node, true);
  1409. } else if (startFound && node != endNode) {
  1410. node.setSelected(true);
  1411. selectedIds.add(node.key);
  1412. }
  1413. if (node == endNode) {
  1414. node.setSelected(true);
  1415. selectedIds.add(node.key);
  1416. break;
  1417. }
  1418. }
  1419. selectionHasChanged = true;
  1420. }
  1421. /**
  1422. * Returns the first common parent of two nodes
  1423. *
  1424. * @param node1
  1425. * The first node
  1426. * @param node2
  1427. * The second node
  1428. * @return The common parent or null
  1429. */
  1430. public TreeNode getCommonGrandParent(TreeNode node1, TreeNode node2) {
  1431. // If either one does not have a grand parent then return null
  1432. if (node1.getParentNode() == null || node2.getParentNode() == null) {
  1433. return null;
  1434. }
  1435. // If the nodes are parents of each other then return null
  1436. if (node1.isGrandParentOf(node2) || node2.isGrandParentOf(node1)) {
  1437. return null;
  1438. }
  1439. // Get parents of node1
  1440. List<TreeNode> parents1 = new ArrayList<TreeNode>();
  1441. TreeNode parent1 = node1.getParentNode();
  1442. while (parent1 != null) {
  1443. parents1.add(parent1);
  1444. parent1 = parent1.getParentNode();
  1445. }
  1446. // Get parents of node2
  1447. List<TreeNode> parents2 = new ArrayList<TreeNode>();
  1448. TreeNode parent2 = node2.getParentNode();
  1449. while (parent2 != null) {
  1450. parents2.add(parent2);
  1451. parent2 = parent2.getParentNode();
  1452. }
  1453. // Search the parents for the first common parent
  1454. for (int i = 0; i < parents1.size(); i++) {
  1455. parent1 = parents1.get(i);
  1456. for (int j = 0; j < parents2.size(); j++) {
  1457. parent2 = parents2.get(j);
  1458. if (parent1 == parent2) {
  1459. return parent1;
  1460. }
  1461. }
  1462. }
  1463. return null;
  1464. }
  1465. /**
  1466. * Sets the node currently in focus
  1467. *
  1468. * @param node
  1469. * The node to focus or null to remove the focus completely
  1470. * @param scrollIntoView
  1471. * Scroll the node into view
  1472. */
  1473. public void setFocusedNode(TreeNode node, boolean scrollIntoView) {
  1474. // Unfocus previously focused node
  1475. if (focusedNode != null) {
  1476. focusedNode.setFocused(false);
  1477. }
  1478. if (node != null) {
  1479. node.setFocused(true);
  1480. }
  1481. focusedNode = node;
  1482. if (node != null && scrollIntoView) {
  1483. /*
  1484. * Delay scrolling the focused node into view if we are still
  1485. * rendering. #5396
  1486. */
  1487. if (!rendering) {
  1488. node.scrollIntoView();
  1489. } else {
  1490. Scheduler.get().scheduleDeferred(new Command() {
  1491. public void execute() {
  1492. focusedNode.scrollIntoView();
  1493. }
  1494. });
  1495. }
  1496. }
  1497. }
  1498. /**
  1499. * Focuses a node and scrolls it into view
  1500. *
  1501. * @param node
  1502. * The node to focus
  1503. */
  1504. public void setFocusedNode(TreeNode node) {
  1505. setFocusedNode(node, true);
  1506. }
  1507. /*
  1508. * (non-Javadoc)
  1509. *
  1510. * @see
  1511. * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
  1512. * .dom.client.FocusEvent)
  1513. */
  1514. public void onFocus(FocusEvent event) {
  1515. treeHasFocus = true;
  1516. // If no node has focus, focus the first item in the tree
  1517. if (focusedNode == null && lastSelection == null && selectable) {
  1518. setFocusedNode(getFirstRootNode(), false);
  1519. } else if (focusedNode != null && selectable) {
  1520. setFocusedNode(focusedNode, false);
  1521. } else if (lastSelection != null && selectable) {
  1522. setFocusedNode(lastSelection, false);
  1523. }
  1524. }
  1525. /*
  1526. * (non-Javadoc)
  1527. *
  1528. * @see
  1529. * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event
  1530. * .dom.client.BlurEvent)
  1531. */
  1532. public void onBlur(BlurEvent event) {
  1533. treeHasFocus = false;
  1534. if (focusedNode != null) {
  1535. focusedNode.setFocused(false);
  1536. }
  1537. }
  1538. /*
  1539. * (non-Javadoc)
  1540. *
  1541. * @see
  1542. * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
  1543. * .gwt.event.dom.client.KeyPressEvent)
  1544. */
  1545. public void onKeyPress(KeyPressEvent event) {
  1546. NativeEvent nativeEvent = event.getNativeEvent();
  1547. int keyCode = nativeEvent.getKeyCode();
  1548. if (keyCode == 0 && nativeEvent.getCharCode() == ' ') {
  1549. // Provide a keyCode for space to be compatible with FireFox
  1550. // keypress event
  1551. keyCode = CHARCODE_SPACE;
  1552. }
  1553. if (handleKeyNavigation(keyCode,
  1554. event.isControlKeyDown() || event.isMetaKeyDown(),
  1555. event.isShiftKeyDown())) {
  1556. event.preventDefault();
  1557. event.stopPropagation();
  1558. }
  1559. }
  1560. /*
  1561. * (non-Javadoc)
  1562. *
  1563. * @see
  1564. * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
  1565. * .event.dom.client.KeyDownEvent)
  1566. */
  1567. public void onKeyDown(KeyDownEvent event) {
  1568. if (handleKeyNavigation(event.getNativeEvent().getKeyCode(),
  1569. event.isControlKeyDown() || event.isMetaKeyDown(),
  1570. event.isShiftKeyDown())) {
  1571. event.preventDefault();
  1572. event.stopPropagation();
  1573. }
  1574. }
  1575. /**
  1576. * Handles the keyboard navigation
  1577. *
  1578. * @param keycode
  1579. * The keycode of the pressed key
  1580. * @param ctrl
  1581. * Was ctrl pressed
  1582. * @param shift
  1583. * Was shift pressed
  1584. * @return Returns true if the key was handled, else false
  1585. */
  1586. protected boolean handleKeyNavigation(int keycode, boolean ctrl,
  1587. boolean shift) {
  1588. // Navigate down
  1589. if (keycode == getNavigationDownKey()) {
  1590. TreeNode node = null;
  1591. // If node is open and has children then move in to the children
  1592. if (!focusedNode.isLeaf() && focusedNode.getState()
  1593. && focusedNode.getChildren().size() > 0) {
  1594. node = focusedNode.getChildren().get(0);
  1595. }
  1596. // Else move down to the next sibling
  1597. else {
  1598. node = getNextSibling(focusedNode);
  1599. if (node == null) {
  1600. // Else jump to the parent and try to select the next
  1601. // sibling there
  1602. TreeNode current = focusedNode;
  1603. while (node == null && current.getParentNode() != null) {
  1604. node = getNextSibling(current.getParentNode());
  1605. current = current.getParentNode();
  1606. }
  1607. }
  1608. }
  1609. if (node != null) {
  1610. setFocusedNode(node);
  1611. if (selectable) {
  1612. if (!ctrl && !shift) {
  1613. selectNode(node, true);
  1614. } else if (shift && isMultiselect) {
  1615. deselectAll();
  1616. selectNodeRange(lastSelection.key, node.key);
  1617. } else if (shift) {
  1618. selectNode(node, true);
  1619. }
  1620. }
  1621. }
  1622. return true;
  1623. }
  1624. // Navigate up
  1625. if (keycode == getNavigationUpKey()) {
  1626. TreeNode prev = getPreviousSibling(focusedNode);
  1627. TreeNode node = null;
  1628. if (prev != null) {
  1629. node = getLastVisibleChildInTree(prev);
  1630. } else if (focusedNode.getParentNode() != null) {
  1631. node = focusedNode.getParentNode();
  1632. }
  1633. if (node != null) {
  1634. setFocusedNode(node);
  1635. if (selectable) {
  1636. if (!ctrl && !shift) {
  1637. selectNode(node, true);
  1638. } else if (shift && isMultiselect) {
  1639. deselectAll();
  1640. selectNodeRange(lastSelection.key, node.key);
  1641. } else if (shift) {
  1642. selectNode(node, true);
  1643. }
  1644. }
  1645. }
  1646. return true;
  1647. }
  1648. // Navigate left (close branch)
  1649. if (keycode == getNavigationLeftKey()) {
  1650. if (!focusedNode.isLeaf() && focusedNode.getState()) {
  1651. focusedNode.setState(false, true);
  1652. } else if (focusedNode.getParentNode() != null
  1653. && (focusedNode.isLeaf() || !focusedNode.getState())) {
  1654. if (ctrl || !selectable) {
  1655. setFocusedNode(focusedNode.getParentNode());
  1656. } else if (shift) {
  1657. doRelationSelection(focusedNode.getParentNode(),
  1658. focusedNode);
  1659. setFocusedNode(focusedNode.getParentNode());
  1660. } else {
  1661. focusAndSelectNode(focusedNode.getParentNode());
  1662. }
  1663. }
  1664. return true;
  1665. }
  1666. // Navigate right (open branch)
  1667. if (keycode == getNavigationRightKey()) {
  1668. if (!focusedNode.isLeaf() && !focusedNode.getState()) {
  1669. focusedNode.setState(true, true);
  1670. } else if (!focusedNode.isLeaf()) {
  1671. if (ctrl || !selectable) {
  1672. setFocusedNode(focusedNode.getChildren().get(0));
  1673. } else if (shift) {
  1674. setSelected(focusedNode, true);
  1675. setFocusedNode(focusedNode.getChildren().get(0));
  1676. setSelected(focusedNode, true);
  1677. } else {
  1678. focusAndSelectNode(focusedNode.getChildren().get(0));
  1679. }
  1680. }
  1681. return true;
  1682. }
  1683. // Selection
  1684. if (keycode == getNavigationSelectKey()) {
  1685. if (!focusedNode.isSelected()) {
  1686. selectNode(
  1687. focusedNode,
  1688. (!isMultiselect || multiSelectMode == MULTISELECT_MODE_SIMPLE)
  1689. && selectable);
  1690. } else {
  1691. deselectNode(focusedNode);
  1692. }
  1693. return true;
  1694. }
  1695. // Home selection
  1696. if (keycode == getNavigationStartKey()) {
  1697. TreeNode node = getFirstRootNode();
  1698. if (ctrl || !selectable) {
  1699. setFocusedNode(node);
  1700. } else if (shift) {
  1701. deselectAll();
  1702. selectNodeRange(focusedNode.key, node.key);
  1703. } else {
  1704. selectNode(node, true);
  1705. }
  1706. sendSelectionToServer();
  1707. return true;
  1708. }
  1709. // End selection
  1710. if (keycode == getNavigationEndKey()) {
  1711. TreeNode lastNode = getLastRootNode();
  1712. TreeNode node = getLastVisibleChildInTree(lastNode);
  1713. if (ctrl || !selectable) {
  1714. setFocusedNode(node);
  1715. } else if (shift) {
  1716. deselectAll();
  1717. selectNodeRange(focusedNode.key, node.key);
  1718. } else {
  1719. selectNode(node, true);
  1720. }
  1721. sendSelectionToServer();
  1722. return true;
  1723. }
  1724. return false;
  1725. }
  1726. private void focusAndSelectNode(TreeNode node) {
  1727. /*
  1728. * Keyboard navigation doesn't work reliably if the tree is in
  1729. * multiselect mode as well as isNullSelectionAllowed = false. It first
  1730. * tries to deselect the old focused node, which fails since there must
  1731. * be at least one selection. After this the newly focused node is
  1732. * selected and we've ended up with two selected nodes even though we
  1733. * only navigated with the arrow keys.
  1734. *
  1735. * Because of this, we first select the next node and later de-select
  1736. * the old one.
  1737. */
  1738. TreeNode oldFocusedNode = focusedNode;
  1739. setFocusedNode(node);
  1740. setSelected(focusedNode, true);
  1741. setSelected(oldFocusedNode, false);
  1742. }
  1743. /**
  1744. * Traverses the tree to the bottom most child
  1745. *
  1746. * @param root
  1747. * The root of the tree
  1748. * @return The bottom most child
  1749. */
  1750. private TreeNode getLastVisibleChildInTree(TreeNode root) {
  1751. if (root.isLeaf() || !root.getState() || root.getChildren().size() == 0) {
  1752. return root;
  1753. }
  1754. List<TreeNode> children = root.getChildren();
  1755. return getLastVisibleChildInTree(children.get(children.size() - 1));
  1756. }
  1757. /**
  1758. * Gets the next sibling in the tree
  1759. *
  1760. * @param node
  1761. * The node to get the sibling for
  1762. * @return The sibling node or null if the node is the last sibling
  1763. */
  1764. private TreeNode getNextSibling(TreeNode node) {
  1765. TreeNode parent = node.getParentNode();
  1766. List<TreeNode> children;
  1767. if (parent == null) {
  1768. children = getRootNodes();
  1769. } else {
  1770. children = parent.getChildren();
  1771. }
  1772. int idx = children.indexOf(node);
  1773. if (idx < children.size() - 1) {
  1774. return children.get(idx + 1);
  1775. }
  1776. return null;
  1777. }
  1778. /**
  1779. * Returns the previous sibling in the tree
  1780. *
  1781. * @param node
  1782. * The node to get the sibling for
  1783. * @return The sibling node or null if the node is the first sibling
  1784. */
  1785. private TreeNode getPreviousSibling(TreeNode node) {
  1786. TreeNode parent = node.getParentNode();
  1787. List<TreeNode> children;
  1788. if (parent == null) {
  1789. children = getRootNodes();
  1790. } else {
  1791. children = parent.getChildren();
  1792. }
  1793. int idx = children.indexOf(node);
  1794. if (idx > 0) {
  1795. return children.get(idx - 1);
  1796. }
  1797. return null;
  1798. }
  1799. /**
  1800. * Add this to the element mouse down event by using element.setPropertyJSO
  1801. * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again
  1802. * when the mouse is depressed in the mouse up event.
  1803. *
  1804. * @return Returns the JSO preventing text selection
  1805. */
  1806. private native JavaScriptObject applyDisableTextSelectionIEHack()
  1807. /*-{
  1808. return function(){ return false; };
  1809. }-*/;
  1810. /**
  1811. * Get the key that moves the selection head upwards. By default it is the
  1812. * up arrow key but by overriding this you can change the key to whatever
  1813. * you want.
  1814. *
  1815. * @return The keycode of the key
  1816. */
  1817. protected int getNavigationUpKey() {
  1818. return KeyCodes.KEY_UP;
  1819. }
  1820. /**
  1821. * Get the key that moves the selection head downwards. By default it is the
  1822. * down arrow key but by overriding this you can change the key to whatever
  1823. * you want.
  1824. *
  1825. * @return The keycode of the key
  1826. */
  1827. protected int getNavigationDownKey() {
  1828. return KeyCodes.KEY_DOWN;
  1829. }
  1830. /**
  1831. * Get the key that scrolls to the left in the table. By default it is the
  1832. * left arrow key but by overriding this you can change the key to whatever
  1833. * you want.
  1834. *
  1835. * @return The keycode of the key
  1836. */
  1837. protected int getNavigationLeftKey() {
  1838. return KeyCodes.KEY_LEFT;
  1839. }
  1840. /**
  1841. * Get the key that scroll to the right on the table. By default it is the
  1842. * right arrow key but by overriding this you can change the key to whatever
  1843. * you want.
  1844. *
  1845. * @return The keycode of the key
  1846. */
  1847. protected int getNavigationRightKey() {
  1848. return KeyCodes.KEY_RIGHT;
  1849. }
  1850. /**
  1851. * Get the key that selects an item in the table. By default it is the space
  1852. * bar key but by overriding this you can change the key to whatever you
  1853. * want.
  1854. *
  1855. * @return
  1856. */
  1857. protected int getNavigationSelectKey() {
  1858. return CHARCODE_SPACE;
  1859. }
  1860. /**
  1861. * Get the key the moves the selection one page up in the table. By default
  1862. * this is the Page Up key but by overriding this you can change the key to
  1863. * whatever you want.
  1864. *
  1865. * @return
  1866. */
  1867. protected int getNavigationPageUpKey() {
  1868. return KeyCodes.KEY_PAGEUP;
  1869. }
  1870. /**
  1871. * Get the key the moves the selection one page down in the table. By
  1872. * default this is the Page Down key but by overriding this you can change
  1873. * the key to whatever you want.
  1874. *
  1875. * @return
  1876. */
  1877. protected int getNavigationPageDownKey() {
  1878. return KeyCodes.KEY_PAGEDOWN;
  1879. }
  1880. /**
  1881. * Get the key the moves the selection to the beginning of the table. By
  1882. * default this is the Home key but by overriding this you can change the
  1883. * key to whatever you want.
  1884. *
  1885. * @return
  1886. */
  1887. protected int getNavigationStartKey() {
  1888. return KeyCodes.KEY_HOME;
  1889. }
  1890. /**
  1891. * Get the key the moves the selection to the end of the table. By default
  1892. * this is the End key but by overriding this you can change the key to
  1893. * whatever you want.
  1894. *
  1895. * @return
  1896. */
  1897. protected int getNavigationEndKey() {
  1898. return KeyCodes.KEY_END;
  1899. }
  1900. private final String SUBPART_NODE_PREFIX = "n";
  1901. private final String EXPAND_IDENTIFIER = "expand";
  1902. /*
  1903. * In webkit, focus may have been requested for this component but not yet
  1904. * gained. Use this to trac if tree has gained the focus on webkit. See
  1905. * FocusImplSafari and #6373
  1906. */
  1907. private boolean treeHasFocus;
  1908. /*
  1909. * (non-Javadoc)
  1910. *
  1911. * @see
  1912. * com.vaadin.terminal.gwt.client.ui.SubPartAware#getSubPartElement(java
  1913. * .lang.String)
  1914. */
  1915. public Element getSubPartElement(String subPart) {
  1916. if (subPart.startsWith(SUBPART_NODE_PREFIX + "[")) {
  1917. boolean expandCollapse = false;
  1918. // Node
  1919. String[] nodes = subPart.split("/");
  1920. TreeNode treeNode = null;
  1921. try {
  1922. for (String node : nodes) {
  1923. if (node.startsWith(SUBPART_NODE_PREFIX)) {
  1924. // skip SUBPART_NODE_PREFIX"["
  1925. node = node.substring(SUBPART_NODE_PREFIX.length() + 1);
  1926. // skip "]"
  1927. node = node.substring(0, node.length() - 1);
  1928. int position = Integer.parseInt(node);
  1929. if (treeNode == null) {
  1930. treeNode = getRootNodes().get(position);
  1931. } else {
  1932. treeNode = treeNode.getChildren().get(position);
  1933. }
  1934. } else if (node.startsWith(EXPAND_IDENTIFIER)) {
  1935. expandCollapse = true;
  1936. }
  1937. }
  1938. if (expandCollapse) {
  1939. if (treeNode.ie6compatnode != null) {
  1940. return treeNode.ie6compatnode;
  1941. } else {
  1942. return treeNode.getElement();
  1943. }
  1944. } else {
  1945. return treeNode.nodeCaptionSpan;
  1946. }
  1947. } catch (Exception e) {
  1948. // Invalid locator string or node could not be found
  1949. return null;
  1950. }
  1951. }
  1952. return null;
  1953. }
  1954. /*
  1955. * (non-Javadoc)
  1956. *
  1957. * @see
  1958. * com.vaadin.terminal.gwt.client.ui.SubPartAware#getSubPartName(com.google
  1959. * .gwt.user.client.Element)
  1960. */
  1961. public String getSubPartName(Element subElement) {
  1962. // Supported identifiers:
  1963. //
  1964. // n[index]/n[index]/n[index]{/expand}
  1965. //
  1966. // Ends with "/expand" if the target is expand/collapse indicator,
  1967. // otherwise ends with the node
  1968. boolean isExpandCollapse = false;
  1969. if (!getElement().isOrHasChild(subElement)) {
  1970. return null;
  1971. }
  1972. TreeNode treeNode = Util.findWidget(subElement, TreeNode.class);
  1973. if (treeNode == null) {
  1974. // Did not click on a node, let somebody else take care of the
  1975. // locator string
  1976. return null;
  1977. }
  1978. if (subElement == treeNode.getElement()
  1979. || subElement == treeNode.ie6compatnode) {
  1980. // Targets expand/collapse arrow
  1981. isExpandCollapse = true;
  1982. }
  1983. ArrayList<Integer> positions = new ArrayList<Integer>();
  1984. while (treeNode.getParentNode() != null) {
  1985. positions.add(0,
  1986. treeNode.getParentNode().getChildren().indexOf(treeNode));
  1987. treeNode = treeNode.getParentNode();
  1988. }
  1989. positions.add(0, getRootNodes().indexOf(treeNode));
  1990. String locator = "";
  1991. for (Integer i : positions) {
  1992. locator += SUBPART_NODE_PREFIX + "[" + i + "]/";
  1993. }
  1994. locator = locator.substring(0, locator.length() - 1);
  1995. if (isExpandCollapse) {
  1996. locator += "/" + EXPAND_IDENTIFIER;
  1997. }
  1998. return locator;
  1999. }
  2000. public Action[] getActions() {
  2001. if (bodyActionKeys == null) {
  2002. return new Action[] {};
  2003. }
  2004. final Action[] actions = new Action[bodyActionKeys.length];
  2005. for (int i = 0; i < actions.length; i++) {
  2006. final String actionKey = bodyActionKeys[i];
  2007. final TreeAction a = new TreeAction(this, null, actionKey);
  2008. a.setCaption(getActionCaption(actionKey));
  2009. a.setIconUrl(getActionIcon(actionKey));
  2010. actions[i] = a;
  2011. }
  2012. return actions;
  2013. }
  2014. public ApplicationConnection getClient() {
  2015. return client;
  2016. }
  2017. public String getPaintableId() {
  2018. return paintableId;
  2019. }
  2020. private void handleBodyContextMenu(ContextMenuEvent event) {
  2021. if (!readonly && !disabled) {
  2022. if (bodyActionKeys != null) {
  2023. int left = event.getNativeEvent().getClientX();
  2024. int top = event.getNativeEvent().getClientY();
  2025. top += Window.getScrollTop();
  2026. left += Window.getScrollLeft();
  2027. client.getContextMenu().showAt(this, left, top);
  2028. }
  2029. event.stopPropagation();
  2030. event.preventDefault();
  2031. }
  2032. }
  2033. private boolean isIE6OrOpera() {
  2034. return BrowserInfo.get().isIE6() || BrowserInfo.get().isOpera();
  2035. }
  2036. }