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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.HashMap;
  6. import java.util.Iterator;
  7. import java.util.Set;
  8. import com.google.gwt.dom.client.Style;
  9. import com.google.gwt.user.client.Command;
  10. import com.google.gwt.user.client.DOM;
  11. import com.google.gwt.user.client.DeferredCommand;
  12. import com.google.gwt.user.client.Element;
  13. import com.google.gwt.user.client.Event;
  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.Widget;
  17. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  18. import com.vaadin.terminal.gwt.client.BrowserInfo;
  19. import com.vaadin.terminal.gwt.client.VCaption;
  20. import com.vaadin.terminal.gwt.client.Paintable;
  21. import com.vaadin.terminal.gwt.client.RenderInformation;
  22. import com.vaadin.terminal.gwt.client.RenderSpace;
  23. import com.vaadin.terminal.gwt.client.UIDL;
  24. import com.vaadin.terminal.gwt.client.Util;
  25. public class VTabsheet extends VTabsheetBase {
  26. private class TabSheetCaption extends VCaption {
  27. TabSheetCaption() {
  28. super(null, client);
  29. }
  30. @Override
  31. public void onBrowserEvent(Event event) {
  32. super.onBrowserEvent(event);
  33. if (event.getTypeInt() == Event.ONLOAD) {
  34. // icon onloads may change total width of tabsheet
  35. if (isDynamicWidth()) {
  36. updateDynamicWidth();
  37. }
  38. updateTabScroller();
  39. }
  40. }
  41. @Override
  42. public void setWidth(String width) {
  43. super.setWidth(width);
  44. if (BrowserInfo.get().isIE7()) {
  45. /*
  46. * IE7 apparently has problems with calculating width for
  47. * floated elements inside a DIV with padding. Set the width
  48. * explicitly for the caption.
  49. */
  50. fixTextWidth();
  51. }
  52. }
  53. private void fixTextWidth() {
  54. Element captionText = getTextElement();
  55. int captionWidth = Util.getRequiredWidth(captionText);
  56. int scrollWidth = captionText.getScrollWidth();
  57. if (scrollWidth > captionWidth) {
  58. captionWidth = scrollWidth;
  59. }
  60. captionText.getStyle().setPropertyPx("width", captionWidth);
  61. }
  62. }
  63. class TabBar extends ComplexPanel implements ClickListener {
  64. private Element tr = DOM.createTR();
  65. private Element spacerTd = DOM.createTD();
  66. TabBar() {
  67. Element el = DOM.createTable();
  68. Element tbody = DOM.createTBody();
  69. DOM.appendChild(el, tbody);
  70. DOM.appendChild(tbody, tr);
  71. setStyleName(spacerTd, CLASSNAME + "-spacertd");
  72. DOM.appendChild(tr, spacerTd);
  73. DOM.appendChild(spacerTd, DOM.createDiv());
  74. setElement(el);
  75. }
  76. protected Element getContainerElement() {
  77. return tr;
  78. }
  79. private Widget oldSelected;
  80. public int getTabCount() {
  81. return getWidgetCount();
  82. }
  83. public void addTab(VCaption c) {
  84. Element td = DOM.createTD();
  85. setStyleName(td, CLASSNAME + "-tabitemcell");
  86. if (getWidgetCount() == 0) {
  87. setStyleName(td, CLASSNAME + "-tabitemcell-first", true);
  88. }
  89. Element div = DOM.createDiv();
  90. setStyleName(div, CLASSNAME + "-tabitem");
  91. DOM.appendChild(td, div);
  92. DOM.insertBefore(tr, td, spacerTd);
  93. c.addClickListener(this);
  94. add(c, div);
  95. }
  96. public void onClick(Widget sender) {
  97. int index = getWidgetIndex(sender);
  98. onTabSelected(index);
  99. }
  100. public void selectTab(int index) {
  101. Widget newSelected = getWidget(index);
  102. Widget.setStyleName(DOM.getParent(newSelected.getElement()),
  103. CLASSNAME + "-tabitem-selected", true);
  104. if (oldSelected != null && oldSelected != newSelected) {
  105. Widget.setStyleName(DOM.getParent(oldSelected.getElement()),
  106. CLASSNAME + "-tabitem-selected", false);
  107. }
  108. oldSelected = newSelected;
  109. }
  110. public void removeTab(int i) {
  111. Widget w = getWidget(i);
  112. if (w == null) {
  113. return;
  114. }
  115. Element caption = w.getElement();
  116. Element div = DOM.getParent(caption);
  117. Element td = DOM.getParent(div);
  118. Element tr = DOM.getParent(td);
  119. remove(w);
  120. /*
  121. * Widget is the Caption but we want to remove everything up to and
  122. * including the parent TD
  123. */
  124. DOM.removeChild(tr, td);
  125. /*
  126. * If this widget was selected we need to unmark it as the last
  127. * selected
  128. */
  129. if (w == oldSelected) {
  130. oldSelected = null;
  131. }
  132. }
  133. @Override
  134. public boolean remove(Widget w) {
  135. ((VCaption) w).removeClickListener(this);
  136. return super.remove(w);
  137. }
  138. public TabSheetCaption getTab(int index) {
  139. if (index >= getWidgetCount()) {
  140. return null;
  141. }
  142. return (TabSheetCaption) getWidget(index);
  143. }
  144. public void setVisible(int index, boolean visible) {
  145. Element e = DOM.getParent(getTab(index).getElement());
  146. if (visible) {
  147. DOM.setStyleAttribute(e, "display", "");
  148. } else {
  149. DOM.setStyleAttribute(e, "display", "none");
  150. }
  151. }
  152. public void updateCaptionSize(int index) {
  153. VCaption c = getTab(index);
  154. c.setWidth(c.getRequiredWidth() + "px");
  155. }
  156. }
  157. public static final String CLASSNAME = "i-tabsheet";
  158. public static final String TABS_CLASSNAME = "i-tabsheet-tabcontainer";
  159. public static final String SCROLLER_CLASSNAME = "i-tabsheet-scroller";
  160. private final Element tabs; // tabbar and 'scroller' container
  161. private final Element scroller; // tab-scroller element
  162. private final Element scrollerNext; // tab-scroller next button element
  163. private final Element scrollerPrev; // tab-scroller prev button element
  164. private int scrollerIndex = 0;
  165. private final TabBar tb = new TabBar();
  166. private final VTabsheetPanel tp = new VTabsheetPanel();
  167. private final Element contentNode, deco;
  168. private final HashMap<String, VCaption> captions = new HashMap<String, VCaption>();
  169. private String height;
  170. private String width;
  171. private boolean waitingForResponse;
  172. private RenderInformation renderInformation = new RenderInformation();
  173. /**
  174. * Previous visible widget is set invisible with CSS (not display: none, but
  175. * visibility: hidden), to avoid flickering during render process. Normal
  176. * visibility must be returned later when new widget is rendered.
  177. */
  178. private Widget previousVisibleWidget;
  179. private boolean rendering = false;
  180. private void onTabSelected(final int tabIndex) {
  181. if (disabled || waitingForResponse) {
  182. return;
  183. }
  184. final Object tabKey = tabKeys.get(tabIndex);
  185. if (disabledTabKeys.contains(tabKey)) {
  186. return;
  187. }
  188. if (client != null && activeTabIndex != tabIndex) {
  189. tb.selectTab(tabIndex);
  190. addStyleDependentName("loading");
  191. // run updating variables in deferred command to bypass some FF
  192. // optimization issues
  193. DeferredCommand.addCommand(new Command() {
  194. public void execute() {
  195. previousVisibleWidget = tp.getWidget(tp.getVisibleWidget());
  196. DOM.setStyleAttribute(DOM.getParent(previousVisibleWidget
  197. .getElement()), "visibility", "hidden");
  198. client.updateVariable(id, "selected", tabKeys.get(tabIndex)
  199. .toString(), true);
  200. }
  201. });
  202. waitingForResponse = true;
  203. }
  204. }
  205. private boolean isDynamicWidth() {
  206. return width == null || width.equals("");
  207. }
  208. private boolean isDynamicHeight() {
  209. return height == null || height.equals("");
  210. }
  211. public VTabsheet() {
  212. super(CLASSNAME);
  213. // Tab scrolling
  214. DOM.setStyleAttribute(getElement(), "overflow", "hidden");
  215. tabs = DOM.createDiv();
  216. DOM.setElementProperty(tabs, "className", TABS_CLASSNAME);
  217. scroller = DOM.createDiv();
  218. DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME);
  219. scrollerPrev = DOM.createButton();
  220. DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME
  221. + "Prev");
  222. DOM.sinkEvents(scrollerPrev, Event.ONCLICK);
  223. scrollerNext = DOM.createButton();
  224. DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME
  225. + "Next");
  226. DOM.sinkEvents(scrollerNext, Event.ONCLICK);
  227. DOM.appendChild(getElement(), tabs);
  228. // Tabs
  229. tp.setStyleName(CLASSNAME + "-tabsheetpanel");
  230. contentNode = DOM.createDiv();
  231. deco = DOM.createDiv();
  232. addStyleDependentName("loading"); // Indicate initial progress
  233. tb.setStyleName(CLASSNAME + "-tabs");
  234. DOM
  235. .setElementProperty(contentNode, "className", CLASSNAME
  236. + "-content");
  237. DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
  238. add(tb, tabs);
  239. DOM.appendChild(scroller, scrollerPrev);
  240. DOM.appendChild(scroller, scrollerNext);
  241. DOM.appendChild(getElement(), contentNode);
  242. add(tp, contentNode);
  243. DOM.appendChild(getElement(), deco);
  244. DOM.appendChild(tabs, scroller);
  245. // TODO Use for Safari only. Fix annoying 1px first cell in TabBar.
  246. // DOM.setStyleAttribute(DOM.getFirstChild(DOM.getFirstChild(DOM
  247. // .getFirstChild(tb.getElement()))), "display", "none");
  248. }
  249. @Override
  250. public void onBrowserEvent(Event event) {
  251. // Tab scrolling
  252. if (isScrolledTabs() && DOM.eventGetTarget(event) == scrollerPrev) {
  253. if (scrollerIndex > 0) {
  254. scrollerIndex--;
  255. DOM.setStyleAttribute(DOM.getChild(DOM.getFirstChild(DOM
  256. .getFirstChild(tb.getElement())), scrollerIndex),
  257. "display", "");
  258. tb.updateCaptionSize(scrollerIndex);
  259. updateTabScroller();
  260. }
  261. } else if (isClippedTabs() && DOM.eventGetTarget(event) == scrollerNext) {
  262. int tabs = tb.getTabCount();
  263. if (scrollerIndex + 1 <= tabs) {
  264. DOM.setStyleAttribute(DOM.getChild(DOM.getFirstChild(DOM
  265. .getFirstChild(tb.getElement())), scrollerIndex),
  266. "display", "none");
  267. tb.updateCaptionSize(scrollerIndex);
  268. scrollerIndex++;
  269. updateTabScroller();
  270. }
  271. } else {
  272. super.onBrowserEvent(event);
  273. }
  274. }
  275. @Override
  276. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  277. rendering = true;
  278. super.updateFromUIDL(uidl, client);
  279. if (cachedUpdate) {
  280. return;
  281. }
  282. // Add proper stylenames for all elements (easier to prevent unwanted
  283. // style inheritance)
  284. if (uidl.hasAttribute("style")) {
  285. final String[] styles = uidl.getStringAttribute("style").split(" ");
  286. final String contentBaseClass = CLASSNAME + "-content";
  287. String contentClass = contentBaseClass;
  288. final String decoBaseClass = CLASSNAME + "-deco";
  289. String decoClass = decoBaseClass;
  290. for (int i = 0; i < styles.length; i++) {
  291. tb.addStyleDependentName(styles[i]);
  292. contentClass += " " + contentBaseClass + "-" + styles[i];
  293. decoClass += " " + decoBaseClass + "-" + styles[i];
  294. }
  295. DOM.setElementProperty(contentNode, "className", contentClass);
  296. DOM.setElementProperty(deco, "className", decoClass);
  297. } else {
  298. tb.setStyleName(CLASSNAME + "-tabs");
  299. DOM.setElementProperty(contentNode, "className", CLASSNAME
  300. + "-content");
  301. DOM.setElementProperty(deco, "className", CLASSNAME + "-deco");
  302. }
  303. if (uidl.hasAttribute("hidetabs")) {
  304. tb.setVisible(false);
  305. addStyleName(CLASSNAME + "-hidetabs");
  306. } else {
  307. tb.setVisible(true);
  308. removeStyleName(CLASSNAME + "-hidetabs");
  309. }
  310. // tabs; push or not
  311. if (!isDynamicWidth()) {
  312. // FIXME: This makes tab sheet tabs go to 1px width on every update
  313. // and then back to original width
  314. // update width later, in updateTabScroller();
  315. DOM.setStyleAttribute(tabs, "width", "1px");
  316. DOM.setStyleAttribute(tabs, "overflow", "hidden");
  317. } else {
  318. showAllTabs();
  319. DOM.setStyleAttribute(tabs, "width", "");
  320. DOM.setStyleAttribute(tabs, "overflow", "visible");
  321. updateDynamicWidth();
  322. }
  323. if (!isDynamicHeight()) {
  324. // Must update height after the styles have been set
  325. updateContentNodeHeight();
  326. updateOpenTabSize();
  327. }
  328. iLayout();
  329. // Re run relative size update to ensure optimal scrollbars
  330. // TODO isolate to situation that visible tab has undefined height
  331. try {
  332. client.handleComponentRelativeSize(tp.getWidget(tp
  333. .getVisibleWidget()));
  334. } catch (Exception e) {
  335. // Ignore, most likely empty tabsheet
  336. }
  337. renderInformation.updateSize(getElement());
  338. waitingForResponse = false;
  339. rendering = false;
  340. }
  341. private void updateDynamicWidth() {
  342. // Find tab width
  343. int tabsWidth = 0;
  344. int count = tb.getTabCount();
  345. for (int i = 0; i < count; i++) {
  346. Element tabTd = tb.getTab(i).getElement().getParentElement().cast();
  347. tabsWidth += tabTd.getOffsetWidth();
  348. }
  349. // Find content width
  350. Style style = tp.getElement().getStyle();
  351. String overflow = style.getProperty("overflow");
  352. style.setProperty("overflow", "hidden");
  353. style.setPropertyPx("width", tabsWidth);
  354. Style wrapperstyle = tp.getWidget(tp.getVisibleWidget()).getElement()
  355. .getParentElement().getStyle();
  356. wrapperstyle.setPropertyPx("width", tabsWidth);
  357. // Get content width from actual widget
  358. int contentWidth = 0;
  359. if (tp.getWidgetCount() > 0) {
  360. contentWidth = tp.getWidget(tp.getVisibleWidget()).getOffsetWidth();
  361. }
  362. style.setProperty("overflow", overflow);
  363. // Set widths to max(tabs,content)
  364. if (tabsWidth < contentWidth) {
  365. tabsWidth = contentWidth;
  366. }
  367. int outerWidth = tabsWidth + getContentAreaBorderWidth();
  368. tabs.getStyle().setPropertyPx("width", outerWidth);
  369. style.setPropertyPx("width", tabsWidth);
  370. wrapperstyle.setPropertyPx("width", tabsWidth);
  371. contentNode.getStyle().setPropertyPx("width", tabsWidth);
  372. super.setWidth(outerWidth + "px");
  373. updateOpenTabSize();
  374. }
  375. @Override
  376. protected void renderTab(final UIDL tabUidl, int index, boolean selected,
  377. boolean hidden) {
  378. TabSheetCaption c = tb.getTab(index);
  379. if (c == null) {
  380. c = new TabSheetCaption();
  381. tb.addTab(c);
  382. }
  383. c.updateCaption(tabUidl);
  384. tb.setVisible(index, !hidden);
  385. /*
  386. * Force the width of the caption container so the content will not wrap
  387. * and tabs won't be too narrow in certain browsers
  388. */
  389. c.setWidth(c.getRequiredWidth() + "px");
  390. captions.put("" + index, c);
  391. UIDL tabContentUIDL = null;
  392. Paintable tabContent = null;
  393. if (tabUidl.getChildCount() > 0) {
  394. tabContentUIDL = tabUidl.getChildUIDL(0);
  395. tabContent = client.getPaintable(tabContentUIDL);
  396. }
  397. if (tabContent != null) {
  398. /* This is a tab with content information */
  399. int oldIndex = tp.getWidgetIndex((Widget) tabContent);
  400. if (oldIndex != -1 && oldIndex != index) {
  401. /*
  402. * The tab has previously been rendered in another position so
  403. * we must move the cached content to correct position
  404. */
  405. tp.insert((Widget) tabContent, index);
  406. }
  407. } else {
  408. /* A tab whose content has not yet been loaded */
  409. /*
  410. * Make sure there is a corresponding empty tab in tp. The same
  411. * operation as the moving above but for not-loaded tabs.
  412. */
  413. if (index < tp.getWidgetCount()) {
  414. Widget oldWidget = tp.getWidget(index);
  415. if (!(oldWidget instanceof PlaceHolder)) {
  416. tp.insert(new PlaceHolder(), index);
  417. }
  418. }
  419. }
  420. if (selected) {
  421. renderContent(tabContentUIDL);
  422. tb.selectTab(index);
  423. } else {
  424. if (tabContentUIDL != null) {
  425. // updating a drawn child on hidden tab
  426. if (tp.getWidgetIndex((Widget) tabContent) < 0) {
  427. tp.insert((Widget) tabContent, index);
  428. }
  429. tabContent.updateFromUIDL(tabContentUIDL, client);
  430. } else if (tp.getWidgetCount() <= index) {
  431. tp.add(new PlaceHolder());
  432. }
  433. }
  434. }
  435. public class PlaceHolder extends VLabel {
  436. public PlaceHolder() {
  437. super("");
  438. }
  439. }
  440. @Override
  441. protected void selectTab(int index, final UIDL contentUidl) {
  442. if (index != activeTabIndex) {
  443. activeTabIndex = index;
  444. tb.selectTab(activeTabIndex);
  445. }
  446. renderContent(contentUidl);
  447. }
  448. private void renderContent(final UIDL contentUIDL) {
  449. final Paintable content = client.getPaintable(contentUIDL);
  450. if (tp.getWidgetCount() > activeTabIndex) {
  451. Widget old = tp.getWidget(activeTabIndex);
  452. if (old != content) {
  453. tp.remove(activeTabIndex);
  454. if (old instanceof Paintable) {
  455. client.unregisterPaintable((Paintable) old);
  456. }
  457. tp.insert((Widget) content, activeTabIndex);
  458. }
  459. } else {
  460. tp.add((Widget) content);
  461. }
  462. tp.showWidget(activeTabIndex);
  463. VTabsheet.this.iLayout();
  464. (content).updateFromUIDL(contentUIDL, client);
  465. /*
  466. * The size of a cached, relative sized component must be updated to
  467. * report correct size to updateOpenTabSize().
  468. */
  469. if (contentUIDL.getBooleanAttribute("cached")) {
  470. client.handleComponentRelativeSize((Widget) content);
  471. }
  472. updateOpenTabSize();
  473. VTabsheet.this.removeStyleDependentName("loading");
  474. if (previousVisibleWidget != null) {
  475. DOM.setStyleAttribute(previousVisibleWidget.getElement(),
  476. "visibility", "");
  477. previousVisibleWidget = null;
  478. }
  479. }
  480. @Override
  481. public void setHeight(String height) {
  482. super.setHeight(height);
  483. this.height = height;
  484. updateContentNodeHeight();
  485. if (!rendering) {
  486. updateOpenTabSize();
  487. iLayout();
  488. // TODO Check if this is needed
  489. client.runDescendentsLayout(this);
  490. }
  491. }
  492. private void updateContentNodeHeight() {
  493. if (height != null && !"".equals(height)) {
  494. int contentHeight = getOffsetHeight();
  495. contentHeight -= DOM.getElementPropertyInt(deco, "offsetHeight");
  496. contentHeight -= tb.getOffsetHeight();
  497. if (contentHeight < 0) {
  498. contentHeight = 0;
  499. }
  500. // Set proper values for content element
  501. DOM.setStyleAttribute(contentNode, "height", contentHeight + "px");
  502. renderSpace.setHeight(contentHeight);
  503. } else {
  504. DOM.setStyleAttribute(contentNode, "height", "");
  505. renderSpace.setHeight(0);
  506. }
  507. }
  508. @Override
  509. public void setWidth(String width) {
  510. if ((this.width == null && width.equals(""))
  511. || (this.width != null && this.width.equals(width))) {
  512. return;
  513. }
  514. super.setWidth(width);
  515. if (width.equals("")) {
  516. width = null;
  517. }
  518. this.width = width;
  519. if (width == null) {
  520. renderSpace.setWidth(0);
  521. contentNode.getStyle().setProperty("width", "");
  522. } else {
  523. int contentWidth = getOffsetWidth() - getContentAreaBorderWidth();
  524. if (contentWidth < 0) {
  525. contentWidth = 0;
  526. }
  527. contentNode.getStyle().setProperty("width", contentWidth + "px");
  528. renderSpace.setWidth(contentWidth);
  529. }
  530. if (!rendering) {
  531. if (isDynamicHeight()) {
  532. Util.updateRelativeChildrenAndSendSizeUpdateEvent(client, tp,
  533. this);
  534. }
  535. updateOpenTabSize();
  536. iLayout();
  537. // TODO Check if this is needed
  538. client.runDescendentsLayout(this);
  539. }
  540. }
  541. public void iLayout() {
  542. updateTabScroller();
  543. tp.runWebkitOverflowAutoFix();
  544. }
  545. /**
  546. * Sets the size of the visible tab (component). As the tab is set to
  547. * position: absolute (to work around a firefox flickering bug) we must keep
  548. * this up-to-date by hand.
  549. */
  550. private void updateOpenTabSize() {
  551. /*
  552. * The overflow=auto element must have a height specified, otherwise it
  553. * will be just as high as the contents and no scrollbars will appear
  554. */
  555. int height = -1;
  556. int width = -1;
  557. int minWidth = 0;
  558. if (!isDynamicHeight()) {
  559. height = renderSpace.getHeight();
  560. }
  561. if (!isDynamicWidth()) {
  562. width = renderSpace.getWidth();
  563. } else {
  564. /*
  565. * If the tabbar is wider than the content we need to use the tabbar
  566. * width as minimum width so scrollbars get placed correctly (at the
  567. * right edge).
  568. */
  569. minWidth = tb.getOffsetWidth() - getContentAreaBorderWidth();
  570. }
  571. tp.fixVisibleTabSize(width, height, minWidth);
  572. }
  573. /**
  574. * Layouts the tab-scroller elements, and applies styles.
  575. */
  576. private void updateTabScroller() {
  577. if (width != null) {
  578. DOM.setStyleAttribute(tabs, "width", width);
  579. }
  580. if (scrollerIndex > tb.getTabCount()) {
  581. scrollerIndex = 0;
  582. }
  583. boolean scrolled = isScrolledTabs();
  584. boolean clipped = isClippedTabs();
  585. if (tb.isVisible() && (scrolled || clipped)) {
  586. DOM.setStyleAttribute(scroller, "display", "");
  587. DOM.setElementProperty(scrollerPrev, "className",
  588. SCROLLER_CLASSNAME + (scrolled ? "Prev" : "Prev-disabled"));
  589. DOM.setElementProperty(scrollerNext, "className",
  590. SCROLLER_CLASSNAME + (clipped ? "Next" : "Next-disabled"));
  591. } else {
  592. DOM.setStyleAttribute(scroller, "display", "none");
  593. }
  594. if (BrowserInfo.get().isSafari()) {
  595. // fix tab height for safari, bugs sometimes if tabs contain icons
  596. String property = tabs.getStyle().getProperty("height");
  597. if (property == null || property.equals("")) {
  598. tabs.getStyle().setPropertyPx("height", tb.getOffsetHeight());
  599. }
  600. /*
  601. * another hack for webkits. tabscroller sometimes drops without
  602. * "shaking it" reproducable in
  603. * com.vaadin.tests.components.tabsheet.TabSheetIcons
  604. */
  605. final Style style = scroller.getStyle();
  606. style.setProperty("whiteSpace", "normal");
  607. DeferredCommand.addCommand(new Command() {
  608. public void execute() {
  609. style.setProperty("whiteSpace", "");
  610. }
  611. });
  612. }
  613. }
  614. private void showAllTabs() {
  615. scrollerIndex = 0;
  616. Element tr = DOM.getFirstChild(DOM.getFirstChild(tb.getElement()));
  617. for (int i = 0; i < tb.getTabCount(); i++) {
  618. DOM.setStyleAttribute(DOM.getChild(tr, i), "display", "");
  619. }
  620. }
  621. private boolean isScrolledTabs() {
  622. return scrollerIndex > 0;
  623. }
  624. private boolean isClippedTabs() {
  625. return tb.getOffsetWidth() > getOffsetWidth();
  626. }
  627. @Override
  628. protected void clearPaintables() {
  629. int i = tb.getTabCount();
  630. while (i > 0) {
  631. tb.removeTab(--i);
  632. }
  633. tp.clear();
  634. }
  635. @Override
  636. protected Iterator getPaintableIterator() {
  637. return tp.iterator();
  638. }
  639. public boolean hasChildComponent(Widget component) {
  640. if (tp.getWidgetIndex(component) < 0) {
  641. return false;
  642. } else {
  643. return true;
  644. }
  645. }
  646. public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
  647. tp.replaceComponent(oldComponent, newComponent);
  648. }
  649. public void updateCaption(Paintable component, UIDL uidl) {
  650. /* Tabsheet does not render its children's captions */
  651. }
  652. public boolean requestLayout(Set<Paintable> child) {
  653. if (!isDynamicHeight() && !isDynamicWidth()) {
  654. /*
  655. * If the height and width has been specified for this container the
  656. * child components cannot make the size of the layout change
  657. */
  658. return true;
  659. }
  660. updateOpenTabSize();
  661. if (renderInformation.updateSize(getElement())) {
  662. /*
  663. * Size has changed so we let the child components know about the
  664. * new size.
  665. */
  666. iLayout();
  667. client.runDescendentsLayout(this);
  668. return false;
  669. } else {
  670. /*
  671. * Size has not changed so we do not need to propagate the event
  672. * further
  673. */
  674. return true;
  675. }
  676. }
  677. private int borderW = -1;
  678. private int getContentAreaBorderWidth() {
  679. if (borderW < 0) {
  680. borderW = Util.measureHorizontalBorder(contentNode);
  681. }
  682. return borderW;
  683. }
  684. private RenderSpace renderSpace = new RenderSpace(0, 0, true);
  685. public RenderSpace getAllocatedSpace(Widget child) {
  686. // All tabs have equal amount of space allocated
  687. return renderSpace;
  688. }
  689. @Override
  690. protected int getTabCount() {
  691. return tb.getWidgetCount();
  692. }
  693. @Override
  694. protected Paintable getTab(int index) {
  695. if (tp.getWidgetCount() > index) {
  696. return (Paintable) tp.getWidget(index);
  697. }
  698. return null;
  699. }
  700. @Override
  701. protected void removeTab(int index) {
  702. tb.removeTab(index);
  703. /*
  704. * This must be checked because renderTab automatically removes the
  705. * active tab content when it changes
  706. */
  707. if (tp.getWidgetCount() > index) {
  708. tp.remove(index);
  709. }
  710. }
  711. }