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

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