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.

IOrderedLayout.java 45KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.itmill.toolkit.terminal.gwt.client.ui;
  5. import java.util.HashMap;
  6. import java.util.Iterator;
  7. import java.util.Vector;
  8. import com.google.gwt.user.client.DOM;
  9. import com.google.gwt.user.client.Element;
  10. import com.google.gwt.user.client.ui.Panel;
  11. import com.google.gwt.user.client.ui.Widget;
  12. import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
  13. import com.itmill.toolkit.terminal.gwt.client.BrowserInfo;
  14. import com.itmill.toolkit.terminal.gwt.client.ICaption;
  15. import com.itmill.toolkit.terminal.gwt.client.Container;
  16. import com.itmill.toolkit.terminal.gwt.client.ContainerResizedListener;
  17. import com.itmill.toolkit.terminal.gwt.client.Paintable;
  18. import com.itmill.toolkit.terminal.gwt.client.UIDL;
  19. import com.itmill.toolkit.terminal.gwt.client.Util;
  20. /**
  21. * Full implementation of OrderedLayout client peer.
  22. *
  23. * This class implements all features of OrderedLayout. It currently only
  24. * supports use through UIDL updates. Direct client side use is not (currently)
  25. * suported in all operation modes.
  26. *
  27. * @author IT Mill Ltd
  28. */
  29. public class IOrderedLayout extends Panel implements Container,
  30. ContainerResizedListener {
  31. public static final String CLASSNAME = "i-orderedlayout";
  32. public static final int ORIENTATION_VERTICAL = 0;
  33. public static final int ORIENTATION_HORIZONTAL = 1;
  34. /**
  35. * If margin and spacing values has been calculated, this holds the values
  36. * for the given UIDL style attribute .
  37. */
  38. private static HashMap measuredMargins = new HashMap();
  39. /**
  40. * Spacing. Correct values will be set in
  41. * updateMarginAndSpacingFromCSS(UIDL)
  42. */
  43. private int hSpacing, vSpacing;
  44. /**
  45. * Margin. Correct values will be set in updateMarginAndSpacingFromCSS(UIDL)
  46. */
  47. private int marginTop, marginBottom, marginLeft, marginRight;
  48. int orientationMode = ORIENTATION_VERTICAL;
  49. protected ApplicationConnection client;
  50. /**
  51. * Reference to Element where wrapped childred are contained. Normally a
  52. * DIV, TR or a TBODY element.
  53. */
  54. private Element wrappedChildContainer;
  55. /**
  56. * List of child widgets. This is not the list of wrappers, but the actual
  57. * widgets
  58. */
  59. private final Vector childWidgets = new Vector();
  60. /**
  61. * In table mode, the root element is table instead of div.
  62. */
  63. private boolean tableMode = false;
  64. /**
  65. * Root element. This element points to the outmost table-element (in table
  66. * mode) or outmost div (in non-table-mode). This non-table-mode this equals
  67. * to the getElement().
  68. */
  69. private Element root = null;
  70. /**
  71. * Last set width of the component. Null if undefined (instead of being "").
  72. */
  73. private String width = null;
  74. /**
  75. * Last set height of the component. Null if undefined (instead of being
  76. * "").
  77. */
  78. private String height = null;
  79. /**
  80. * List of child widget wrappers. These wrappers are in exact same indexes
  81. * as the widgets in childWidgets list.
  82. */
  83. private final Vector childWidgetWrappers = new Vector();
  84. /** Whether the component has spacing enabled. */
  85. private boolean hasComponentSpacing;
  86. /** Information about margin states. */
  87. private MarginInfo margins = new MarginInfo(0);
  88. /**
  89. * Flag that indicates that the child layouts must be updated as soon as
  90. * possible. This will be done in the end of updateFromUIDL.
  91. */
  92. private boolean childLayoutsHaveChanged = false;
  93. /**
  94. * Construct the DOM of the orderder layout.
  95. *
  96. * <p>
  97. * There are two modes - vertical and horizontal.
  98. * <ul>
  99. * <li>Vertical mode uses structure: div-root ( div-wrap ( child ) div-wrap
  100. * ( child ))).</li>
  101. * <li>Horizontal mode uses structure: table ( tbody ( tr-childcontainer (
  102. * td-wrap ( child ) td-wrap ( child) )) )</li>
  103. * </ul>
  104. * where root and childcontainer refer to the root element and the element
  105. * that contain WidgetWrappers.
  106. * </p>
  107. *
  108. */
  109. public IOrderedLayout() {
  110. wrappedChildContainer = root = DOM.createDiv();
  111. setElement(wrappedChildContainer);
  112. setStyleName(CLASSNAME);
  113. }
  114. /**
  115. * Update orientation, if it has changed.
  116. *
  117. * @param newOrientationMode
  118. */
  119. private void rebuildRootDomStructure(int oldOrientationMode) {
  120. // Should we have table as a root element?
  121. boolean newTableMode = !(orientationMode == ORIENTATION_VERTICAL && width != null);
  122. // Already in correct mode?
  123. if (oldOrientationMode == orientationMode && newTableMode == tableMode) {
  124. return;
  125. }
  126. boolean oldTableMode = tableMode;
  127. tableMode = newTableMode;
  128. // Constuct base DOM-structure and clean any already attached
  129. // widgetwrappers from DOM.
  130. if (tableMode) {
  131. final String structure = "<table cellspacing=\"0\" cellpadding=\"0\"><tbody>"
  132. + (orientationMode == ORIENTATION_HORIZONTAL ? "<tr valign=\"top\"></tr>"
  133. : "") + "</tbody></table>";
  134. DOM.setInnerHTML(getElement(), structure);
  135. root = DOM.getFirstChild(getElement());
  136. // set TBODY to be the wrappedChildContainer
  137. wrappedChildContainer = DOM.getFirstChild(root);
  138. // In case of horizontal layouts, we must user TR instead of TBODY
  139. if (orientationMode == ORIENTATION_HORIZONTAL) {
  140. wrappedChildContainer = DOM
  141. .getFirstChild(wrappedChildContainer);
  142. }
  143. } else {
  144. root = wrappedChildContainer = getElement();
  145. DOM.setInnerHTML(getElement(), "");
  146. }
  147. // Reinsert all widget wrappers to this container
  148. final int currentOrientationMode = orientationMode;
  149. for (int i = 0; i < childWidgetWrappers.size(); i++) {
  150. WidgetWrapper wr = (WidgetWrapper) childWidgetWrappers.get(i);
  151. orientationMode = oldOrientationMode;
  152. tableMode = oldTableMode;
  153. Element oldWrElement = wr.getElementWrappingWidgetAndCaption();
  154. orientationMode = currentOrientationMode;
  155. tableMode = newTableMode;
  156. String classe = DOM.getElementAttribute(oldWrElement, "class");
  157. wr.resetRootElement();
  158. Element newWrElement = wr.getElementWrappingWidgetAndCaption();
  159. if (classe != null && classe.length() > 0) {
  160. DOM.setElementAttribute(newWrElement, "class", classe);
  161. }
  162. while (DOM.getChildCount(oldWrElement) > 0) {
  163. Element c = DOM.getFirstChild(oldWrElement);
  164. DOM.removeChild(oldWrElement, c);
  165. DOM.appendChild(newWrElement, c);
  166. }
  167. DOM.appendChild(wrappedChildContainer, wr.getElement());
  168. }
  169. // Update child layouts
  170. childLayoutsHaveChanged = true;
  171. }
  172. /** Update the contents of the layout from UIDL. */
  173. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  174. this.client = client;
  175. // Only non-cached UIDL:s can introduce changes
  176. if (uidl.getBooleanAttribute("cached")) {
  177. return;
  178. }
  179. updateMarginAndSpacingSizesFromCSS(uidl);
  180. // Update sizes, ...
  181. if (client.updateComponent(this, uidl, true)) {
  182. return;
  183. }
  184. // Rebuild DOM tree root if necessary
  185. int oldO = orientationMode;
  186. orientationMode = "horizontal".equals(uidl
  187. .getStringAttribute("orientation")) ? ORIENTATION_HORIZONTAL
  188. : ORIENTATION_VERTICAL;
  189. rebuildRootDomStructure(oldO);
  190. // Handle component spacing later in handleAlignments() method
  191. hasComponentSpacing = uidl.getBooleanAttribute("spacing");
  192. // Collect the list of contained widgets after this update
  193. final Vector newWidgets = new Vector();
  194. for (final Iterator it = uidl.getChildIterator(); it.hasNext();) {
  195. final UIDL uidlForChild = (UIDL) it.next();
  196. final Paintable child = client.getPaintable(uidlForChild);
  197. newWidgets.add(child);
  198. }
  199. // Iterator for old widgets
  200. final Iterator oldWidgetsIterator = (new Vector(childWidgets))
  201. .iterator();
  202. // Iterator for new widgets
  203. final Iterator newWidgetsIterator = newWidgets.iterator();
  204. // Iterator for new UIDL
  205. final Iterator newUIDLIterator = uidl.getChildIterator();
  206. // List to collect all now painted widgets to in order to remove
  207. // unpainted ones later
  208. final Vector paintedWidgets = new Vector();
  209. final Vector childsToPaint = new Vector();
  210. // Add any new widgets to the ordered layout
  211. Widget oldChild = null;
  212. while (newWidgetsIterator.hasNext()) {
  213. final Widget newChild = (Widget) newWidgetsIterator.next();
  214. final UIDL newChildUIDL = (UIDL) newUIDLIterator.next();
  215. // Remove any unneeded old widgets
  216. if (oldChild == null && oldWidgetsIterator.hasNext()) {
  217. // search for next old Paintable which still exists in layout
  218. // and delete others
  219. while (oldWidgetsIterator.hasNext()) {
  220. oldChild = (Widget) oldWidgetsIterator.next();
  221. // now oldChild is an instance of Paintable
  222. if (paintedWidgets.contains(oldChild)) {
  223. continue;
  224. } else if (newWidgets.contains(oldChild)) {
  225. break;
  226. } else {
  227. remove(oldChild);
  228. oldChild = null;
  229. }
  230. }
  231. }
  232. if (oldChild == null) {
  233. // we are adding components to the end of layout
  234. add(newChild);
  235. } else if (newChild == oldChild) {
  236. // child already attached in correct position
  237. oldChild = null;
  238. } else if (hasChildComponent(newChild)) {
  239. // current child has been moved, re-insert before current
  240. // oldChild
  241. add(newChild, childWidgets.indexOf(oldChild));
  242. } else {
  243. // insert new child before old one
  244. add(newChild, childWidgets.indexOf(oldChild));
  245. }
  246. // Update the child component
  247. childsToPaint.add(new Object[] { newChild, newChildUIDL });
  248. // Add this newly handled component to the list of painted
  249. // components
  250. paintedWidgets.add(newChild);
  251. }
  252. // Remove possibly remaining old widgets which were not in painted UIDL
  253. while (oldWidgetsIterator.hasNext()) {
  254. oldChild = (Widget) oldWidgetsIterator.next();
  255. if (!newWidgets.contains(oldChild)) {
  256. remove(oldChild);
  257. }
  258. }
  259. // Handle component alignments
  260. handleAlignmentsSpacingAndMargins(uidl);
  261. // Reset sizes for the children
  262. updateChildSizes();
  263. // Paint children
  264. for (int i = 0; i < childsToPaint.size(); i++) {
  265. Object[] t = (Object[]) childsToPaint.get(i);
  266. ((Paintable) t[0]).updateFromUIDL((UIDL) t[1], client);
  267. }
  268. // Update child layouts
  269. // TODO This is most probably unnecessary and should be done within
  270. // update Child H/W
  271. if (childLayoutsHaveChanged) {
  272. Util.runDescendentsLayout(this);
  273. childLayoutsHaveChanged = false;
  274. }
  275. }
  276. private void updateMarginAndSpacingSizesFromCSS(UIDL uidl) {
  277. // Style for this layout
  278. String style = uidl.getStringAttribute("style");
  279. if (style == null) {
  280. style = "";
  281. }
  282. // Try to find measured from cache
  283. int[] r = (int[]) measuredMargins.get(style);
  284. // Measure from DOM
  285. if (r == null) {
  286. r = new int[] { 0, 0, 0, 0, 0, 0 };
  287. // Construct DOM for measurements
  288. Element e1 = DOM.createTable();
  289. DOM.setStyleAttribute(e1, "position", "absolute");
  290. DOM.setElementProperty(e1, "cellSpacing", "0");
  291. DOM.setElementProperty(e1, "cellPadding", "0");
  292. Element e11 = DOM.createTBody();
  293. Element e12 = DOM.createTR();
  294. Element e13 = DOM.createTD();
  295. Element e2 = DOM.createDiv();
  296. Element e3 = DOM.createDiv();
  297. DOM.setStyleAttribute(e3, "width", "100px");
  298. DOM.setStyleAttribute(e3, "height", "100px");
  299. DOM.appendChild(getElement(), e1);
  300. DOM.appendChild(e1, e11);
  301. DOM.appendChild(e11, e12);
  302. DOM.appendChild(e12, e13);
  303. DOM.appendChild(e13, e2);
  304. DOM.appendChild(e2, e3);
  305. DOM.setInnerText(e3, ".");
  306. // Measure different properties
  307. final String[] classes = { "margin-top", "margin-right",
  308. "margin-bottom", "margin-left", "vspacing", "hspacing" };
  309. for (int c = 0; c < 6; c++) {
  310. StringBuffer styleBuf = new StringBuffer();
  311. final String primaryName = getStylePrimaryName();
  312. styleBuf.append(primaryName + "-" + classes[c]);
  313. if (style.length() > 0) {
  314. final String[] styles = style.split(" ");
  315. for (int i = 0; i < styles.length; i++) {
  316. styleBuf.append(" ");
  317. styleBuf.append(primaryName);
  318. styleBuf.append("-");
  319. styleBuf.append(styles[i]);
  320. styleBuf.append("-");
  321. styleBuf.append(classes[c]);
  322. }
  323. }
  324. DOM.setElementProperty(e2, "className", styleBuf.toString());
  325. // Measure
  326. r[c] = DOM.getElementPropertyInt(e1,
  327. (c % 2) == 1 ? "offsetWidth" : "offsetHeight") - 100;
  328. }
  329. // Clean-up
  330. DOM.removeChild(getElement(), e1);
  331. // Cache for further use
  332. measuredMargins.put(style, r);
  333. }
  334. // Set the properties
  335. marginTop = r[0];
  336. marginRight = r[1];
  337. marginBottom = r[2];
  338. marginLeft = r[3];
  339. vSpacing = r[4];
  340. hSpacing = r[5];
  341. }
  342. /**
  343. * While setting width, ensure that margin div is also resized properly.
  344. * Furthermore, enable/disable fixed mode
  345. */
  346. public void setWidth(String newWidth) {
  347. width = newWidth == null || "".equals(newWidth) ? null : newWidth;
  348. // As we use divs at root - for them using 100% width should be
  349. // calculated with ""
  350. super.setWidth("");
  351. // Update child layouts
  352. childLayoutsHaveChanged = true;
  353. }
  354. /**
  355. * While setting height, ensure that margin div is also resized properly.
  356. * Furthermore, enable/disable fixed mode
  357. */
  358. public void setHeight(String newHeight) {
  359. super.setHeight(newHeight);
  360. height = newHeight == null || "".equals(newHeight) ? null : newHeight;
  361. // Update child layouts
  362. childLayoutsHaveChanged = true;
  363. }
  364. /** Recalculate and apply the space given for each child in this layout. */
  365. private void updateChildSizes() {
  366. int numChild = childWidgets.size();
  367. int childHeightTotal = -1;
  368. int childHeightDivisor = 1;
  369. int childWidthTotal = -1;
  370. int childWidthDivisor = 1;
  371. // Vertical layout is calculated by us
  372. if (height != null) {
  373. // Calculate the space for fixed contents minus marginals
  374. if (tableMode) {
  375. // If we know explicitly set pixel-size, use that
  376. if (height != null && height.endsWith("px")) {
  377. try {
  378. childHeightTotal = Integer.parseInt(height.substring(0,
  379. height.length() - 2));
  380. // For negative sizes, use measurements
  381. if (childHeightTotal < 0) {
  382. childHeightTotal = rootOffsetMeasure("offsetHeight");
  383. }
  384. } catch (NumberFormatException e) {
  385. // In case of invalid number, try to measure the size;
  386. childHeightTotal = rootOffsetMeasure("offsetHeight");
  387. }
  388. }
  389. // If not, try to measure the size
  390. else {
  391. childHeightTotal = rootOffsetMeasure("offsetHeight");
  392. }
  393. } else {
  394. childHeightTotal = DOM.getElementPropertyInt(getElement(),
  395. "offsetHeight");
  396. }
  397. childHeightTotal -= margins.hasTop() ? marginTop : 0;
  398. childHeightTotal -= margins.hasBottom() ? marginBottom : 0;
  399. // Reduce spacing from the size
  400. if (hasComponentSpacing) {
  401. childHeightTotal -= ((orientationMode == ORIENTATION_HORIZONTAL) ? hSpacing
  402. : vSpacing)
  403. * (numChild - 1);
  404. }
  405. // Total space is divided among the children
  406. if (orientationMode == ORIENTATION_VERTICAL) {
  407. childHeightDivisor = numChild;
  408. }
  409. }
  410. // layout is calculated by us
  411. if (width != null) {
  412. // Calculate the space for fixed contents minus marginals
  413. // If we know explicitly set pixel-size, use that
  414. if (width != null && width.endsWith("px")) {
  415. try {
  416. childWidthTotal = Integer.parseInt(width.substring(0, width
  417. .length() - 2));
  418. // For negative sizes, use measurements
  419. if (childWidthTotal < 0) {
  420. childWidthTotal = rootOffsetMeasure("offsetWidth");
  421. }
  422. } catch (NumberFormatException e) {
  423. // In case of invalid number, try to measure the size;
  424. childWidthTotal = rootOffsetMeasure("offsetWidth");
  425. }
  426. }
  427. // If not, try to measure the size
  428. else {
  429. childWidthTotal = rootOffsetMeasure("offsetWidth");
  430. }
  431. childWidthTotal -= margins.hasLeft() ? marginLeft : 0;
  432. childWidthTotal -= margins.hasRight() ? marginRight : 0;
  433. // Reduce spacing from the size
  434. if (hasComponentSpacing
  435. && orientationMode == ORIENTATION_HORIZONTAL) {
  436. childWidthTotal -= hSpacing * (numChild - 1);
  437. }
  438. // Total space is divided among the children
  439. if (orientationMode == ORIENTATION_HORIZONTAL) {
  440. childWidthDivisor = numChild;
  441. }
  442. }
  443. // Set the sizes for each child
  444. for (Iterator i = childWidgetWrappers.iterator(); i.hasNext();) {
  445. int w, h;
  446. if (childHeightDivisor > 1) {
  447. h = Math.round(((float) childHeightTotal)
  448. / (childHeightDivisor--));
  449. childHeightTotal -= h;
  450. } else {
  451. h = childHeightTotal;
  452. }
  453. if (childWidthDivisor > 1) {
  454. w = Math.round(((float) childWidthTotal)
  455. / (childWidthDivisor--));
  456. childWidthTotal -= w;
  457. } else {
  458. w = childWidthTotal;
  459. }
  460. WidgetWrapper ww = (WidgetWrapper) i.next();
  461. ww.forceSize(w, h);
  462. }
  463. }
  464. /**
  465. * Measure how much space the root element could get.
  466. *
  467. * This measures the space allocated by the parent for the root element
  468. * without letting root element to affect the calculation.
  469. *
  470. * @param offset
  471. * offsetWidth or offsetHeight
  472. */
  473. private int rootOffsetMeasure(String offset) {
  474. // TODO This method must be optimized!
  475. Element measure = DOM.createDiv();
  476. DOM.setStyleAttribute(measure, "height", "100%");
  477. Element parent = DOM.getParent(getElement());
  478. DOM.insertBefore(parent, measure, getElement());
  479. DOM.removeChild(parent, getElement());
  480. int size = DOM.getElementPropertyInt(measure, offset);
  481. DOM.insertBefore(parent, getElement(), measure);
  482. DOM.removeChild(parent, measure);
  483. // In case the no space would be given for this element
  484. // without pushing, use the current side of the root
  485. return size;
  486. }
  487. /** Parse alignments from UIDL and pass whem to correct widgetwrappers */
  488. private void handleAlignmentsSpacingAndMargins(UIDL uidl) {
  489. // Only update margins when they have changed
  490. // TODO this should be optimized to avoid reupdating these
  491. margins = new MarginInfo(uidl.getIntAttribute("margins"));
  492. // Component alignments as a comma separated list.
  493. // See com.itmill.toolkit.terminal.gwt.client.ui.AlignmentInfo.java for
  494. // possible values.
  495. final int[] alignments = uidl.getIntArrayAttribute("alignments");
  496. int alignmentIndex = 0;
  497. // Insert alignment attributes
  498. final Iterator it = childWidgetWrappers.iterator();
  499. while (it.hasNext()) {
  500. // Calculate alignment info
  501. final AlignmentInfo ai = new AlignmentInfo(
  502. alignments[alignmentIndex++]);
  503. final WidgetWrapper wr = (WidgetWrapper) it.next();
  504. wr.setAlignment(ai.getVerticalAlignment(), ai
  505. .getHorizontalAlignment());
  506. // Handle spacing and margins in this loop as well
  507. wr.setSpacingAndMargins(alignmentIndex == 1,
  508. alignmentIndex == alignments.length);
  509. }
  510. }
  511. /**
  512. * Wrapper around single child in the layout.
  513. *
  514. * This helper also manages spacing, margins and alignment for individual
  515. * cells handling. It also can put hard size limits for its contens by
  516. * clipping the content to given pixel size.
  517. *
  518. */
  519. class WidgetWrapper {
  520. /**
  521. * When alignment table structure is used, these elements correspond to
  522. * the TD elements within the structure. If alignment is not used, these
  523. * are null.
  524. */
  525. Element alignmentTD, innermostTDinAlignmnetStructure;
  526. /**
  527. * When clipping must be done and the element wrapping clipped content
  528. * would be TD instead of DIV, this element points to additional DIV
  529. * that is used for clipping.
  530. */
  531. Element clipperDiv;
  532. /** Caption element when used. */
  533. ICaption caption = null;
  534. /**
  535. * Last set pixel height for the wrapper. -1 if vertical clipping is not
  536. * used.
  537. */
  538. int lastForcedPixelHeight = -1;
  539. /**
  540. * Last set pidel width for the wrapper. -1 if horizontal clipping is
  541. * not used.
  542. */
  543. int lastForcedPixelWidth = -1;
  544. /** Widget Wrapper root element */
  545. Element wrapperElement;
  546. /** Set the root element */
  547. public WidgetWrapper() {
  548. resetRootElement();
  549. }
  550. public Element getElement() {
  551. return wrapperElement;
  552. }
  553. /**
  554. * Set the width and height given for the wrapped widget in pixels.
  555. *
  556. * -1 if unconstrained.
  557. */
  558. public void forceSize(int pixelWidth, int pixelHeight) {
  559. // If we are already at the correct size, do nothing
  560. if (lastForcedPixelHeight == pixelHeight
  561. && lastForcedPixelWidth == pixelWidth) {
  562. return;
  563. }
  564. // Clipper DIV is needed?
  565. if (tableMode && (pixelHeight >= 0 || pixelWidth >= 0)) {
  566. if (clipperDiv == null) {
  567. createClipperDiv();
  568. }
  569. }
  570. // ClipperDiv is not needed, remove if necessary
  571. else if (clipperDiv != null) {
  572. removeClipperDiv();
  573. }
  574. Element e = clipperDiv != null ? clipperDiv
  575. : getElementWrappingAlignmentStructures();
  576. // Overflow
  577. DOM.setStyleAttribute(e, "overflow", pixelWidth < 0
  578. && pixelHeight < 0 ? "" : "hidden");
  579. // Set size
  580. DOM.setStyleAttribute(e, "width", pixelWidth < 0 ? "" : pixelWidth
  581. + "px");
  582. DOM.setStyleAttribute(e, "height",
  583. pixelHeight < 0 ? (e == clipperDiv ? "100%" : "")
  584. : pixelHeight + "px");
  585. // Set cached values
  586. lastForcedPixelWidth = pixelWidth;
  587. lastForcedPixelHeight = pixelHeight;
  588. }
  589. /** Create a DIV for clipping the child */
  590. private void createClipperDiv() {
  591. clipperDiv = DOM.createDiv();
  592. final Element e = getElementWrappingClipperDiv();
  593. String classe = DOM.getElementAttribute(e, "class");
  594. while (DOM.getChildCount(e) > 0) {
  595. final Element c = DOM.getFirstChild(e);
  596. DOM.removeChild(e, c);
  597. DOM.appendChild(clipperDiv, c);
  598. }
  599. if (classe != null && classe.length() > 0) {
  600. DOM.removeElementAttribute(e, "class");
  601. DOM.setElementAttribute(clipperDiv, "class", classe);
  602. }
  603. DOM.appendChild(e, clipperDiv);
  604. }
  605. /** Undo createClipperDiv() */
  606. private void removeClipperDiv() {
  607. final Element e = getElementWrappingClipperDiv();
  608. String classe = DOM.getElementAttribute(clipperDiv, "class");
  609. while (DOM.getChildCount(clipperDiv) > 0) {
  610. final Element c = DOM.getFirstChild(clipperDiv);
  611. DOM.removeChild(clipperDiv, c);
  612. DOM.appendChild(e, c);
  613. }
  614. DOM.removeChild(e, clipperDiv);
  615. clipperDiv = null;
  616. if (classe != null && classe.length() > 0) {
  617. DOM.setElementAttribute(e, "class", classe);
  618. }
  619. }
  620. /**
  621. * Get the element containing the caption and the wrapped widget.
  622. * Returned element can one of the following:
  623. * <ul>
  624. * <li>(a) Root DIV of the WrapperElement when not in tableMode</li>
  625. * <li>(b) TD in just below the root TR of the WrapperElement when in
  626. * tableMode</li>
  627. * <li>(c) clipperDiv inside the (a) or (b)</li>
  628. * <li>(d) The innermost TD within alignment structures located in (a),
  629. * (b) or (c)</li>
  630. * </ul>
  631. *
  632. * @return Element described above
  633. */
  634. private Element getElementWrappingWidgetAndCaption() {
  635. // When alignment is used, we will can safely return the innermost
  636. // TD
  637. if (innermostTDinAlignmnetStructure != null) {
  638. return innermostTDinAlignmnetStructure;
  639. }
  640. // In all other cases element wrapping the potential alignment
  641. // structures is the correct one
  642. return getElementWrappingAlignmentStructures();
  643. }
  644. /**
  645. * Get the element where alignment structures should be placed in if
  646. * they are in use.
  647. *
  648. * Returned element can one of the following:
  649. * <ul>
  650. * <li>(a) Root DIV of the WrapperElement when not in tableMode</li>
  651. * <li>(b) TD in just below the root TR of the WrapperElement when in
  652. * tableMode</li>
  653. * <li>(c) clipperDiv inside the (a) or (b)</li>
  654. * </ul>
  655. *
  656. * @return Element described above
  657. */
  658. private Element getElementWrappingAlignmentStructures() {
  659. // Clipper DIV wraps the alignment structures if present
  660. if (clipperDiv != null) {
  661. return clipperDiv;
  662. }
  663. // When Clipper DIV is not used, we just give the element
  664. // that would wrap it if it would be used
  665. return getElementWrappingClipperDiv();
  666. }
  667. /**
  668. * Get the element where clipperDiv should be placed in if they it is in
  669. * use.
  670. *
  671. * Returned element can one of the following:
  672. * <ul>
  673. * <li>(a) Root DIV of the WrapperElement when not in tableMode</li>
  674. * <li>(b) TD in just below the root TR of the WrapperElement when in
  675. * tableMode</li>
  676. * </ul>
  677. *
  678. * @return Element described above
  679. */
  680. private Element getElementWrappingClipperDiv() {
  681. // Only vertical layouts in non-table mode use TR as root, for the
  682. // rest we can safely give root element
  683. if (!tableMode || orientationMode == ORIENTATION_HORIZONTAL) {
  684. return wrapperElement;
  685. }
  686. // The root is TR, we'll thus give the TD that is immediately within
  687. // the root
  688. return DOM.getFirstChild(wrapperElement);
  689. }
  690. /**
  691. * Create tr, td or div - depending on the orientation of the layout and
  692. * set it as root.
  693. *
  694. * All contents of the wrapper are cleared. Caller is responsible for
  695. * preserving the contents and moving them into new root.
  696. *
  697. * @return Previous root element.
  698. */
  699. private void resetRootElement() {
  700. // TODO Should we remove the existing element?
  701. if (tableMode) {
  702. if (orientationMode == ORIENTATION_HORIZONTAL) {
  703. wrapperElement = DOM.createTD();
  704. } else {
  705. wrapperElement = DOM.createTR();
  706. DOM.appendChild(wrapperElement, DOM.createTD());
  707. }
  708. } else {
  709. wrapperElement = DOM.createDiv();
  710. // Apply 'hasLayout' for IE (needed to get accurate dimension
  711. // calculations)
  712. if (BrowserInfo.get().isIE()) {
  713. DOM.setStyleAttribute(wrapperElement, "zoom", "1");
  714. }
  715. }
  716. // Clear any references to intermediate elements
  717. clipperDiv = alignmentTD = innermostTDinAlignmnetStructure = null;
  718. }
  719. /** Update the caption of the element contained in this wrapper. */
  720. public void updateCaption(UIDL uidl, Paintable paintable) {
  721. final Widget widget = (Widget) paintable;
  722. final Element captionWrapper = getElementWrappingWidgetAndCaption();
  723. // The widget needs caption
  724. if (ICaption.isNeeded(uidl)) {
  725. // If the caption element is missing, create it
  726. boolean justAdded = false;
  727. if (caption == null) {
  728. justAdded = true;
  729. caption = new ICaption(paintable, client);
  730. }
  731. // Update caption contents
  732. caption.updateCaption(uidl);
  733. final boolean after = caption.shouldBePlacedAfterComponent();
  734. final Element captionElement = caption.getElement();
  735. final Element widgetElement = widget.getElement();
  736. if (justAdded) {
  737. // As the caption has just been created, insert it to DOM
  738. if (after) {
  739. DOM.appendChild(captionWrapper, captionElement);
  740. DOM.setElementAttribute(captionWrapper, "class",
  741. "i-orderedlayout-w");
  742. caption.addStyleName("i-orderedlayout-c");
  743. widget.addStyleName("i-orderedlayout-w-e");
  744. } else {
  745. DOM.insertChild(captionWrapper, captionElement, 0);
  746. }
  747. } else
  748. // Caption exists. Move it to correct position if needed
  749. if (after == (DOM.getChildIndex(captionWrapper, widgetElement) > DOM
  750. .getChildIndex(captionWrapper, captionElement))) {
  751. Element firstElement = DOM.getChild(captionWrapper, DOM
  752. .getChildCount(captionWrapper) - 2);
  753. if (firstElement != null) {
  754. DOM.removeChild(captionWrapper, firstElement);
  755. DOM.appendChild(captionWrapper, firstElement);
  756. }
  757. DOM.setElementAttribute(captionWrapper, "class",
  758. after ? "i-orderedlayout-w" : "");
  759. if (after) {
  760. caption.addStyleName("i-orderedlayout-c");
  761. widget.addStyleName("i-orderedlayout-w-e");
  762. } else {
  763. widget.removeStyleName("i-orderedlayout-w-e");
  764. caption.removeStyleName("i-orderedlayout-w-c");
  765. }
  766. }
  767. }
  768. // Caption is not needed
  769. else {
  770. // Remove existing caption from DOM
  771. if (caption != null) {
  772. DOM.removeChild(captionWrapper, caption.getElement());
  773. caption = null;
  774. DOM.setElementAttribute(captionWrapper, "class", "");
  775. widget.removeStyleName("i-orderedlayout-w-e");
  776. caption.removeStyleName("i-orderedlayout-w-c");
  777. }
  778. }
  779. }
  780. /**
  781. * Set alignments for this wrapper.
  782. */
  783. void setAlignment(String verticalAlignment, String horizontalAlignment) {
  784. // use one-cell table to implement horizontal alignments, only
  785. // for values other than top-left (which is default)
  786. if (!horizontalAlignment.equals("left")
  787. || !verticalAlignment.equals("top")) {
  788. // The previous positioning has been left (or unspecified).
  789. // Thus we need to create a one-cell-table to position
  790. // this element.
  791. if (alignmentTD == null) {
  792. // Store and remove the current childs (widget and caption)
  793. Element c1 = DOM
  794. .getFirstChild(getElementWrappingWidgetAndCaption());
  795. if (c1 != null) {
  796. DOM.removeChild(getElementWrappingWidgetAndCaption(),
  797. c1);
  798. }
  799. Element c2 = DOM
  800. .getFirstChild(getElementWrappingWidgetAndCaption());
  801. if (c2 != null) {
  802. DOM.removeChild(getElementWrappingWidgetAndCaption(),
  803. c2);
  804. }
  805. // Construct table structure to align children
  806. final String t = "<table cellpadding='0' cellspacing='0' width='100%' height='100%'><tbody><tr><td>"
  807. + "<table cellpadding='0' cellspacing='0' ><tbody><tr><td align='left'>"
  808. + "</td></tr></tbody></table></td></tr></tbody></table>";
  809. DOM.setInnerHTML(getElementWrappingWidgetAndCaption(), t);
  810. alignmentTD = DOM
  811. .getFirstChild(DOM
  812. .getFirstChild(DOM
  813. .getFirstChild(DOM
  814. .getFirstChild(getElementWrappingWidgetAndCaption()))));
  815. innermostTDinAlignmnetStructure = DOM.getFirstChild(DOM
  816. .getFirstChild(DOM.getFirstChild(DOM
  817. .getFirstChild(alignmentTD))));
  818. // Restore children inside the
  819. if (c1 != null) {
  820. DOM.appendChild(innermostTDinAlignmnetStructure, c1);
  821. if (c2 != null) {
  822. DOM
  823. .appendChild(
  824. innermostTDinAlignmnetStructure, c2);
  825. }
  826. }
  827. } else {
  828. // Go around optimization bug in WebKit and ensure repaint
  829. if (BrowserInfo.get().isSafari()) {
  830. String prevValue = DOM.getElementAttribute(alignmentTD,
  831. "align");
  832. if (!horizontalAlignment.equals(prevValue)) {
  833. Element parent = DOM.getParent(alignmentTD);
  834. DOM.removeChild(parent, alignmentTD);
  835. DOM.appendChild(parent, alignmentTD);
  836. }
  837. }
  838. }
  839. // Set the alignment in td
  840. DOM.setElementAttribute(alignmentTD, "align",
  841. horizontalAlignment);
  842. DOM.setElementAttribute(alignmentTD, "valign",
  843. verticalAlignment);
  844. } else {
  845. // In this case we are requested to position this left
  846. // while as it has had some other position in the past.
  847. // Thus the one-cell wrapper table must be removed.
  848. if (alignmentTD != null) {
  849. // Move content to main container
  850. final Element itd = innermostTDinAlignmnetStructure;
  851. final Element alignmentTable = DOM.getParent(DOM
  852. .getParent(DOM.getParent(alignmentTD)));
  853. final Element target = DOM.getParent(alignmentTable);
  854. while (DOM.getChildCount(itd) > 0) {
  855. Element content = DOM.getFirstChild(itd);
  856. if (content != null) {
  857. DOM.removeChild(itd, content);
  858. DOM.appendChild(target, content);
  859. }
  860. }
  861. // Remove unneeded table element
  862. DOM.removeChild(target, alignmentTable);
  863. alignmentTD = innermostTDinAlignmnetStructure = null;
  864. }
  865. }
  866. }
  867. /** Set class for spacing */
  868. void setSpacingAndMargins(boolean first, boolean last) {
  869. final Element e = getElementWrappingWidgetAndCaption();
  870. if (orientationMode == ORIENTATION_HORIZONTAL) {
  871. DOM.setStyleAttribute(e, "paddingLeft", first ? (margins
  872. .hasLeft() ? marginLeft + "px" : "0")
  873. : (hasComponentSpacing ? hSpacing + "px" : "0"));
  874. DOM.setStyleAttribute(e, "paddingRight", last ? (margins
  875. .hasRight() ? marginRight + "px" : "0") : "");
  876. DOM.setStyleAttribute(e, "paddingTop",
  877. margins.hasTop() ? marginTop + "px" : "");
  878. DOM.setStyleAttribute(e, "paddingBottom",
  879. margins.hasBottom() ? marginBottom + "px" : "");
  880. } else {
  881. DOM.setStyleAttribute(e, "paddingLeft",
  882. margins.hasLeft() ? marginLeft + "px" : "0");
  883. DOM.setStyleAttribute(e, "paddingRight",
  884. margins.hasRight() ? marginRight + "px" : "0");
  885. DOM.setStyleAttribute(e, "paddingTop", first ? (margins
  886. .hasTop() ? marginTop + "px" : "")
  887. : (hasComponentSpacing ? vSpacing + "px" : "0"));
  888. DOM.setStyleAttribute(e, "paddingBottom", last
  889. && margins.hasBottom() ? marginBottom + "px" : "");
  890. }
  891. }
  892. }
  893. /* documented at super */
  894. public void add(Widget child) {
  895. add(child, childWidgets.size());
  896. }
  897. /**
  898. * Add widget to this layout at given position.
  899. *
  900. * This methods supports reinserting exiting child into layout - it just
  901. * moves the position of the child in the layout.
  902. */
  903. public void add(Widget child, int atIndex) {
  904. /*
  905. * <b>Validate:</b> Perform any sanity checks to ensure the Panel can
  906. * accept a new Widget. Examples: checking for a valid index on
  907. * insertion; checking that the Panel is not full if there is a max
  908. * capacity.
  909. */
  910. if (atIndex < 0 || atIndex > childWidgets.size()) {
  911. return;
  912. }
  913. /*
  914. * <b>Adjust for Reinsertion:</b> Some Panels need to handle the case
  915. * where the Widget is already a child of this Panel. Example: when
  916. * performing a reinsert, the index might need to be adjusted to account
  917. * for the Widget's removal. See {@link ComplexPanel#adjustIndex(Widget,
  918. * int)}.
  919. */
  920. if (childWidgets.contains(child)) {
  921. if (childWidgets.indexOf(child) == atIndex) {
  922. return;
  923. }
  924. final int removeFromIndex = childWidgets.indexOf(child);
  925. final WidgetWrapper wrapper = (WidgetWrapper) childWidgetWrappers
  926. .get(removeFromIndex);
  927. Element wrapperElement = wrapper.getElement();
  928. final int nonWidgetChildElements = DOM
  929. .getChildCount(wrappedChildContainer)
  930. - childWidgets.size();
  931. DOM.removeChild(wrappedChildContainer, wrapperElement);
  932. DOM.insertChild(wrappedChildContainer, wrapperElement, atIndex
  933. + nonWidgetChildElements);
  934. childWidgets.remove(removeFromIndex);
  935. childWidgetWrappers.remove(removeFromIndex);
  936. childWidgets.insertElementAt(child, atIndex);
  937. childWidgetWrappers.insertElementAt(wrapper, atIndex);
  938. return;
  939. }
  940. /*
  941. * <b>Detach Child:</b> Remove the Widget from its existing parent, if
  942. * any. Most Panels will simply call {@link Widget#removeFromParent()}
  943. * on the Widget.
  944. */
  945. child.removeFromParent();
  946. /*
  947. * <b>Logical Attach:</b> Any state variables of the Panel should be
  948. * updated to reflect the addition of the new Widget. Example: the
  949. * Widget is added to the Panel's {@link WidgetCollection} at the
  950. * appropriate index.
  951. */
  952. childWidgets.insertElementAt(child, atIndex);
  953. /*
  954. * <b>Physical Attach:</b> The Widget's Element must be physically
  955. * attached to the Panel's Element, either directly or indirectly.
  956. */
  957. final WidgetWrapper wrapper = new WidgetWrapper();
  958. final int nonWidgetChildElements = DOM
  959. .getChildCount(wrappedChildContainer)
  960. - childWidgetWrappers.size();
  961. childWidgetWrappers.insertElementAt(wrapper, atIndex);
  962. DOM.insertChild(wrappedChildContainer, wrapper.getElement(), atIndex
  963. + nonWidgetChildElements);
  964. DOM.appendChild(wrapper.getElementWrappingWidgetAndCaption(), child
  965. .getElement());
  966. /*
  967. * <b>Adopt:</b> Call {@link #adopt(Widget)} to finalize the add as the
  968. * very last step.
  969. */
  970. adopt(child);
  971. }
  972. /* documented at super */
  973. public boolean remove(Widget child) {
  974. /*
  975. * <b>Validate:</b> Make sure this Panel is actually the parent of the
  976. * child Widget; return <code>false</code> if it is not.
  977. */
  978. if (!childWidgets.contains(child)) {
  979. return false;
  980. }
  981. /*
  982. * <b>Orphan:</b> Call {@link #orphan(Widget)} first while the child
  983. * Widget is still attached.
  984. */
  985. orphan(child);
  986. /*
  987. * <b>Physical Detach:</b> Adjust the DOM to account for the removal of
  988. * the child Widget. The Widget's Element must be physically removed
  989. * from the DOM.
  990. */
  991. final int index = childWidgets.indexOf(child);
  992. final WidgetWrapper wrapper = (WidgetWrapper) childWidgetWrappers
  993. .get(index);
  994. DOM.removeChild(wrappedChildContainer, wrapper.getElement());
  995. childWidgetWrappers.remove(index);
  996. /*
  997. * <b>Logical Detach:</b> Update the Panel's state variables to reflect
  998. * the removal of the child Widget. Example: the Widget is removed from
  999. * the Panel's {@link WidgetCollection}.
  1000. */
  1001. childWidgets.remove(index);
  1002. return true;
  1003. }
  1004. /* documented at super */
  1005. public boolean hasChildComponent(Widget component) {
  1006. return childWidgets.contains(component);
  1007. }
  1008. /* documented at super */
  1009. public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
  1010. final int index = childWidgets.indexOf(oldComponent);
  1011. if (index >= 0) {
  1012. client.unregisterPaintable((Paintable) oldComponent);
  1013. remove(oldComponent);
  1014. add(newComponent, index);
  1015. }
  1016. }
  1017. /* documented at super */
  1018. public void updateCaption(Paintable component, UIDL uidl) {
  1019. final int index = childWidgets.indexOf(component);
  1020. if (index >= 0) {
  1021. ((WidgetWrapper) childWidgetWrappers.get(index)).updateCaption(
  1022. uidl, component);
  1023. }
  1024. }
  1025. /* documented at super */
  1026. public Iterator iterator() {
  1027. return childWidgets.iterator();
  1028. }
  1029. /* documented at super */
  1030. public void iLayout() {
  1031. updateChildSizes();
  1032. Util.runDescendentsLayout(this);
  1033. childLayoutsHaveChanged = false;
  1034. }
  1035. }