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.

ITabsheet.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.itmill.toolkit.terminal.gwt.client.ui;
  5. import java.util.HashMap;
  6. import java.util.Iterator;
  7. import java.util.Set;
  8. import com.google.gwt.user.client.Command;
  9. import com.google.gwt.user.client.DOM;
  10. import com.google.gwt.user.client.DeferredCommand;
  11. import com.google.gwt.user.client.Element;
  12. import com.google.gwt.user.client.Event;
  13. import com.google.gwt.user.client.Timer;
  14. import com.google.gwt.user.client.ui.ClickListener;
  15. import com.google.gwt.user.client.ui.ComplexPanel;
  16. import com.google.gwt.user.client.ui.Label;
  17. import com.google.gwt.user.client.ui.Widget;
  18. import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
  19. import com.itmill.toolkit.terminal.gwt.client.BrowserInfo;
  20. import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
  21. import com.itmill.toolkit.terminal.gwt.client.ICaption;
  22. import com.itmill.toolkit.terminal.gwt.client.Paintable;
  23. import com.itmill.toolkit.terminal.gwt.client.RenderInformation;
  24. import com.itmill.toolkit.terminal.gwt.client.RenderSpace;
  25. import com.itmill.toolkit.terminal.gwt.client.UIDL;
  26. public class ITabsheet extends ITabsheetBase implements
  27. ContainerResizedListener {
  28. class TabBar extends ComplexPanel implements ClickListener {
  29. private Element tr = DOM.createTR();
  30. private Element spacerTd = DOM.createTD();
  31. TabBar() {
  32. Element el = DOM.createTable();
  33. Element tbody = DOM.createTBody();
  34. DOM.appendChild(el, tbody);
  35. DOM.appendChild(tbody, tr);
  36. setStyleName(spacerTd, CLASSNAME + "-spacertd");
  37. DOM.appendChild(tr, spacerTd);
  38. DOM.appendChild(spacerTd, DOM.createDiv());
  39. setElement(el);
  40. }
  41. protected Element getContainerElement() {
  42. return tr;
  43. }
  44. private Widget oldSelected;
  45. public int getTabCount() {
  46. return getWidgetCount();
  47. }
  48. public void addTab(ICaption c) {
  49. Element td = DOM.createTD();
  50. setStyleName(td, CLASSNAME + "-tabitemcell");
  51. if (getWidgetCount() == 0) {
  52. setStyleName(td, CLASSNAME + "-tabitemcell-first", true);
  53. }
  54. Element div = DOM.createDiv();
  55. setStyleName(div, CLASSNAME + "-tabitem");
  56. DOM.appendChild(td, div);
  57. DOM.insertBefore(tr, td, spacerTd);
  58. c.addClickListener(this);
  59. add(c, div);
  60. }
  61. public void onClick(Widget sender) {
  62. int index = getWidgetIndex(sender);
  63. onTabSelected(index);
  64. }
  65. public void selectTab(int index) {
  66. Widget newSelected = getWidget(index);
  67. Widget.setStyleName(DOM.getParent(newSelected.getElement()),
  68. CLASSNAME + "-tabitem-selected", true);
  69. if (oldSelected != null && oldSelected != newSelected) {
  70. Widget.setStyleName(DOM.getParent(oldSelected.getElement()),
  71. CLASSNAME + "-tabitem-selected", false);
  72. }
  73. oldSelected = newSelected;
  74. }
  75. public void removeTab(int i) {
  76. remove(i);
  77. }
  78. public boolean remove(Widget w) {
  79. ((ICaption) w).removeClickListener(this);
  80. return super.remove(w);
  81. }
  82. public ICaption getTab(int index) {
  83. if (index >= getWidgetCount()) {
  84. return null;
  85. }
  86. return (ICaption) getWidget(index);
  87. }
  88. }
  89. public static final String CLASSNAME = "i-tabsheet";
  90. public static final String TABS_CLASSNAME = "i-tabsheet-tabcontainer";
  91. public static final String SCROLLER_CLASSNAME = "i-tabsheet-scroller";
  92. private final Element tabs; // tabbar and 'scroller' container
  93. private final Element scroller; // tab-scroller element
  94. private final Element scrollerNext; // tab-scroller next button element
  95. private final Element scrollerPrev; // tab-scroller prev button element
  96. private int scrollerIndex = 0;
  97. private final TabBar tb = new TabBar();
  98. private final ITabsheetPanel tp = new ITabsheetPanel();
  99. private final Element contentNode, deco;
  100. private final HashMap captions = new HashMap();
  101. private String height;
  102. private String width;
  103. private boolean waitingForResponse;
  104. private RenderInformation renderInformation = new RenderInformation();
  105. /**
  106. * Previous visible widget is set invisible with CSS (not display: none, but
  107. * visibility: hidden), to avoid flickering during render process. Normal
  108. * visibility must be returned later when new widget is rendered.
  109. */
  110. private Widget previousVisibleWidget;
  111. private void onTabSelected(final int tabIndex) {
  112. if (disabled || waitingForResponse) {
  113. return;
  114. }
  115. final Object tabKey = tabKeys.get(tabIndex);
  116. if (disabledTabKeys.contains(tabKey)) {
  117. return;
  118. }
  119. if (client != null && activeTabIndex != tabIndex) {
  120. tb.selectTab(tabIndex);
  121. addStyleDependentName("loading");
  122. // run updating variables in deferred command to bypass some
  123. // FF
  124. // optimization issues
  125. DeferredCommand.addCommand(new Command() {
  126. public void execute() {
  127. previousVisibleWidget = tp.getWidget(tp.getVisibleWidget());
  128. DOM.setStyleAttribute(previousVisibleWidget.getElement(),
  129. "visibility", "hidden");
  130. client.updateVariable(id, "selected", tabKeys.get(tabIndex)
  131. .toString(), true);
  132. }
  133. });
  134. waitingForResponse = true;
  135. }
  136. }
  137. public ITabsheet() {
  138. super(CLASSNAME);
  139. // Tab scrolling
  140. DOM.setStyleAttribute(getElement(), "overflow", "hidden");
  141. tabs = DOM.createDiv();
  142. DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
  143. scroller = DOM.createDiv();
  144. DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME);
  145. scrollerPrev = DOM.createButton();
  146. DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME
  147. + "Prev");
  148. DOM.sinkEvents(scrollerPrev, Event.ONCLICK);
  149. scrollerNext = DOM.createButton();
  150. DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME
  151. + "Next");
  152. DOM.sinkEvents(scrollerNext, Event.ONCLICK);
  153. DOM.appendChild(getElement(), tabs);
  154. // Tabs
  155. tp.setStyleName(CLASSNAME + "-tabsheetpanel");
  156. contentNode = DOM.createDiv();
  157. deco = DOM.createDiv();
  158. addStyleDependentName("loading"); // Indicate initial progress
  159. tb.setStyleName(CLASSNAME + "-tabs");
  160. DOM
  161. .setElementProperty(contentNode, "className", CLASSNAME
  162. + "-content");
  163. DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
  164. add(tb, tabs);
  165. DOM.appendChild(scroller, scrollerPrev);
  166. DOM.appendChild(scroller, scrollerNext);
  167. DOM.appendChild(getElement(), contentNode);
  168. add(tp, contentNode);
  169. DOM.appendChild(getElement(), deco);
  170. DOM.appendChild(tabs, scroller);
  171. // TODO Use for Safari only. Fix annoying 1px first cell in TabBar.
  172. // DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM
  173. // .getFirstChild(tb.getElement()))), "display", "none");
  174. }
  175. public void onBrowserEvent(Event event) {
  176. // Tab scrolling
  177. if (isScrolledTabs() && DOM.eventGetTarget(event) == scrollerPrev) {
  178. if (scrollerIndex > 0) {
  179. scrollerIndex--;
  180. DOM.setStyleAttribute(DOM.getChild(DOM.getFirstChild(DOM
  181. .getFirstChild(tb.getElement())), scrollerIndex),
  182. "display", "");
  183. updateTabScroller();
  184. }
  185. } else if (isClippedTabs() && DOM.eventGetTarget(event) == scrollerNext) {
  186. int tabs = tb.getTabCount();
  187. if (scrollerIndex + 1 <= tabs) {
  188. DOM.setStyleAttribute(DOM.getChild(DOM.getFirstChild(DOM
  189. .getFirstChild(tb.getElement())), scrollerIndex),
  190. "display", "none");
  191. scrollerIndex++;
  192. updateTabScroller();
  193. }
  194. } else {
  195. super.onBrowserEvent(event);
  196. }
  197. }
  198. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  199. super.updateFromUIDL(uidl, client);
  200. // Add proper stylenames for all elements (easier to prevent unwanted
  201. // style inheritance)
  202. if (uidl.hasAttribute("style")) {
  203. final String[] styles = uidl.getStringAttribute("style").split(" ");
  204. final String contentBaseClass = CLASSNAME + "-content";
  205. String contentClass = contentBaseClass;
  206. final String decoBaseClass = CLASSNAME + "-deco";
  207. String decoClass = decoBaseClass;
  208. for (int i = 0; i < styles.length; i++) {
  209. tb.addStyleDependentName(styles[i]);
  210. contentClass += " " + contentBaseClass + "-" + styles[i];
  211. decoClass += " " + decoBaseClass + "-" + styles[i];
  212. }
  213. DOM.setElementProperty(contentNode, "className", contentClass);
  214. DOM.setElementProperty(deco, "className", decoClass);
  215. } else {
  216. tb.setStyleName(CLASSNAME + "-tabs");
  217. DOM.setElementProperty(contentNode, "className", CLASSNAME
  218. + "-content");
  219. DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
  220. }
  221. if (uidl.hasAttribute("hidetabs")) {
  222. tb.setVisible(false);
  223. addStyleName(CLASSNAME + "-hidetabs");
  224. } else {
  225. tb.setVisible(true);
  226. removeStyleName(CLASSNAME + "-hidetabs");
  227. }
  228. // tabs; push or not
  229. if (uidl.hasAttribute("width")) {
  230. // update width later, in updateTabScroller();
  231. DOM.setStyleAttribute(tabs, "width", "1px");
  232. DOM.setStyleAttribute(tabs, "overflow", "hidden");
  233. } else {
  234. showAllTabs();
  235. DOM.setStyleAttribute(tabs, "width", "");
  236. DOM.setStyleAttribute(tabs, "overflow", "visible");
  237. }
  238. updateTabScroller();
  239. waitingForResponse = false;
  240. }
  241. protected void renderTab(final UIDL tabUidl, int index, boolean selected) {
  242. ICaption c = tb.getTab(index);
  243. if (c == null) {
  244. c = new ICaption(null, client);
  245. tb.addTab(c);
  246. }
  247. c.updateCaption(tabUidl);
  248. captions.put("" + index, c);
  249. if (selected) {
  250. renderContent(tabUidl.getChildUIDL(0));
  251. tb.selectTab(index);
  252. } else {
  253. if (tabUidl.getChildCount() > 0) {
  254. // updating a drawn child on hidden tab
  255. Paintable paintable = client.getPaintable(tabUidl
  256. .getChildUIDL(0));
  257. if (tp.getWidgetIndex((Widget) paintable) < 0) {
  258. tp.insert((Widget) paintable, index);
  259. }
  260. paintable.updateFromUIDL(tabUidl.getChildUIDL(0), client);
  261. } else if (tp.getWidgetCount() <= index) {
  262. tp.add(new Label(""));
  263. }
  264. }
  265. }
  266. protected void selectTab(int index, final UIDL contentUidl) {
  267. if (index != activeTabIndex) {
  268. activeTabIndex = index;
  269. tb.selectTab(activeTabIndex);
  270. }
  271. renderContent(contentUidl);
  272. }
  273. private void renderContent(final UIDL contentUIDL) {
  274. final Paintable content = client.getPaintable(contentUIDL);
  275. if (tp.getWidgetCount() > activeTabIndex) {
  276. Widget old = tp.getWidget(activeTabIndex);
  277. if (old != content) {
  278. tp.remove(activeTabIndex);
  279. if (old instanceof Paintable) {
  280. client.unregisterPaintable((Paintable) old);
  281. }
  282. tp.insert((Widget) content, activeTabIndex);
  283. }
  284. } else {
  285. tp.add((Widget) content);
  286. }
  287. tp.showWidget(activeTabIndex);
  288. ITabsheet.this.iLayout();
  289. (content).updateFromUIDL(contentUIDL, client);
  290. ITabsheet.this.removeStyleDependentName("loading");
  291. if (previousVisibleWidget != null) {
  292. DOM.setStyleAttribute(previousVisibleWidget.getElement(),
  293. "visibility", "");
  294. previousVisibleWidget = null;
  295. }
  296. }
  297. public void setHeight(String height) {
  298. super.setHeight(height);
  299. this.height = height;
  300. if (height != null && !"".equals(height)) {
  301. int contentHeight = getOffsetHeight()
  302. - DOM.getElementPropertyInt(deco, "offsetHeight")
  303. - tb.getOffsetHeight() - 5;
  304. if (contentHeight < 0) {
  305. contentHeight = 0;
  306. }
  307. // Set proper values for content element
  308. DOM.setStyleAttribute(contentNode, "height", contentHeight + "px");
  309. renderSpace.setHeight(contentHeight);
  310. DOM.setStyleAttribute(contentNode, "overflow", "auto");
  311. } else {
  312. DOM.setStyleAttribute(contentNode, "height", "");
  313. DOM.setStyleAttribute(contentNode, "overflow", "");
  314. renderSpace.setHeight(0);
  315. }
  316. iLayout();
  317. }
  318. public void setWidth(String width) {
  319. super.setWidth(width);
  320. this.width = width;
  321. if ("".equals(width)) {
  322. renderSpace.setWidth(0);
  323. contentNode.getStyle().setProperty("width", "");
  324. } else {
  325. int contentWidth = getOffsetWidth() - getContentAreaBorderWidth();
  326. contentNode.getStyle().setProperty("width", contentWidth + "px");
  327. renderSpace.setWidth(contentWidth);
  328. }
  329. iLayout();
  330. }
  331. public void iLayout() {
  332. renderInformation.updateSize(getElement());
  333. if (client != null) {
  334. client.runDescendentsLayout(this);
  335. }
  336. updateTabScroller();
  337. if (BrowserInfo.get().getWebkitVersion() > 0) {
  338. DeferredCommand.addCommand(new Command() {
  339. public void execute() {
  340. // Dough, safari scoll auto means actually just a moped
  341. contentNode.getStyle().setProperty("overflow", "hidden");
  342. (new Timer() {
  343. @Override
  344. public void run() {
  345. contentNode.getStyle().setProperty("overflow",
  346. "auto");
  347. }
  348. }).schedule(100);
  349. }
  350. });
  351. }
  352. }
  353. /**
  354. * Layouts the tab-scroller elements, and applies styles.
  355. */
  356. private void updateTabScroller() {
  357. if (width != null) {
  358. DOM.setStyleAttribute(tabs, "width", width);
  359. }
  360. if (scrollerIndex > tb.getTabCount()) {
  361. scrollerIndex = 0;
  362. }
  363. boolean scrolled = isScrolledTabs();
  364. boolean clipped = isClippedTabs();
  365. if (tb.isVisible() && (scrolled || clipped)) {
  366. DOM.setStyleAttribute(scroller, "display", "");
  367. DOM.setElementProperty(scrollerPrev, "className",
  368. SCROLLER_CLASSNAME + (scrolled ? "Prev" : "Prev-disabled"));
  369. DOM.setElementProperty(scrollerNext, "className",
  370. SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled"));
  371. } else {
  372. DOM.setStyleAttribute(scroller, "display", "none");
  373. }
  374. }
  375. private void showAllTabs() {
  376. scrollerIndex = 0;
  377. Element tr = DOM.getFirstChild(DOM.getFirstChild(tb.getElement()));
  378. for (int i = 0; i < tb.getTabCount(); i++) {
  379. DOM.setStyleAttribute(DOM.getChild(tr, i), "display", "");
  380. }
  381. }
  382. private boolean isScrolledTabs() {
  383. return scrollerIndex > 0;
  384. }
  385. private boolean isClippedTabs() {
  386. return tb.getOffsetWidth() > getOffsetWidth();
  387. }
  388. protected void clearPaintables() {
  389. int i = tb.getTabCount();
  390. while (i > 0) {
  391. tb.removeTab(--i);
  392. }
  393. tp.clear();
  394. }
  395. protected Iterator getPaintableIterator() {
  396. return tp.iterator();
  397. }
  398. public boolean hasChildComponent(Widget component) {
  399. if (tp.getWidgetIndex(component) < 0) {
  400. return false;
  401. } else {
  402. return true;
  403. }
  404. }
  405. public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
  406. int widgetIndex = tp.getWidgetIndex(oldComponent);
  407. tp.remove(oldComponent);
  408. tp.insert(newComponent, widgetIndex);
  409. }
  410. public void updateCaption(Paintable component, UIDL uidl) {
  411. int i = tp.getWidgetIndex((Widget) component);
  412. ICaption c = (ICaption) captions.get("" + i);
  413. c.updateCaption(uidl);
  414. }
  415. public boolean requestLayout(Set<Paintable> child) {
  416. if (height != null && width != null) {
  417. /*
  418. * If the height and width has been specified for this container the
  419. * child components cannot make the size of the layout change
  420. */
  421. return true;
  422. }
  423. if (renderInformation.updateSize(getElement())) {
  424. /*
  425. * Size has changed so we let the child components know about the
  426. * new size.
  427. */
  428. iLayout();
  429. return false;
  430. } else {
  431. /*
  432. * Size has not changed so we do not need to propagate the event
  433. * further
  434. */
  435. return true;
  436. }
  437. }
  438. private int borderW = -1;
  439. private void detectBorder() {
  440. String property = contentNode.getStyle().getProperty("overflow");
  441. contentNode.getStyle().setProperty("overflow", "hidden");
  442. borderW = contentNode.getOffsetWidth()
  443. - contentNode.getPropertyInt("clientWidth");
  444. contentNode.getStyle().setProperty("overflow", property);
  445. }
  446. private int getContentAreaBorderWidth() {
  447. if (borderW < 0) {
  448. detectBorder();
  449. }
  450. return borderW;
  451. }
  452. private RenderSpace renderSpace = new RenderSpace(0, 0, true);
  453. public RenderSpace getAllocatedSpace(Widget child) {
  454. // All tabs have equal amount of space allocated
  455. return renderSpace;
  456. }
  457. }