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

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