選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

VAccordion.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. package com.vaadin.terminal.gwt.client.ui;
  2. import java.util.HashMap;
  3. import java.util.HashSet;
  4. import java.util.Iterator;
  5. import java.util.Set;
  6. import com.google.gwt.user.client.DOM;
  7. import com.google.gwt.user.client.Element;
  8. import com.google.gwt.user.client.Event;
  9. import com.google.gwt.user.client.ui.ClickListener;
  10. import com.google.gwt.user.client.ui.ComplexPanel;
  11. import com.google.gwt.user.client.ui.Widget;
  12. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  13. import com.vaadin.terminal.gwt.client.BrowserInfo;
  14. import com.vaadin.terminal.gwt.client.ContainerResizedListener;
  15. import com.vaadin.terminal.gwt.client.VCaption;
  16. import com.vaadin.terminal.gwt.client.Paintable;
  17. import com.vaadin.terminal.gwt.client.RenderInformation;
  18. import com.vaadin.terminal.gwt.client.RenderSpace;
  19. import com.vaadin.terminal.gwt.client.UIDL;
  20. import com.vaadin.terminal.gwt.client.Util;
  21. public class VAccordion extends VTabsheetBase implements
  22. ContainerResizedListener {
  23. public static final String CLASSNAME = "i-accordion";
  24. private Set<Paintable> paintables = new HashSet<Paintable>();
  25. private String height;
  26. private String width = "";
  27. private HashMap<StackItem, UIDL> lazyUpdateMap = new HashMap<StackItem, UIDL>();
  28. private RenderSpace renderSpace = new RenderSpace(0, 0, true);
  29. private StackItem openTab = null;
  30. private boolean rendering = false;
  31. private int selectedUIDLItemIndex = -1;
  32. private RenderInformation renderInformation = new RenderInformation();
  33. public VAccordion() {
  34. super(CLASSNAME);
  35. // IE6 needs this to calculate offsetHeight correctly
  36. if (BrowserInfo.get().isIE6()) {
  37. DOM.setStyleAttribute(getElement(), "zoom", "1");
  38. }
  39. }
  40. @Override
  41. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  42. rendering = true;
  43. selectedUIDLItemIndex = -1;
  44. super.updateFromUIDL(uidl, client);
  45. /*
  46. * Render content after all tabs have been created and we know how large
  47. * the content area is
  48. */
  49. if (selectedUIDLItemIndex >= 0) {
  50. StackItem selectedItem = getStackItem(selectedUIDLItemIndex);
  51. UIDL selectedTabUIDL = lazyUpdateMap.remove(selectedItem);
  52. open(selectedUIDLItemIndex);
  53. selectedItem.setContent(selectedTabUIDL);
  54. } else if (!uidl.getBooleanAttribute("cached") && openTab != null) {
  55. close(openTab);
  56. }
  57. iLayout();
  58. // finally render possible hidden tabs
  59. if (lazyUpdateMap.size() > 0) {
  60. for (Iterator iterator = lazyUpdateMap.keySet().iterator(); iterator
  61. .hasNext();) {
  62. StackItem item = (StackItem) iterator.next();
  63. item.setContent(lazyUpdateMap.get(item));
  64. }
  65. lazyUpdateMap.clear();
  66. }
  67. renderInformation.updateSize(getElement());
  68. rendering = false;
  69. }
  70. @Override
  71. protected void renderTab(UIDL tabUidl, int index, boolean selected,
  72. boolean hidden) {
  73. StackItem item;
  74. int itemIndex;
  75. if (getWidgetCount() <= index) {
  76. // Create stackItem and render caption
  77. item = new StackItem(tabUidl);
  78. if (getWidgetCount() == 0) {
  79. item.addStyleDependentName("first");
  80. }
  81. itemIndex = getWidgetCount();
  82. add(item, getElement());
  83. } else {
  84. item = getStackItem(index);
  85. item = moveStackItemIfNeeded(item, index, tabUidl);
  86. itemIndex = index;
  87. }
  88. item.updateCaption(tabUidl);
  89. item.setVisible(!hidden);
  90. if (selected) {
  91. selectedUIDLItemIndex = itemIndex;
  92. }
  93. if (tabUidl.getChildCount() > 0) {
  94. lazyUpdateMap.put(item, tabUidl.getChildUIDL(0));
  95. }
  96. }
  97. /**
  98. * This method tries to find out if a tab has been rendered with a different
  99. * index previously. If this is the case it re-orders the children so the
  100. * same StackItem is used for rendering this time. E.g. if the first tab has
  101. * been removed all tabs which contain cached content must be moved 1 step
  102. * up to preserve the cached content.
  103. *
  104. * @param item
  105. * @param newIndex
  106. * @param tabUidl
  107. * @return
  108. */
  109. private StackItem moveStackItemIfNeeded(StackItem item, int newIndex,
  110. UIDL tabUidl) {
  111. UIDL tabContentUIDL = null;
  112. Paintable tabContent = null;
  113. if (tabUidl.getChildCount() > 0) {
  114. tabContentUIDL = tabUidl.getChildUIDL(0);
  115. tabContent = client.getPaintable(tabContentUIDL);
  116. }
  117. Widget itemWidget = item.getComponent();
  118. if (tabContent != null) {
  119. if (tabContent != itemWidget) {
  120. /*
  121. * This is not the same widget as before, find out if it has
  122. * been moved
  123. */
  124. int oldIndex = -1;
  125. StackItem oldItem = null;
  126. for (int i = 0; i < getWidgetCount(); i++) {
  127. Widget w = getWidget(i);
  128. oldItem = (StackItem) w;
  129. if (tabContent == oldItem.getComponent()) {
  130. oldIndex = i;
  131. break;
  132. }
  133. }
  134. if (oldIndex != -1 && oldIndex > newIndex) {
  135. /*
  136. * The tab has previously been rendered in another position
  137. * so we must move the cached content to correct position.
  138. * We move only items with oldIndex > newIndex to prevent
  139. * moving items already rendered in this update. If for
  140. * instance tabs 1,2,3 are removed and added as 3,2,1 we
  141. * cannot re-use "1" when we get to the third tab.
  142. */
  143. insert(oldItem, getElement(), newIndex, true);
  144. return oldItem;
  145. }
  146. }
  147. } else {
  148. // Tab which has never been loaded. Must assure we use an empty
  149. // StackItem
  150. Widget oldWidget = item.getComponent();
  151. if (oldWidget != null) {
  152. item = new StackItem(tabUidl);
  153. insert(item, getElement(), newIndex, true);
  154. }
  155. }
  156. return item;
  157. }
  158. private void open(int itemIndex) {
  159. StackItem item = (StackItem) getWidget(itemIndex);
  160. boolean alreadyOpen = false;
  161. if (openTab != null) {
  162. if (openTab.isOpen()) {
  163. if (openTab == item) {
  164. alreadyOpen = true;
  165. } else {
  166. openTab.close();
  167. }
  168. }
  169. }
  170. if (!alreadyOpen) {
  171. item.open();
  172. activeTabIndex = itemIndex;
  173. openTab = item;
  174. }
  175. // Update the size for the open tab
  176. updateOpenTabSize();
  177. }
  178. private void close(StackItem item) {
  179. if (!item.isOpen()) {
  180. return;
  181. }
  182. item.close();
  183. activeTabIndex = -1;
  184. openTab = null;
  185. }
  186. @Override
  187. protected void selectTab(final int index, final UIDL contentUidl) {
  188. StackItem item = getStackItem(index);
  189. if (index != activeTabIndex) {
  190. open(index);
  191. iLayout();
  192. // TODO Check if this is needed
  193. client.runDescendentsLayout(this);
  194. }
  195. item.setContent(contentUidl);
  196. }
  197. public void onSelectTab(StackItem item) {
  198. final int index = getWidgetIndex(item);
  199. if (index != activeTabIndex && !disabled && !readonly
  200. && !disabledTabKeys.contains(tabKeys.get(index))) {
  201. addStyleDependentName("loading");
  202. client
  203. .updateVariable(id, "selected", "" + tabKeys.get(index),
  204. true);
  205. }
  206. }
  207. @Override
  208. public void setWidth(String width) {
  209. if (this.width.equals(width)) {
  210. return;
  211. }
  212. super.setWidth(width);
  213. this.width = width;
  214. if (!rendering) {
  215. updateOpenTabSize();
  216. if (isDynamicHeight()) {
  217. Util.updateRelativeChildrenAndSendSizeUpdateEvent(client,
  218. openTab, this);
  219. updateOpenTabSize();
  220. }
  221. if (isDynamicHeight()) {
  222. openTab.setHeightFromWidget();
  223. }
  224. iLayout();
  225. }
  226. }
  227. @Override
  228. public void setHeight(String height) {
  229. super.setHeight(height);
  230. this.height = height;
  231. if (!rendering) {
  232. updateOpenTabSize();
  233. }
  234. }
  235. /**
  236. * Sets the size of the open tab
  237. */
  238. private void updateOpenTabSize() {
  239. if (openTab == null) {
  240. renderSpace.setHeight(0);
  241. renderSpace.setWidth(0);
  242. return;
  243. }
  244. // WIDTH
  245. if (!isDynamicWidth()) {
  246. int w = getOffsetWidth();
  247. openTab.setWidth(w);
  248. renderSpace.setWidth(w);
  249. } else {
  250. renderSpace.setWidth(0);
  251. }
  252. // HEIGHT
  253. if (!isDynamicHeight()) {
  254. int usedPixels = 0;
  255. for (Widget w : getChildren()) {
  256. StackItem item = (StackItem) w;
  257. if (item == openTab) {
  258. usedPixels += item.getCaptionHeight();
  259. } else {
  260. // This includes the captionNode borders
  261. usedPixels += item.getHeight();
  262. }
  263. }
  264. int offsetHeight = getOffsetHeight();
  265. int spaceForOpenItem = offsetHeight - usedPixels;
  266. if (spaceForOpenItem < 0) {
  267. spaceForOpenItem = 0;
  268. }
  269. renderSpace.setHeight(spaceForOpenItem);
  270. openTab.setHeight(spaceForOpenItem);
  271. } else {
  272. renderSpace.setHeight(0);
  273. openTab.setHeightFromWidget();
  274. }
  275. }
  276. public void iLayout() {
  277. if (openTab == null) {
  278. return;
  279. }
  280. if (isDynamicWidth()) {
  281. int maxWidth = 40;
  282. for (Widget w : getChildren()) {
  283. StackItem si = (StackItem) w;
  284. int captionWidth = si.getCaptionWidth();
  285. if (captionWidth > maxWidth) {
  286. maxWidth = captionWidth;
  287. }
  288. }
  289. int widgetWidth = openTab.getWidgetWidth();
  290. if (widgetWidth > maxWidth) {
  291. maxWidth = widgetWidth;
  292. }
  293. super.setWidth(maxWidth + "px");
  294. openTab.setWidth(maxWidth);
  295. }
  296. Util.runWebkitOverflowAutoFix(openTab.getContainerElement());
  297. }
  298. /**
  299. *
  300. */
  301. protected class StackItem extends ComplexPanel implements ClickListener {
  302. public void setHeight(int height) {
  303. if (height == -1) {
  304. super.setHeight("");
  305. DOM.setStyleAttribute(content, "height", "0px");
  306. } else {
  307. super.setHeight((height + getCaptionHeight()) + "px");
  308. DOM.setStyleAttribute(content, "height", height + "px");
  309. DOM
  310. .setStyleAttribute(content, "top", getCaptionHeight()
  311. + "px");
  312. }
  313. }
  314. public Widget getComponent() {
  315. if (getWidgetCount() < 2) {
  316. return null;
  317. }
  318. return getWidget(1);
  319. }
  320. @Override
  321. public void setVisible(boolean visible) {
  322. super.setVisible(visible);
  323. }
  324. public void setHeightFromWidget() {
  325. Widget paintable = getPaintable();
  326. if (paintable == null) {
  327. return;
  328. }
  329. int paintableHeight = (paintable).getElement().getOffsetHeight();
  330. setHeight(paintableHeight);
  331. }
  332. /**
  333. * Returns caption width including padding
  334. *
  335. * @return
  336. */
  337. public int getCaptionWidth() {
  338. if (caption == null) {
  339. return 0;
  340. }
  341. int captionWidth = caption.getRequiredWidth();
  342. int padding = Util.measureHorizontalPaddingAndBorder(caption
  343. .getElement(), 18);
  344. return captionWidth + padding;
  345. }
  346. public void setWidth(int width) {
  347. if (width == -1) {
  348. super.setWidth("");
  349. } else {
  350. super.setWidth(width + "px");
  351. }
  352. }
  353. public int getHeight() {
  354. return getOffsetHeight();
  355. }
  356. public int getCaptionHeight() {
  357. return captionNode.getOffsetHeight();
  358. }
  359. private VCaption caption;
  360. private boolean open = false;
  361. private Element content = DOM.createDiv();
  362. private Element captionNode = DOM.createDiv();
  363. public StackItem(UIDL tabUidl) {
  364. setElement(DOM.createDiv());
  365. caption = new VCaption(null, client);
  366. caption.addClickListener(this);
  367. if (BrowserInfo.get().isIE6()) {
  368. DOM.setEventListener(captionNode, this);
  369. DOM.sinkEvents(captionNode, Event.BUTTON_LEFT);
  370. }
  371. super.add(caption, captionNode);
  372. DOM.appendChild(captionNode, caption.getElement());
  373. DOM.appendChild(getElement(), captionNode);
  374. DOM.appendChild(getElement(), content);
  375. setStylePrimaryName(CLASSNAME + "-item");
  376. DOM.setElementProperty(content, "className", CLASSNAME
  377. + "-item-content");
  378. DOM.setElementProperty(captionNode, "className", CLASSNAME
  379. + "-item-caption");
  380. close();
  381. }
  382. @Override
  383. public void onBrowserEvent(Event event) {
  384. onSelectTab(this);
  385. }
  386. public Element getContainerElement() {
  387. return content;
  388. }
  389. public Widget getPaintable() {
  390. if (getWidgetCount() > 1) {
  391. return getWidget(1);
  392. } else {
  393. return null;
  394. }
  395. }
  396. public void replacePaintable(Paintable newPntbl) {
  397. if (getWidgetCount() > 1) {
  398. client.unregisterPaintable((Paintable) getWidget(1));
  399. paintables.remove(getWidget(1));
  400. remove(1);
  401. }
  402. add((Widget) newPntbl, content);
  403. paintables.add(newPntbl);
  404. }
  405. public void open() {
  406. open = true;
  407. DOM.setStyleAttribute(content, "top", getCaptionHeight() + "px");
  408. DOM.setStyleAttribute(content, "left", "0px");
  409. DOM.setStyleAttribute(content, "visibility", "");
  410. addStyleDependentName("open");
  411. }
  412. public void hide() {
  413. DOM.setStyleAttribute(content, "visibility", "hidden");
  414. }
  415. public void close() {
  416. DOM.setStyleAttribute(content, "visibility", "hidden");
  417. DOM.setStyleAttribute(content, "top", "-100000px");
  418. DOM.setStyleAttribute(content, "left", "-100000px");
  419. removeStyleDependentName("open");
  420. setHeight(-1);
  421. open = false;
  422. }
  423. public boolean isOpen() {
  424. return open;
  425. }
  426. public void setContent(UIDL contentUidl) {
  427. final Paintable newPntbl = client.getPaintable(contentUidl);
  428. if (getPaintable() == null) {
  429. add((Widget) newPntbl, content);
  430. paintables.add(newPntbl);
  431. } else if (getPaintable() != newPntbl) {
  432. replacePaintable(newPntbl);
  433. }
  434. newPntbl.updateFromUIDL(contentUidl, client);
  435. if (contentUidl.getBooleanAttribute("cached")) {
  436. /*
  437. * The size of a cached, relative sized component must be
  438. * updated to report correct size.
  439. */
  440. client.handleComponentRelativeSize((Widget) newPntbl);
  441. }
  442. if (isOpen() && isDynamicHeight()) {
  443. setHeightFromWidget();
  444. }
  445. }
  446. public void onClick(Widget sender) {
  447. onSelectTab(this);
  448. }
  449. public void updateCaption(UIDL uidl) {
  450. caption.updateCaption(uidl);
  451. }
  452. public int getWidgetWidth() {
  453. return DOM.getFirstChild(content).getOffsetWidth();
  454. }
  455. public boolean contains(Paintable p) {
  456. return (getPaintable() == p);
  457. }
  458. public boolean isCaptionVisible() {
  459. return caption.isVisible();
  460. }
  461. }
  462. @Override
  463. protected void clearPaintables() {
  464. clear();
  465. }
  466. public boolean isDynamicHeight() {
  467. return height == null || height.equals("");
  468. }
  469. public boolean isDynamicWidth() {
  470. return width == null || width.equals("");
  471. }
  472. @Override
  473. protected Iterator getPaintableIterator() {
  474. return paintables.iterator();
  475. }
  476. public boolean hasChildComponent(Widget component) {
  477. if (paintables.contains(component)) {
  478. return true;
  479. } else {
  480. return false;
  481. }
  482. }
  483. public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
  484. for (Widget w : getChildren()) {
  485. StackItem item = (StackItem) w;
  486. if (item.getPaintable() == oldComponent) {
  487. item.replacePaintable((Paintable) newComponent);
  488. return;
  489. }
  490. }
  491. }
  492. public void updateCaption(Paintable component, UIDL uidl) {
  493. /* Accordion does not render its children's captions */
  494. }
  495. public boolean requestLayout(Set<Paintable> child) {
  496. if (!isDynamicHeight() && !isDynamicWidth()) {
  497. /*
  498. * If the height and width has been specified for this container the
  499. * child components cannot make the size of the layout change
  500. */
  501. return true;
  502. }
  503. updateOpenTabSize();
  504. if (renderInformation.updateSize(getElement())) {
  505. /*
  506. * Size has changed so we let the child components know about the
  507. * new size.
  508. */
  509. iLayout();
  510. // TODO Check if this is needed
  511. client.runDescendentsLayout(this);
  512. return false;
  513. } else {
  514. /*
  515. * Size has not changed so we do not need to propagate the event
  516. * further
  517. */
  518. return true;
  519. }
  520. }
  521. public RenderSpace getAllocatedSpace(Widget child) {
  522. return renderSpace;
  523. }
  524. @Override
  525. protected int getTabCount() {
  526. return getWidgetCount();
  527. }
  528. @Override
  529. protected void removeTab(int index) {
  530. StackItem item = getStackItem(index);
  531. remove(item);
  532. }
  533. @Override
  534. protected Paintable getTab(int index) {
  535. if (index < getWidgetCount()) {
  536. return (Paintable) (getStackItem(index)).getPaintable();
  537. }
  538. return null;
  539. }
  540. private StackItem getStackItem(int index) {
  541. return (StackItem) getWidget(index);
  542. }
  543. }