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.

VTabsheet.java 37KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.Iterator;
  6. import java.util.Set;
  7. import com.google.gwt.core.client.Scheduler;
  8. import com.google.gwt.dom.client.DivElement;
  9. import com.google.gwt.dom.client.Style;
  10. import com.google.gwt.dom.client.TableCellElement;
  11. import com.google.gwt.dom.client.TableElement;
  12. import com.google.gwt.event.dom.client.ClickEvent;
  13. import com.google.gwt.event.dom.client.ClickHandler;
  14. import com.google.gwt.user.client.Command;
  15. import com.google.gwt.user.client.DOM;
  16. import com.google.gwt.user.client.Element;
  17. import com.google.gwt.user.client.Event;
  18. import com.google.gwt.user.client.ui.ComplexPanel;
  19. import com.google.gwt.user.client.ui.SimplePanel;
  20. import com.google.gwt.user.client.ui.Widget;
  21. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  22. import com.vaadin.terminal.gwt.client.BrowserInfo;
  23. import com.vaadin.terminal.gwt.client.ComponentState;
  24. import com.vaadin.terminal.gwt.client.RenderInformation;
  25. import com.vaadin.terminal.gwt.client.RenderSpace;
  26. import com.vaadin.terminal.gwt.client.TooltipInfo;
  27. import com.vaadin.terminal.gwt.client.UIDL;
  28. import com.vaadin.terminal.gwt.client.Util;
  29. import com.vaadin.terminal.gwt.client.VCaption;
  30. import com.vaadin.terminal.gwt.client.VPaintableMap;
  31. import com.vaadin.terminal.gwt.client.VPaintableWidget;
  32. import com.vaadin.terminal.gwt.client.ui.label.VLabel;
  33. public class VTabsheet extends VTabsheetBase {
  34. private static class VCloseEvent {
  35. private Tab tab;
  36. VCloseEvent(Tab tab) {
  37. this.tab = tab;
  38. }
  39. public Tab getTab() {
  40. return tab;
  41. }
  42. }
  43. private interface VCloseHandler {
  44. public void onClose(VCloseEvent event);
  45. }
  46. /**
  47. * Representation of a single "tab" shown in the TabBar
  48. *
  49. */
  50. private static class Tab extends SimplePanel {
  51. private static final String TD_CLASSNAME = CLASSNAME + "-tabitemcell";
  52. private static final String TD_FIRST_CLASSNAME = TD_CLASSNAME
  53. + "-first";
  54. private static final String TD_SELECTED_CLASSNAME = TD_CLASSNAME
  55. + "-selected";
  56. private static final String TD_SELECTED_FIRST_CLASSNAME = TD_SELECTED_CLASSNAME
  57. + "-first";
  58. private static final String TD_DISABLED_CLASSNAME = TD_CLASSNAME
  59. + "-disabled";
  60. private static final String DIV_CLASSNAME = CLASSNAME + "-tabitem";
  61. private static final String DIV_SELECTED_CLASSNAME = DIV_CLASSNAME
  62. + "-selected";
  63. private TabCaption tabCaption;
  64. Element td = getElement();
  65. private VCloseHandler closeHandler;
  66. private boolean enabledOnServer = true;
  67. private Element div;
  68. private TabBar tabBar;
  69. private boolean hiddenOnServer = false;
  70. private String styleName;
  71. private Tab(TabBar tabBar) {
  72. super(DOM.createTD());
  73. this.tabBar = tabBar;
  74. setStyleName(td, TD_CLASSNAME);
  75. div = DOM.createDiv();
  76. setStyleName(div, DIV_CLASSNAME);
  77. DOM.appendChild(td, div);
  78. tabCaption = new TabCaption(this, getTabsheet()
  79. .getApplicationConnection());
  80. add(tabCaption);
  81. }
  82. public boolean isHiddenOnServer() {
  83. return hiddenOnServer;
  84. }
  85. public void setHiddenOnServer(boolean hiddenOnServer) {
  86. this.hiddenOnServer = hiddenOnServer;
  87. }
  88. @Override
  89. protected Element getContainerElement() {
  90. // Attach caption element to div, not td
  91. return div;
  92. }
  93. public boolean isEnabledOnServer() {
  94. return enabledOnServer;
  95. }
  96. public void setEnabledOnServer(boolean enabled) {
  97. enabledOnServer = enabled;
  98. setStyleName(td, TD_DISABLED_CLASSNAME, !enabled);
  99. }
  100. public void addClickHandler(ClickHandler handler) {
  101. tabCaption.addClickHandler(handler);
  102. }
  103. public void setCloseHandler(VCloseHandler closeHandler) {
  104. this.closeHandler = closeHandler;
  105. }
  106. /**
  107. * Toggles the style names for the Tab
  108. *
  109. * @param selected
  110. * true if the Tab is selected
  111. * @param first
  112. * true if the Tab is the first visible Tab
  113. */
  114. public void setStyleNames(boolean selected, boolean first) {
  115. setStyleName(td, TD_FIRST_CLASSNAME, first);
  116. setStyleName(td, TD_SELECTED_CLASSNAME, selected);
  117. setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first);
  118. setStyleName(div, DIV_SELECTED_CLASSNAME, selected);
  119. }
  120. public void onClose() {
  121. closeHandler.onClose(new VCloseEvent(this));
  122. }
  123. public VTabsheet getTabsheet() {
  124. return tabBar.getTabsheet();
  125. }
  126. public void updateFromUIDL(UIDL tabUidl) {
  127. tabCaption.updateCaption(tabUidl);
  128. // Apply the styleName set for the tab
  129. String newStyleName = tabUidl.getStringAttribute(TAB_STYLE_NAME);
  130. // Find the nth td element
  131. if (newStyleName != null && newStyleName.length() != 0) {
  132. if (!newStyleName.equals(styleName)) {
  133. // If we have a new style name
  134. if (styleName != null && styleName.length() != 0) {
  135. // Remove old style name if present
  136. td.removeClassName(TD_CLASSNAME + "-" + styleName);
  137. }
  138. // Set new style name
  139. td.addClassName(TD_CLASSNAME + "-" + newStyleName);
  140. styleName = newStyleName;
  141. }
  142. } else if (styleName != null) {
  143. // Remove the set stylename if no stylename is present in the
  144. // uidl
  145. td.removeClassName(TD_CLASSNAME + "-" + styleName);
  146. styleName = null;
  147. }
  148. }
  149. public void recalculateCaptionWidth() {
  150. tabCaption.setWidth(tabCaption.getRequiredWidth() + "px");
  151. }
  152. }
  153. private static class TabCaption extends VCaption {
  154. private boolean closable = false;
  155. private Element closeButton;
  156. private Tab tab;
  157. private ApplicationConnection client;
  158. TabCaption(Tab tab, ApplicationConnection client) {
  159. super(client);
  160. this.client = client;
  161. this.tab = tab;
  162. }
  163. @Override
  164. public boolean updateCaption(UIDL uidl) {
  165. if (uidl.hasAttribute(VTabsheetBasePaintable.ATTRIBUTE_TAB_DESCRIPTION)
  166. || uidl.hasAttribute(VAbstractPaintableWidget.ATTRIBUTE_ERROR)) {
  167. TooltipInfo tooltipInfo = new TooltipInfo();
  168. tooltipInfo
  169. .setTitle(uidl
  170. .getStringAttribute(VTabsheetBasePaintable.ATTRIBUTE_TAB_DESCRIPTION));
  171. if (uidl.hasAttribute(VAbstractPaintableWidget.ATTRIBUTE_ERROR)) {
  172. tooltipInfo.setErrorUidl(uidl.getErrors());
  173. }
  174. client.registerWidgetTooltip(getTabsheet(), getElement(),
  175. tooltipInfo);
  176. } else {
  177. client.registerWidgetTooltip(getTabsheet(), getElement(), null);
  178. }
  179. // TODO need to call this instead of super because the caption does
  180. // not have an owner
  181. boolean ret = updateCaptionWithoutOwner(
  182. uidl,
  183. uidl.getStringAttribute(VTabsheetBasePaintable.ATTRIBUTE_TAB_CAPTION),
  184. uidl.hasAttribute(VTabsheetBasePaintable.ATTRIBUTE_TAB_DISABLED),
  185. uidl.hasAttribute(VTabsheetBasePaintable.ATTRIBUTE_TAB_DESCRIPTION));
  186. setClosable(uidl.hasAttribute("closable"));
  187. return ret;
  188. }
  189. private VTabsheet getTabsheet() {
  190. return tab.getTabsheet();
  191. }
  192. @Override
  193. public void onBrowserEvent(Event event) {
  194. if (closable && event.getTypeInt() == Event.ONCLICK
  195. && event.getEventTarget().cast() == closeButton) {
  196. tab.onClose();
  197. event.stopPropagation();
  198. event.preventDefault();
  199. }
  200. super.onBrowserEvent(event);
  201. if (event.getTypeInt() == Event.ONLOAD) {
  202. getTabsheet().tabSizeMightHaveChanged(getTab());
  203. }
  204. client.handleWidgetTooltipEvent(event, getTabsheet(), getElement());
  205. }
  206. public Tab getTab() {
  207. return tab;
  208. }
  209. public void setClosable(boolean closable) {
  210. this.closable = closable;
  211. if (closable && closeButton == null) {
  212. closeButton = DOM.createSpan();
  213. closeButton.setInnerHTML("×");
  214. closeButton
  215. .setClassName(VTabsheet.CLASSNAME + "-caption-close");
  216. getElement().insertBefore(closeButton,
  217. getElement().getLastChild());
  218. } else if (!closable && closeButton != null) {
  219. getElement().removeChild(closeButton);
  220. closeButton = null;
  221. }
  222. if (closable) {
  223. addStyleDependentName("closable");
  224. } else {
  225. removeStyleDependentName("closable");
  226. }
  227. }
  228. @Override
  229. public int getRequiredWidth() {
  230. int width = super.getRequiredWidth();
  231. if (closeButton != null) {
  232. width += Util.getRequiredWidth(closeButton);
  233. }
  234. return width;
  235. }
  236. }
  237. static class TabBar extends ComplexPanel implements ClickHandler,
  238. VCloseHandler {
  239. private final Element tr = DOM.createTR();
  240. private final Element spacerTd = DOM.createTD();
  241. private Tab selected;
  242. private VTabsheet tabsheet;
  243. TabBar(VTabsheet tabsheet) {
  244. this.tabsheet = tabsheet;
  245. Element el = DOM.createTable();
  246. Element tbody = DOM.createTBody();
  247. DOM.appendChild(el, tbody);
  248. DOM.appendChild(tbody, tr);
  249. setStyleName(spacerTd, CLASSNAME + "-spacertd");
  250. DOM.appendChild(tr, spacerTd);
  251. DOM.appendChild(spacerTd, DOM.createDiv());
  252. setElement(el);
  253. }
  254. public void onClose(VCloseEvent event) {
  255. Tab tab = event.getTab();
  256. if (!tab.isEnabledOnServer()) {
  257. return;
  258. }
  259. int tabIndex = getWidgetIndex(tab);
  260. getTabsheet().sendTabClosedEvent(tabIndex);
  261. }
  262. protected Element getContainerElement() {
  263. return tr;
  264. }
  265. public int getTabCount() {
  266. return getWidgetCount();
  267. }
  268. public Tab addTab() {
  269. Tab t = new Tab(this);
  270. int tabIndex = getTabCount();
  271. // Logical attach
  272. insert(t, tr, tabIndex, true);
  273. if (tabIndex == 0) {
  274. // Set the "first" style
  275. t.setStyleNames(false, true);
  276. }
  277. t.addClickHandler(this);
  278. t.setCloseHandler(this);
  279. return t;
  280. }
  281. public void onClick(ClickEvent event) {
  282. Widget caption = (Widget) event.getSource();
  283. int index = getWidgetIndex(caption.getParent());
  284. getTabsheet().onTabSelected(index);
  285. }
  286. public VTabsheet getTabsheet() {
  287. return tabsheet;
  288. }
  289. public Tab getTab(int index) {
  290. if (index < 0 || index >= getTabCount()) {
  291. return null;
  292. }
  293. return (Tab) super.getWidget(index);
  294. }
  295. public void selectTab(int index) {
  296. final Tab newSelected = getTab(index);
  297. final Tab oldSelected = selected;
  298. newSelected.setStyleNames(true, isFirstVisibleTab(index));
  299. if (oldSelected != null && oldSelected != newSelected) {
  300. oldSelected.setStyleNames(false,
  301. isFirstVisibleTab(getWidgetIndex(oldSelected)));
  302. }
  303. // Update the field holding the currently selected tab
  304. selected = newSelected;
  305. // The selected tab might need more (or less) space
  306. newSelected.recalculateCaptionWidth();
  307. getTab(tabsheet.activeTabIndex).recalculateCaptionWidth();
  308. }
  309. public void removeTab(int i) {
  310. Tab tab = getTab(i);
  311. if (tab == null) {
  312. return;
  313. }
  314. remove(tab);
  315. /*
  316. * If this widget was selected we need to unmark it as the last
  317. * selected
  318. */
  319. if (tab == selected) {
  320. selected = null;
  321. }
  322. // FIXME: Shouldn't something be selected instead?
  323. }
  324. private boolean isFirstVisibleTab(int index) {
  325. return getFirstVisibleTab() == index;
  326. }
  327. /**
  328. * Returns the index of the first visible tab
  329. *
  330. * @return
  331. */
  332. private int getFirstVisibleTab() {
  333. return getNextVisibleTab(-1);
  334. }
  335. /**
  336. * Find the next visible tab. Returns -1 if none is found.
  337. *
  338. * @param i
  339. * @return
  340. */
  341. private int getNextVisibleTab(int i) {
  342. int tabs = getTabCount();
  343. do {
  344. i++;
  345. } while (i < tabs && getTab(i).isHiddenOnServer());
  346. if (i == tabs) {
  347. return -1;
  348. } else {
  349. return i;
  350. }
  351. }
  352. /**
  353. * Find the previous visible tab. Returns -1 if none is found.
  354. *
  355. * @param i
  356. * @return
  357. */
  358. private int getPreviousVisibleTab(int i) {
  359. do {
  360. i--;
  361. } while (i >= 0 && getTab(i).isHiddenOnServer());
  362. return i;
  363. }
  364. public int scrollLeft(int currentFirstVisible) {
  365. int prevVisible = getPreviousVisibleTab(currentFirstVisible);
  366. if (prevVisible == -1) {
  367. return -1;
  368. }
  369. Tab newFirst = getTab(prevVisible);
  370. newFirst.setVisible(true);
  371. newFirst.recalculateCaptionWidth();
  372. return prevVisible;
  373. }
  374. public int scrollRight(int currentFirstVisible) {
  375. int nextVisible = getNextVisibleTab(currentFirstVisible);
  376. if (nextVisible == -1) {
  377. return -1;
  378. }
  379. Tab currentFirst = getTab(currentFirstVisible);
  380. currentFirst.setVisible(false);
  381. currentFirst.recalculateCaptionWidth();
  382. return nextVisible;
  383. }
  384. }
  385. public static final String CLASSNAME = "v-tabsheet";
  386. public static final String TABS_CLASSNAME = "v-tabsheet-tabcontainer";
  387. public static final String SCROLLER_CLASSNAME = "v-tabsheet-scroller";
  388. // Can't use "style" as it's already in use
  389. public static final String TAB_STYLE_NAME = "tabstyle";
  390. final Element tabs; // tabbar and 'scroller' container
  391. private final Element scroller; // tab-scroller element
  392. private final Element scrollerNext; // tab-scroller next button element
  393. private final Element scrollerPrev; // tab-scroller prev button element
  394. /**
  395. * The index of the first visible tab (when scrolled)
  396. */
  397. private int scrollerIndex = 0;
  398. private final TabBar tb = new TabBar(this);
  399. final VTabsheetPanel tp = new VTabsheetPanel();
  400. private final Element contentNode, deco;
  401. private String height;
  402. private String width;
  403. boolean waitingForResponse;
  404. final RenderInformation renderInformation = new RenderInformation();
  405. /**
  406. * Previous visible widget is set invisible with CSS (not display: none, but
  407. * visibility: hidden), to avoid flickering during render process. Normal
  408. * visibility must be returned later when new widget is rendered.
  409. */
  410. private Widget previousVisibleWidget;
  411. boolean rendering = false;
  412. private String currentStyle;
  413. private void onTabSelected(final int tabIndex) {
  414. if (disabled || waitingForResponse) {
  415. return;
  416. }
  417. final Object tabKey = tabKeys.get(tabIndex);
  418. if (disabledTabKeys.contains(tabKey)) {
  419. return;
  420. }
  421. if (client != null && activeTabIndex != tabIndex) {
  422. tb.selectTab(tabIndex);
  423. addStyleDependentName("loading");
  424. // run updating variables in deferred command to bypass some FF
  425. // optimization issues
  426. Scheduler.get().scheduleDeferred(new Command() {
  427. public void execute() {
  428. previousVisibleWidget = tp.getWidget(tp.getVisibleWidget());
  429. DOM.setStyleAttribute(
  430. DOM.getParent(previousVisibleWidget.getElement()),
  431. "visibility", "hidden");
  432. client.updateVariable(id, "selected", tabKeys.get(tabIndex)
  433. .toString(), true);
  434. }
  435. });
  436. waitingForResponse = true;
  437. }
  438. }
  439. public ApplicationConnection getApplicationConnection() {
  440. return client;
  441. }
  442. public void tabSizeMightHaveChanged(Tab tab) {
  443. // icon onloads may change total width of tabsheet
  444. if (isDynamicWidth()) {
  445. updateDynamicWidth();
  446. }
  447. updateTabScroller();
  448. }
  449. void sendTabClosedEvent(int tabIndex) {
  450. client.updateVariable(id, "close", tabKeys.get(tabIndex), true);
  451. }
  452. boolean isDynamicWidth() {
  453. return width == null || width.equals("");
  454. }
  455. boolean isDynamicHeight() {
  456. return height == null || height.equals("");
  457. }
  458. public VTabsheet() {
  459. super(CLASSNAME);
  460. // Tab scrolling
  461. DOM.setStyleAttribute(getElement(), "overflow", "hidden");
  462. tabs = DOM.createDiv();
  463. DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
  464. scroller = DOM.createDiv();
  465. DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME);
  466. scrollerPrev = DOM.createButton();
  467. DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME
  468. + "Prev");
  469. DOM.sinkEvents(scrollerPrev, Event.ONCLICK);
  470. scrollerNext = DOM.createButton();
  471. DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME
  472. + "Next");
  473. DOM.sinkEvents(scrollerNext, Event.ONCLICK);
  474. DOM.appendChild(getElement(), tabs);
  475. // Tabs
  476. tp.setStyleName(CLASSNAME + "-tabsheetpanel");
  477. contentNode = DOM.createDiv();
  478. deco = DOM.createDiv();
  479. addStyleDependentName("loading"); // Indicate initial progress
  480. tb.setStyleName(CLASSNAME + "-tabs");
  481. DOM.setElementProperty(contentNode, "className", CLASSNAME + "-content");
  482. DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
  483. add(tb, tabs);
  484. DOM.appendChild(scroller, scrollerPrev);
  485. DOM.appendChild(scroller, scrollerNext);
  486. DOM.appendChild(getElement(), contentNode);
  487. add(tp, contentNode);
  488. DOM.appendChild(getElement(), deco);
  489. DOM.appendChild(tabs, scroller);
  490. // TODO Use for Safari only. Fix annoying 1px first cell in TabBar.
  491. // DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM
  492. // .getFirstChild(tb.getElement()))), "display", "none");
  493. }
  494. @Override
  495. public void onBrowserEvent(Event event) {
  496. // Tab scrolling
  497. if (isScrolledTabs() && DOM.eventGetTarget(event) == scrollerPrev) {
  498. int newFirstIndex = tb.scrollLeft(scrollerIndex);
  499. if (newFirstIndex != -1) {
  500. scrollerIndex = newFirstIndex;
  501. updateTabScroller();
  502. }
  503. } else if (isClippedTabs() && DOM.eventGetTarget(event) == scrollerNext) {
  504. int newFirstIndex = tb.scrollRight(scrollerIndex);
  505. if (newFirstIndex != -1) {
  506. scrollerIndex = newFirstIndex;
  507. updateTabScroller();
  508. }
  509. } else {
  510. super.onBrowserEvent(event);
  511. }
  512. }
  513. /**
  514. * Checks if the tab with the selected index has been scrolled out of the
  515. * view (on the left side).
  516. *
  517. * @param index
  518. * @return
  519. */
  520. private boolean scrolledOutOfView(int index) {
  521. return scrollerIndex > index;
  522. }
  523. void handleStyleNames(UIDL uidl, ComponentState state) {
  524. // Add proper stylenames for all elements (easier to prevent unwanted
  525. // style inheritance)
  526. if (state.hasStyles()) {
  527. final String style = state.getStyle();
  528. if (currentStyle != style) {
  529. currentStyle = style;
  530. final String[] styles = style.split(" ");
  531. final String tabsBaseClass = TABS_CLASSNAME;
  532. String tabsClass = tabsBaseClass;
  533. final String contentBaseClass = CLASSNAME + "-content";
  534. String contentClass = contentBaseClass;
  535. final String decoBaseClass = CLASSNAME + "-deco";
  536. String decoClass = decoBaseClass;
  537. for (int i = 0; i < styles.length; i++) {
  538. tb.addStyleDependentName(styles[i]);
  539. tabsClass += " " + tabsBaseClass + "-" + styles[i];
  540. contentClass += " " + contentBaseClass + "-" + styles[i];
  541. decoClass += " " + decoBaseClass + "-" + styles[i];
  542. }
  543. DOM.setElementProperty(tabs, "className", tabsClass);
  544. DOM.setElementProperty(contentNode, "className", contentClass);
  545. DOM.setElementProperty(deco, "className", decoClass);
  546. borderW = -1;
  547. }
  548. } else {
  549. tb.setStyleName(CLASSNAME + "-tabs");
  550. DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
  551. DOM.setElementProperty(contentNode, "className", CLASSNAME
  552. + "-content");
  553. DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
  554. }
  555. if (uidl.hasAttribute("hidetabs")) {
  556. tb.setVisible(false);
  557. addStyleName(CLASSNAME + "-hidetabs");
  558. } else {
  559. tb.setVisible(true);
  560. removeStyleName(CLASSNAME + "-hidetabs");
  561. }
  562. }
  563. void updateDynamicWidth() {
  564. // Find width consumed by tabs
  565. TableCellElement spacerCell = ((TableElement) tb.getElement().cast())
  566. .getRows().getItem(0).getCells().getItem(tb.getTabCount());
  567. int spacerWidth = spacerCell.getOffsetWidth();
  568. DivElement div = (DivElement) spacerCell.getFirstChildElement();
  569. int spacerMinWidth = spacerCell.getOffsetWidth() - div.getOffsetWidth();
  570. int tabsWidth = tb.getOffsetWidth() - spacerWidth + spacerMinWidth;
  571. // Find content width
  572. Style style = tp.getElement().getStyle();
  573. String overflow = style.getProperty("overflow");
  574. style.setProperty("overflow", "hidden");
  575. style.setPropertyPx("width", tabsWidth);
  576. boolean hasTabs = tp.getWidgetCount() > 0;
  577. Style wrapperstyle = null;
  578. if (hasTabs) {
  579. wrapperstyle = tp.getWidget(tp.getVisibleWidget()).getElement()
  580. .getParentElement().getStyle();
  581. wrapperstyle.setPropertyPx("width", tabsWidth);
  582. }
  583. // Get content width from actual widget
  584. int contentWidth = 0;
  585. if (hasTabs) {
  586. contentWidth = tp.getWidget(tp.getVisibleWidget()).getOffsetWidth();
  587. }
  588. style.setProperty("overflow", overflow);
  589. // Set widths to max(tabs,content)
  590. if (tabsWidth < contentWidth) {
  591. tabsWidth = contentWidth;
  592. }
  593. int outerWidth = tabsWidth + getContentAreaBorderWidth();
  594. tabs.getStyle().setPropertyPx("width", outerWidth);
  595. style.setPropertyPx("width", tabsWidth);
  596. if (hasTabs) {
  597. wrapperstyle.setPropertyPx("width", tabsWidth);
  598. }
  599. contentNode.getStyle().setPropertyPx("width", tabsWidth);
  600. super.setWidth(outerWidth + "px");
  601. updateOpenTabSize();
  602. }
  603. @Override
  604. protected void renderTab(final UIDL tabUidl, int index, boolean selected,
  605. boolean hidden) {
  606. Tab tab = tb.getTab(index);
  607. if (tab == null) {
  608. tab = tb.addTab();
  609. }
  610. tab.updateFromUIDL(tabUidl);
  611. tab.setEnabledOnServer((!disabledTabKeys.contains(tabKeys.get(index))));
  612. tab.setHiddenOnServer(hidden);
  613. if (scrolledOutOfView(index)) {
  614. // Should not set tabs visible if they are scrolled out of view
  615. hidden = true;
  616. }
  617. // Set the current visibility of the tab (in the browser)
  618. tab.setVisible(!hidden);
  619. /*
  620. * Force the width of the caption container so the content will not wrap
  621. * and tabs won't be too narrow in certain browsers
  622. */
  623. tab.recalculateCaptionWidth();
  624. UIDL tabContentUIDL = null;
  625. VPaintableWidget tabContentPaintable = null;
  626. Widget tabContentWidget = null;
  627. if (tabUidl.getChildCount() > 0) {
  628. tabContentUIDL = tabUidl.getChildUIDL(0);
  629. tabContentPaintable = client.getPaintable(tabContentUIDL);
  630. tabContentWidget = tabContentPaintable.getWidgetForPaintable();
  631. }
  632. if (tabContentPaintable != null) {
  633. /* This is a tab with content information */
  634. int oldIndex = tp.getWidgetIndex(tabContentWidget);
  635. if (oldIndex != -1 && oldIndex != index) {
  636. /*
  637. * The tab has previously been rendered in another position so
  638. * we must move the cached content to correct position
  639. */
  640. tp.insert(tabContentWidget, index);
  641. }
  642. } else {
  643. /* A tab whose content has not yet been loaded */
  644. /*
  645. * Make sure there is a corresponding empty tab in tp. The same
  646. * operation as the moving above but for not-loaded tabs.
  647. */
  648. if (index < tp.getWidgetCount()) {
  649. Widget oldWidget = tp.getWidget(index);
  650. if (!(oldWidget instanceof PlaceHolder)) {
  651. tp.insert(new PlaceHolder(), index);
  652. }
  653. }
  654. }
  655. if (selected) {
  656. renderContent(tabContentUIDL);
  657. tb.selectTab(index);
  658. } else {
  659. if (tabContentUIDL != null) {
  660. // updating a drawn child on hidden tab
  661. if (tp.getWidgetIndex(tabContentWidget) < 0) {
  662. tp.insert(tabContentWidget, index);
  663. }
  664. tabContentPaintable.updateFromUIDL(tabContentUIDL, client);
  665. } else if (tp.getWidgetCount() <= index) {
  666. tp.add(new PlaceHolder());
  667. }
  668. }
  669. }
  670. public class PlaceHolder extends VLabel {
  671. public PlaceHolder() {
  672. super("");
  673. }
  674. }
  675. @Override
  676. protected void selectTab(int index, final UIDL contentUidl) {
  677. if (index != activeTabIndex) {
  678. activeTabIndex = index;
  679. tb.selectTab(activeTabIndex);
  680. }
  681. renderContent(contentUidl);
  682. }
  683. private void renderContent(final UIDL contentUIDL) {
  684. final VPaintableWidget content = client.getPaintable(contentUIDL);
  685. if (tp.getWidgetCount() > activeTabIndex) {
  686. Widget old = tp.getWidget(activeTabIndex);
  687. if (old != content) {
  688. tp.remove(activeTabIndex);
  689. VPaintableMap paintableMap = VPaintableMap.get(client);
  690. if (paintableMap.isPaintable(old)) {
  691. paintableMap.unregisterPaintable(paintableMap
  692. .getPaintable(old));
  693. }
  694. tp.insert(content.getWidgetForPaintable(), activeTabIndex);
  695. }
  696. } else {
  697. tp.add(content.getWidgetForPaintable());
  698. }
  699. tp.showWidget(activeTabIndex);
  700. VTabsheet.this.iLayout();
  701. content.updateFromUIDL(contentUIDL, client);
  702. /*
  703. * The size of a cached, relative sized component must be updated to
  704. * report correct size to updateOpenTabSize().
  705. */
  706. if (contentUIDL.getBooleanAttribute("cached")) {
  707. client.handleComponentRelativeSize(content.getWidgetForPaintable());
  708. }
  709. updateOpenTabSize();
  710. VTabsheet.this.removeStyleDependentName("loading");
  711. if (previousVisibleWidget != null) {
  712. DOM.setStyleAttribute(
  713. DOM.getParent(previousVisibleWidget.getElement()),
  714. "visibility", "");
  715. previousVisibleWidget = null;
  716. }
  717. }
  718. @Override
  719. public void setHeight(String height) {
  720. super.setHeight(height);
  721. this.height = height;
  722. updateContentNodeHeight();
  723. if (!rendering) {
  724. updateOpenTabSize();
  725. iLayout();
  726. // TODO Check if this is needed
  727. client.runDescendentsLayout(this);
  728. }
  729. }
  730. void updateContentNodeHeight() {
  731. if (height != null && !"".equals(height)) {
  732. int contentHeight = getOffsetHeight();
  733. contentHeight -= DOM.getElementPropertyInt(deco, "offsetHeight");
  734. contentHeight -= tb.getOffsetHeight();
  735. if (contentHeight < 0) {
  736. contentHeight = 0;
  737. }
  738. // Set proper values for content element
  739. DOM.setStyleAttribute(contentNode, "height", contentHeight + "px");
  740. renderSpace.setHeight(contentHeight);
  741. } else {
  742. DOM.setStyleAttribute(contentNode, "height", "");
  743. renderSpace.setHeight(0);
  744. }
  745. }
  746. @Override
  747. public void setWidth(String width) {
  748. if ((this.width == null && width.equals(""))
  749. || (this.width != null && this.width.equals(width))) {
  750. return;
  751. }
  752. super.setWidth(width);
  753. if (width.equals("")) {
  754. width = null;
  755. }
  756. this.width = width;
  757. if (width == null) {
  758. renderSpace.setWidth(0);
  759. contentNode.getStyle().setProperty("width", "");
  760. } else {
  761. int contentWidth = getOffsetWidth() - getContentAreaBorderWidth();
  762. if (contentWidth < 0) {
  763. contentWidth = 0;
  764. }
  765. contentNode.getStyle().setProperty("width", contentWidth + "px");
  766. renderSpace.setWidth(contentWidth);
  767. }
  768. if (!rendering) {
  769. if (isDynamicHeight()) {
  770. Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, tp,
  771. this);
  772. }
  773. updateOpenTabSize();
  774. iLayout();
  775. // TODO Check if this is needed
  776. client.runDescendentsLayout(this);
  777. }
  778. }
  779. public void iLayout() {
  780. updateTabScroller();
  781. tp.runWebkitOverflowAutoFix();
  782. }
  783. /**
  784. * Sets the size of the visible tab (component). As the tab is set to
  785. * position: absolute (to work around a firefox flickering bug) we must keep
  786. * this up-to-date by hand.
  787. */
  788. void updateOpenTabSize() {
  789. /*
  790. * The overflow=auto element must have a height specified, otherwise it
  791. * will be just as high as the contents and no scrollbars will appear
  792. */
  793. int height = -1;
  794. int width = -1;
  795. int minWidth = 0;
  796. if (!isDynamicHeight()) {
  797. height = renderSpace.getHeight();
  798. }
  799. if (!isDynamicWidth()) {
  800. width = renderSpace.getWidth();
  801. } else {
  802. /*
  803. * If the tabbar is wider than the content we need to use the tabbar
  804. * width as minimum width so scrollbars get placed correctly (at the
  805. * right edge).
  806. */
  807. minWidth = tb.getOffsetWidth() - getContentAreaBorderWidth();
  808. }
  809. tp.fixVisibleTabSize(width, height, minWidth);
  810. }
  811. /**
  812. * Layouts the tab-scroller elements, and applies styles.
  813. */
  814. private void updateTabScroller() {
  815. if (width != null) {
  816. DOM.setStyleAttribute(tabs, "width", width);
  817. }
  818. // Make sure scrollerIndex is valid
  819. if (scrollerIndex < 0 || scrollerIndex > tb.getTabCount()) {
  820. scrollerIndex = tb.getFirstVisibleTab();
  821. } else if (tb.getTabCount() > 0
  822. && tb.getTab(scrollerIndex).isHiddenOnServer()) {
  823. scrollerIndex = tb.getNextVisibleTab(scrollerIndex);
  824. }
  825. boolean scrolled = isScrolledTabs();
  826. boolean clipped = isClippedTabs();
  827. if (tb.getTabCount() > 0 && tb.isVisible() && (scrolled || clipped)) {
  828. DOM.setStyleAttribute(scroller, "display", "");
  829. DOM.setElementProperty(scrollerPrev, "className",
  830. SCROLLER_CLASSNAME + (scrolled ? "Prev" : "Prev-disabled"));
  831. DOM.setElementProperty(scrollerNext, "className",
  832. SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled"));
  833. } else {
  834. DOM.setStyleAttribute(scroller, "display", "none");
  835. }
  836. if (BrowserInfo.get().isSafari()) {
  837. // fix tab height for safari, bugs sometimes if tabs contain icons
  838. String property = tabs.getStyle().getProperty("height");
  839. if (property == null || property.equals("")) {
  840. tabs.getStyle().setPropertyPx("height", tb.getOffsetHeight());
  841. }
  842. /*
  843. * another hack for webkits. tabscroller sometimes drops without
  844. * "shaking it" reproducable in
  845. * com.vaadin.tests.components.tabsheet.TabSheetIcons
  846. */
  847. final Style style = scroller.getStyle();
  848. style.setProperty("whiteSpace", "normal");
  849. Scheduler.get().scheduleDeferred(new Command() {
  850. public void execute() {
  851. style.setProperty("whiteSpace", "");
  852. }
  853. });
  854. }
  855. }
  856. void showAllTabs() {
  857. scrollerIndex = tb.getFirstVisibleTab();
  858. for (int i = 0; i < tb.getTabCount(); i++) {
  859. Tab t = tb.getTab(i);
  860. if (!t.isHiddenOnServer()) {
  861. t.setVisible(true);
  862. }
  863. }
  864. }
  865. private boolean isScrolledTabs() {
  866. return scrollerIndex > tb.getFirstVisibleTab();
  867. }
  868. private boolean isClippedTabs() {
  869. return (tb.getOffsetWidth() - DOM.getElementPropertyInt((Element) tb
  870. .getContainerElement().getLastChild().cast(), "offsetWidth")) > getOffsetWidth()
  871. - (isScrolledTabs() ? scroller.getOffsetWidth() : 0);
  872. }
  873. @Override
  874. protected void clearPaintables() {
  875. int i = tb.getTabCount();
  876. while (i > 0) {
  877. tb.removeTab(--i);
  878. }
  879. tp.clear();
  880. }
  881. @Override
  882. protected Iterator<Widget> getWidgetIterator() {
  883. return tp.iterator();
  884. }
  885. public boolean hasChildComponent(Widget component) {
  886. if (tp.getWidgetIndex(component) < 0) {
  887. return false;
  888. } else {
  889. return true;
  890. }
  891. }
  892. public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
  893. tp.replaceComponent(oldComponent, newComponent);
  894. }
  895. public boolean requestLayout(Set<Widget> children) {
  896. if (!isDynamicHeight() && !isDynamicWidth()) {
  897. /*
  898. * If the height and width has been specified for this container the
  899. * child components cannot make the size of the layout change
  900. */
  901. // layout size change may affect its available space (scrollbars)
  902. for (Widget widget : children) {
  903. client.handleComponentRelativeSize(widget);
  904. }
  905. return true;
  906. }
  907. updateOpenTabSize();
  908. if (renderInformation.updateSize(getElement())) {
  909. /*
  910. * Size has changed so we let the child components know about the
  911. * new size.
  912. */
  913. iLayout();
  914. client.runDescendentsLayout(this);
  915. return false;
  916. } else {
  917. /*
  918. * Size has not changed so we do not need to propagate the event
  919. * further
  920. */
  921. return true;
  922. }
  923. }
  924. private int borderW = -1;
  925. private int getContentAreaBorderWidth() {
  926. if (borderW < 0) {
  927. borderW = Util.measureHorizontalBorder(contentNode);
  928. }
  929. return borderW;
  930. }
  931. private final RenderSpace renderSpace = new RenderSpace(0, 0, true);
  932. public RenderSpace getAllocatedSpace(Widget child) {
  933. // All tabs have equal amount of space allocated
  934. return renderSpace;
  935. }
  936. @Override
  937. protected int getTabCount() {
  938. return tb.getTabCount();
  939. }
  940. @Override
  941. protected VPaintableWidget getTab(int index) {
  942. if (tp.getWidgetCount() > index) {
  943. Widget widget = tp.getWidget(index);
  944. return VPaintableMap.get(client).getPaintable(widget);
  945. }
  946. return null;
  947. }
  948. @Override
  949. protected void removeTab(int index) {
  950. tb.removeTab(index);
  951. /*
  952. * This must be checked because renderTab automatically removes the
  953. * active tab content when it changes
  954. */
  955. if (tp.getWidgetCount() > index) {
  956. tp.remove(index);
  957. }
  958. }
  959. }