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.

ChildComponentContainer.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. package com.vaadin.terminal.gwt.client.ui.layout;
  2. import java.util.Iterator;
  3. import java.util.NoSuchElementException;
  4. import com.google.gwt.dom.client.DivElement;
  5. import com.google.gwt.dom.client.Document;
  6. import com.google.gwt.dom.client.Element;
  7. import com.google.gwt.dom.client.Style;
  8. import com.google.gwt.dom.client.TableElement;
  9. import com.google.gwt.user.client.ui.Panel;
  10. import com.google.gwt.user.client.ui.Widget;
  11. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  12. import com.vaadin.terminal.gwt.client.BrowserInfo;
  13. import com.vaadin.terminal.gwt.client.Paintable;
  14. import com.vaadin.terminal.gwt.client.UIDL;
  15. import com.vaadin.terminal.gwt.client.Util;
  16. import com.vaadin.terminal.gwt.client.VCaption;
  17. import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize;
  18. import com.vaadin.terminal.gwt.client.RenderInformation.Size;
  19. import com.vaadin.terminal.gwt.client.ui.AlignmentInfo;
  20. public class ChildComponentContainer extends Panel {
  21. /**
  22. * Size of the container DIV excluding any margins and also excluding the
  23. * expansion amount (containerExpansion)
  24. */
  25. private Size contSize = new Size(0, 0);
  26. /**
  27. * Size of the widget inside the container DIV
  28. */
  29. private Size widgetSize = new Size(0, 0);
  30. /**
  31. * Size of the caption
  32. */
  33. private int captionRequiredWidth = 0;
  34. private int captionWidth = 0;
  35. private int captionHeight = 0;
  36. /**
  37. *
  38. * Padding added to the container when it is larger than the component.
  39. */
  40. private Size containerExpansion = new Size(0, 0);
  41. private double expandRatio;
  42. private int containerMarginLeft = 0;
  43. private int containerMarginTop = 0;
  44. AlignmentInfo alignment = AlignmentInfo.TOP_LEFT;
  45. private int alignmentLeftOffsetForWidget = 0;
  46. private int alignmentLeftOffsetForCaption = 0;
  47. /**
  48. * Top offset for implementing alignment. Top offset is set to the container
  49. * DIV as it otherwise would have to be set to either the Caption or the
  50. * Widget depending on whether there is a caption and where the caption is
  51. * located.
  52. */
  53. private int alignmentTopOffset = 0;
  54. // private Margins alignmentOffset = new Margins(0, 0, 0, 0);
  55. private VCaption caption = null;
  56. private DivElement containerDIV;
  57. private DivElement widgetDIV;
  58. private Widget widget;
  59. private FloatSize relativeSize = null;
  60. public ChildComponentContainer(Widget widget, int orientation) {
  61. super();
  62. containerDIV = Document.get().createDivElement();
  63. widgetDIV = Document.get().createDivElement();
  64. if (BrowserInfo.get().isFF2()) {
  65. Style style = widgetDIV.getStyle();
  66. // FF2 chokes on some floats very easily. Measuring size escpecially
  67. // becomes terribly slow
  68. TableElement tableEl = Document.get().createTableElement();
  69. tableEl
  70. .setInnerHTML("<tbody><tr><td><div></div></td></tr></tbody>");
  71. DivElement div = (DivElement) tableEl.getFirstChildElement()
  72. .getFirstChildElement().getFirstChildElement()
  73. .getFirstChildElement();
  74. tableEl.setCellPadding(0);
  75. tableEl.setCellSpacing(0);
  76. tableEl.setBorder(0);
  77. div.getStyle().setProperty("padding", "0");
  78. setElement(tableEl);
  79. containerDIV = div;
  80. } else {
  81. setFloat(widgetDIV, "left");
  82. setElement(containerDIV);
  83. containerDIV.getStyle().setProperty("height", "0");
  84. containerDIV.getStyle().setProperty("width", "0px");
  85. containerDIV.getStyle().setProperty("overflow", "hidden");
  86. }
  87. if (BrowserInfo.get().isIE()) {
  88. /*
  89. * IE requires position: relative on overflow:hidden elements if
  90. * they should hide position:relative elements. Without this e.g. a
  91. * 1000x1000 Panel inside an 500x500 OrderedLayout will not be
  92. * clipped but fully shown.
  93. */
  94. containerDIV.getStyle().setProperty("position", "relative");
  95. widgetDIV.getStyle().setProperty("position", "relative");
  96. }
  97. containerDIV.appendChild(widgetDIV);
  98. setOrientation(orientation);
  99. setWidget(widget);
  100. }
  101. public void setWidget(Widget w) {
  102. // Validate
  103. if (w == widget) {
  104. return;
  105. }
  106. // Detach new child.
  107. if (w != null) {
  108. w.removeFromParent();
  109. }
  110. // Remove old child.
  111. if (widget != null) {
  112. remove(widget);
  113. }
  114. // Logical attach.
  115. widget = w;
  116. if (w != null) {
  117. // Physical attach.
  118. widgetDIV.appendChild(widget.getElement());
  119. adopt(w);
  120. }
  121. }
  122. private static void setFloat(Element div, String floatString) {
  123. if (BrowserInfo.get().isIE()) {
  124. div.getStyle().setProperty("styleFloat", floatString);
  125. // IE requires display:inline for margin-left to work together
  126. // with float:left
  127. if (floatString.equals("left")) {
  128. div.getStyle().setProperty("display", "inline");
  129. } else {
  130. div.getStyle().setProperty("display", "block");
  131. }
  132. } else {
  133. div.getStyle().setProperty("cssFloat", floatString);
  134. }
  135. }
  136. public void setOrientation(int orientation) {
  137. if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) {
  138. setFloat(getElement(), "left");
  139. } else {
  140. setFloat(getElement(), "");
  141. }
  142. setHeight("0px");
  143. // setWidth("0px");
  144. contSize.setHeight(0);
  145. contSize.setWidth(0);
  146. containerMarginLeft = 0;
  147. containerMarginTop = 0;
  148. containerDIV.getStyle().setProperty("paddingLeft", "0");
  149. containerDIV.getStyle().setProperty("paddingTop", "0");
  150. containerExpansion.setHeight(0);
  151. containerExpansion.setWidth(0);
  152. // Clear old alignments
  153. clearAlignments();
  154. }
  155. public void renderChild(UIDL childUIDL, ApplicationConnection client,
  156. int fixedWidth) {
  157. /*
  158. * Must remove width specification from container before rendering to
  159. * allow components to grow in horizontal direction.
  160. *
  161. * For fixed width layouts we specify the width directly so that height
  162. * is automatically calculated correctly (e.g. for Labels).
  163. */
  164. /*
  165. * This should no longer be needed (after #2563) as all components are
  166. * such that they can be rendered inside a 0x0 DIV.
  167. */
  168. // if (fixedWidth > 0) {
  169. // setLimitedContainerWidth(fixedWidth);
  170. // } else {
  171. // setUnlimitedContainerWidth();
  172. // }
  173. ((Paintable) widget).updateFromUIDL(childUIDL, client);
  174. }
  175. public void setUnlimitedContainerWidth() {
  176. setLimitedContainerWidth(1000000);
  177. }
  178. public void setLimitedContainerWidth(int width) {
  179. containerDIV.getStyle().setProperty("width", width + "px");
  180. }
  181. public void updateWidgetSize() {
  182. /*
  183. * Widget wrapper includes margin which the widget offsetWidth/Height
  184. * does not include
  185. */
  186. int w = Util.getRequiredWidth(widgetDIV);
  187. int h = Util.getRequiredHeight(widgetDIV);
  188. widgetSize.setWidth(w);
  189. widgetSize.setHeight(h);
  190. // ApplicationConnection.getConsole().log(
  191. // Util.getSimpleName(widget) + " size is " + w + "," + h);
  192. }
  193. public void setMarginLeft(int marginLeft) {
  194. containerMarginLeft = marginLeft;
  195. containerDIV.getStyle().setPropertyPx("paddingLeft", marginLeft);
  196. }
  197. public void setMarginTop(int marginTop) {
  198. containerMarginTop = marginTop;
  199. containerDIV.getStyle().setPropertyPx("paddingTop",
  200. marginTop + alignmentTopOffset);
  201. updateContainerDOMSize();
  202. }
  203. public void updateAlignments(int parentWidth, int parentHeight) {
  204. if (parentHeight == -1) {
  205. parentHeight = contSize.getHeight();
  206. }
  207. if (parentWidth == -1) {
  208. parentWidth = contSize.getWidth();
  209. }
  210. alignmentTopOffset = calculateVerticalAlignmentTopOffset(parentHeight);
  211. calculateHorizontalAlignment(parentWidth);
  212. applyAlignments();
  213. }
  214. private void applyAlignments() {
  215. // Update top margin to take alignment into account
  216. setMarginTop(containerMarginTop);
  217. if (caption != null) {
  218. caption.getElement().getStyle().setPropertyPx("marginLeft",
  219. alignmentLeftOffsetForCaption);
  220. }
  221. widgetDIV.getStyle().setPropertyPx("marginLeft",
  222. alignmentLeftOffsetForWidget);
  223. }
  224. public int getCaptionRequiredWidth() {
  225. if (caption == null) {
  226. return 0;
  227. }
  228. return captionRequiredWidth;
  229. }
  230. public int getCaptionWidth() {
  231. if (caption == null) {
  232. return 0;
  233. }
  234. return captionWidth;
  235. }
  236. public int getCaptionHeight() {
  237. if (caption == null) {
  238. return 0;
  239. }
  240. return captionHeight;
  241. }
  242. public int getCaptionWidthAfterComponent() {
  243. if (caption == null || !caption.shouldBePlacedAfterComponent()) {
  244. return 0;
  245. }
  246. return getCaptionWidth();
  247. }
  248. public int getCaptionHeightAboveComponent() {
  249. if (caption == null || caption.shouldBePlacedAfterComponent()) {
  250. return 0;
  251. }
  252. return getCaptionHeight();
  253. }
  254. private int calculateVerticalAlignmentTopOffset(int emptySpace) {
  255. if (alignment.isTop()) {
  256. return 0;
  257. }
  258. if (caption != null) {
  259. if (caption.shouldBePlacedAfterComponent()) {
  260. /*
  261. * Take into account the rare case that the caption on the right
  262. * side of the component AND is higher than the component
  263. */
  264. emptySpace -= Math.max(widgetSize.getHeight(), caption
  265. .getHeight());
  266. } else {
  267. emptySpace -= widgetSize.getHeight();
  268. emptySpace -= getCaptionHeight();
  269. }
  270. } else {
  271. /*
  272. * There is no caption and thus we do not need to take anything but
  273. * the widget into account
  274. */
  275. emptySpace -= widgetSize.getHeight();
  276. }
  277. int top = 0;
  278. if (alignment.isVerticalCenter()) {
  279. top = emptySpace / 2;
  280. } else if (alignment.isBottom()) {
  281. top = emptySpace;
  282. }
  283. if (top < 0) {
  284. top = 0;
  285. }
  286. return top;
  287. }
  288. private void calculateHorizontalAlignment(int emptySpace) {
  289. alignmentLeftOffsetForCaption = 0;
  290. alignmentLeftOffsetForWidget = 0;
  291. if (alignment.isLeft()) {
  292. return;
  293. }
  294. int captionSpace = emptySpace;
  295. int widgetSpace = emptySpace;
  296. if (caption != null) {
  297. // There is a caption
  298. if (caption.shouldBePlacedAfterComponent()) {
  299. /*
  300. * The caption is after component. In this case the caption
  301. * needs no alignment.
  302. */
  303. captionSpace = 0;
  304. widgetSpace -= widgetSize.getWidth();
  305. widgetSpace -= getCaptionWidth();
  306. } else {
  307. /*
  308. * The caption is above the component. Caption and widget needs
  309. * separate alignment offsets.
  310. */
  311. widgetSpace -= widgetSize.getWidth();
  312. captionSpace -= getCaptionWidth();
  313. }
  314. } else {
  315. /*
  316. * There is no caption and thus we do not need to take anything but
  317. * the widget into account
  318. */
  319. captionSpace = 0;
  320. widgetSpace -= widgetSize.getWidth();
  321. }
  322. if (alignment.isHorizontalCenter()) {
  323. alignmentLeftOffsetForCaption = captionSpace / 2;
  324. alignmentLeftOffsetForWidget = widgetSpace / 2;
  325. } else if (alignment.isRight()) {
  326. alignmentLeftOffsetForCaption = captionSpace;
  327. alignmentLeftOffsetForWidget = widgetSpace;
  328. }
  329. if (alignmentLeftOffsetForCaption < 0) {
  330. alignmentLeftOffsetForCaption = 0;
  331. }
  332. if (alignmentLeftOffsetForWidget < 0) {
  333. alignmentLeftOffsetForWidget = 0;
  334. }
  335. }
  336. public void setAlignment(AlignmentInfo alignmentInfo) {
  337. alignment = alignmentInfo;
  338. }
  339. public Size getWidgetSize() {
  340. return widgetSize;
  341. }
  342. public void updateCaption(UIDL uidl, ApplicationConnection client) {
  343. if (VCaption.isNeeded(uidl)) {
  344. // We need a caption
  345. VCaption newCaption = caption;
  346. if (newCaption == null) {
  347. newCaption = new VCaption((Paintable) widget, client);
  348. // Set initial height to avoid Safari flicker
  349. newCaption.setHeight("18px");
  350. // newCaption.setHeight(newCaption.getHeight()); // This might
  351. // be better... ??
  352. if (BrowserInfo.get().isIE()) {
  353. /*
  354. * Must attach caption here so IE sends an immediate onload
  355. * event for images coming from the cache
  356. */
  357. setCaption(newCaption);
  358. }
  359. }
  360. boolean positionChanged = newCaption.updateCaption(uidl);
  361. if (newCaption != caption || positionChanged) {
  362. setCaption(newCaption);
  363. }
  364. } else {
  365. // Caption is not needed
  366. if (caption != null) {
  367. remove(caption);
  368. }
  369. }
  370. updateCaptionSize();
  371. }
  372. public void updateCaptionSize() {
  373. captionWidth = 0;
  374. captionHeight = 0;
  375. if (caption != null) {
  376. captionWidth = caption.getRenderedWidth();
  377. captionHeight = caption.getHeight();
  378. captionRequiredWidth = caption.getRequiredWidth();
  379. /*
  380. * ApplicationConnection.getConsole().log(
  381. * "Caption rendered width: " + captionWidth +
  382. * ", caption required width: " + captionRequiredWidth +
  383. * ", caption height: " + captionHeight);
  384. */
  385. }
  386. }
  387. private void setCaption(VCaption newCaption) {
  388. // Validate
  389. // if (newCaption == caption) {
  390. // return;
  391. // }
  392. // Detach new child.
  393. if (newCaption != null) {
  394. newCaption.removeFromParent();
  395. }
  396. // Remove old child.
  397. if (caption != null && newCaption != caption) {
  398. remove(caption);
  399. }
  400. // Logical attach.
  401. caption = newCaption;
  402. if (caption != null) {
  403. // Physical attach.
  404. if (caption.shouldBePlacedAfterComponent()) {
  405. Util.setFloat(caption.getElement(), "left");
  406. containerDIV.appendChild(caption.getElement());
  407. } else {
  408. Util.setFloat(caption.getElement(), "");
  409. containerDIV.insertBefore(caption.getElement(), widgetDIV);
  410. }
  411. adopt(caption);
  412. }
  413. }
  414. @Override
  415. public boolean remove(Widget child) {
  416. // Validate
  417. if (child != caption && child != widget) {
  418. return false;
  419. }
  420. // Orphan
  421. orphan(child);
  422. // Physical && Logical Detach
  423. if (child == caption) {
  424. containerDIV.removeChild(child.getElement());
  425. caption = null;
  426. } else {
  427. widgetDIV.removeChild(child.getElement());
  428. widget = null;
  429. }
  430. return true;
  431. }
  432. public Iterator<Widget> iterator() {
  433. return new ChildComponentContainerIterator<Widget>();
  434. }
  435. public class ChildComponentContainerIterator<T> implements Iterator<Widget> {
  436. private int id = 0;
  437. public boolean hasNext() {
  438. return (id < size());
  439. }
  440. public Widget next() {
  441. Widget w = get(id);
  442. id++;
  443. return w;
  444. }
  445. private Widget get(int i) {
  446. if (i == 0) {
  447. if (widget != null) {
  448. return widget;
  449. } else if (caption != null) {
  450. return caption;
  451. } else {
  452. throw new NoSuchElementException();
  453. }
  454. } else if (i == 1) {
  455. if (widget != null && caption != null) {
  456. return caption;
  457. } else {
  458. throw new NoSuchElementException();
  459. }
  460. } else {
  461. throw new NoSuchElementException();
  462. }
  463. }
  464. public void remove() {
  465. int toRemove = id - 1;
  466. if (toRemove == 0) {
  467. if (widget != null) {
  468. ChildComponentContainer.this.remove(widget);
  469. } else if (caption != null) {
  470. ChildComponentContainer.this.remove(caption);
  471. } else {
  472. throw new IllegalStateException();
  473. }
  474. } else if (toRemove == 1) {
  475. if (widget != null && caption != null) {
  476. ChildComponentContainer.this.remove(caption);
  477. } else {
  478. throw new IllegalStateException();
  479. }
  480. } else {
  481. throw new IllegalStateException();
  482. }
  483. id--;
  484. }
  485. }
  486. public int size() {
  487. if (widget != null) {
  488. if (caption != null) {
  489. return 2;
  490. } else {
  491. return 1;
  492. }
  493. } else {
  494. if (caption != null) {
  495. return 1;
  496. } else {
  497. return 0;
  498. }
  499. }
  500. }
  501. public Widget getWidget() {
  502. return widget;
  503. }
  504. /**
  505. * Return true if the size of the widget has been specified in the selected
  506. * orientation.
  507. *
  508. * @return
  509. */
  510. public boolean widgetHasSizeSpecified(int orientation) {
  511. String size;
  512. if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) {
  513. size = widget.getElement().getStyle().getProperty("width");
  514. } else {
  515. size = widget.getElement().getStyle().getProperty("height");
  516. }
  517. return (size != null && !size.equals(""));
  518. }
  519. public boolean isComponentRelativeSized(int orientation) {
  520. if (relativeSize == null) {
  521. return false;
  522. }
  523. if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) {
  524. return relativeSize.getWidth() >= 0;
  525. } else {
  526. return relativeSize.getHeight() >= 0;
  527. }
  528. }
  529. public void setRelativeSize(FloatSize relativeSize) {
  530. this.relativeSize = relativeSize;
  531. }
  532. public Size getContSize() {
  533. return contSize;
  534. }
  535. public void clearAlignments() {
  536. alignmentLeftOffsetForCaption = 0;
  537. alignmentLeftOffsetForWidget = 0;
  538. alignmentTopOffset = 0;
  539. applyAlignments();
  540. }
  541. /**
  542. * Sets the normalized expand ratio of this slot. The fraction that this
  543. * slot will use of "excess space".
  544. *
  545. * @param expandRatio
  546. */
  547. public void setNormalizedExpandRatio(double expandRatio) {
  548. this.expandRatio = expandRatio;
  549. }
  550. public int expand(int orientation, int spaceForExpansion) {
  551. int expansionAmount = (int) (spaceForExpansion * expandRatio);
  552. if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) {
  553. // HORIZONTAL
  554. containerExpansion.setWidth(expansionAmount);
  555. } else {
  556. // VERTICAL
  557. containerExpansion.setHeight(expansionAmount);
  558. }
  559. return expansionAmount;
  560. }
  561. public void expandExtra(int orientation, int extra) {
  562. if (orientation == CellBasedLayout.ORIENTATION_HORIZONTAL) {
  563. // HORIZONTAL
  564. containerExpansion.setWidth(containerExpansion.getWidth() + extra);
  565. } else {
  566. // VERTICAL
  567. containerExpansion
  568. .setHeight(containerExpansion.getHeight() + extra);
  569. }
  570. }
  571. public void setContainerSize(int widgetAndCaptionWidth,
  572. int widgetAndCaptionHeight) {
  573. int containerWidth = widgetAndCaptionWidth;
  574. containerWidth += containerExpansion.getWidth();
  575. int containerHeight = widgetAndCaptionHeight;
  576. containerHeight += containerExpansion.getHeight();
  577. // ApplicationConnection.getConsole().log(
  578. // "Setting container size for " + Util.getSimpleName(widget)
  579. // + " to " + containerWidth + "," + containerHeight);
  580. if (containerWidth < 0) {
  581. ApplicationConnection.getConsole().log(
  582. "containerWidth should never be negative: "
  583. + containerWidth);
  584. containerWidth = 0;
  585. }
  586. if (containerHeight < 0) {
  587. ApplicationConnection.getConsole().log(
  588. "containerHeight should never be negative: "
  589. + containerHeight);
  590. containerHeight = 0;
  591. }
  592. contSize.setWidth(containerWidth);
  593. contSize.setHeight(containerHeight);
  594. updateContainerDOMSize();
  595. }
  596. public void updateContainerDOMSize() {
  597. int width = contSize.getWidth();
  598. int height = contSize.getHeight() - alignmentTopOffset;
  599. if (width < 0) {
  600. width = 0;
  601. }
  602. if (height < 0) {
  603. height = 0;
  604. }
  605. setWidth(width + "px");
  606. setHeight(height + "px");
  607. // Also update caption max width
  608. if (caption != null) {
  609. if (caption.shouldBePlacedAfterComponent()) {
  610. caption.setMaxWidth(captionWidth);
  611. } else {
  612. caption.setMaxWidth(width);
  613. }
  614. captionWidth = caption.getRenderedWidth();
  615. // Remove initial height
  616. caption.setHeight("");
  617. }
  618. }
  619. }