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.

LayoutManager.java 48KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client;
  5. import java.util.Collection;
  6. import java.util.HashMap;
  7. import java.util.HashSet;
  8. import java.util.Map;
  9. import java.util.Set;
  10. import com.google.gwt.core.client.Duration;
  11. import com.google.gwt.core.client.JsArrayString;
  12. import com.google.gwt.dom.client.Element;
  13. import com.google.gwt.dom.client.Style;
  14. import com.google.gwt.dom.client.Style.Overflow;
  15. import com.google.gwt.user.client.Timer;
  16. import com.vaadin.terminal.gwt.client.MeasuredSize.MeasureResult;
  17. import com.vaadin.terminal.gwt.client.ui.ManagedLayout;
  18. import com.vaadin.terminal.gwt.client.ui.PostLayoutListener;
  19. import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout;
  20. import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeEvent;
  21. import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeListener;
  22. import com.vaadin.terminal.gwt.client.ui.layout.LayoutDependencyTree;
  23. import com.vaadin.terminal.gwt.client.ui.notification.VNotification;
  24. public class LayoutManager {
  25. private static final String LOOP_ABORT_MESSAGE = "Aborting layout after 100 passes. This would probably be an infinite loop.";
  26. private static final boolean debugLogging = false;
  27. private ApplicationConnection connection;
  28. private final Set<Element> measuredNonConnectorElements = new HashSet<Element>();
  29. private final MeasuredSize nullSize = new MeasuredSize();
  30. private LayoutDependencyTree currentDependencyTree;
  31. private final Collection<ManagedLayout> needsHorizontalLayout = new HashSet<ManagedLayout>();
  32. private final Collection<ManagedLayout> needsVerticalLayout = new HashSet<ManagedLayout>();
  33. private final Collection<ComponentConnector> needsMeasure = new HashSet<ComponentConnector>();
  34. private Collection<ComponentConnector> pendingOverflowFixes = new HashSet<ComponentConnector>();
  35. private final Map<Element, Collection<ElementResizeListener>> elementResizeListeners = new HashMap<Element, Collection<ElementResizeListener>>();
  36. private final Set<Element> listenersToFire = new HashSet<Element>();
  37. private boolean layoutPending = false;
  38. private Timer layoutTimer = new Timer() {
  39. @Override
  40. public void run() {
  41. cancel();
  42. layoutNow();
  43. }
  44. };
  45. private boolean everythingNeedsMeasure = false;
  46. public void setConnection(ApplicationConnection connection) {
  47. if (this.connection != null) {
  48. throw new RuntimeException(
  49. "LayoutManager connection can never be changed");
  50. }
  51. this.connection = connection;
  52. }
  53. /**
  54. * Gets the layout manager associated with the given
  55. * {@link ApplicationConnection}.
  56. *
  57. * @param connection
  58. * the application connection to get a layout manager for
  59. * @return the layout manager associated with the provided application
  60. * connection
  61. */
  62. public static LayoutManager get(ApplicationConnection connection) {
  63. return connection.getLayoutManager();
  64. }
  65. /**
  66. * Registers that a ManagedLayout is depending on the size of an Element.
  67. * This causes this layout manager to measure the element in the beginning
  68. * of every layout phase and call the appropriate layout method of the
  69. * managed layout if the size of the element has changed.
  70. *
  71. * @param owner
  72. * the ManagedLayout that depends on an element
  73. * @param element
  74. * the Element that should be measured
  75. */
  76. public void registerDependency(ManagedLayout owner, Element element) {
  77. MeasuredSize measuredSize = ensureMeasured(element);
  78. setNeedsLayout(owner);
  79. measuredSize.addDependent(owner.getConnectorId());
  80. }
  81. private MeasuredSize ensureMeasured(Element element) {
  82. MeasuredSize measuredSize = getMeasuredSize(element, null);
  83. if (measuredSize == null) {
  84. measuredSize = new MeasuredSize();
  85. if (ConnectorMap.get(connection).getConnector(element) == null) {
  86. measuredNonConnectorElements.add(element);
  87. }
  88. setMeasuredSize(element, measuredSize);
  89. }
  90. return measuredSize;
  91. }
  92. private boolean needsMeasure(Element e) {
  93. if (connection.getConnectorMap().getConnectorId(e) != null) {
  94. return true;
  95. } else if (elementResizeListeners.containsKey(e)) {
  96. return true;
  97. } else if (getMeasuredSize(e, nullSize).hasDependents()) {
  98. return true;
  99. } else {
  100. return false;
  101. }
  102. }
  103. /**
  104. * Assigns a measured size to an element. Method defined as protected to
  105. * allow separate implementation for IE8.
  106. *
  107. * @param element
  108. * the dom element to attach the measured size to
  109. * @param measuredSize
  110. * the measured size to attach to the element. If
  111. * <code>null</code>, any previous measured size is removed.
  112. */
  113. protected native void setMeasuredSize(Element element,
  114. MeasuredSize measuredSize)
  115. /*-{
  116. if (measuredSize) {
  117. element.vMeasuredSize = measuredSize;
  118. } else {
  119. delete element.vMeasuredSize;
  120. }
  121. }-*/;
  122. /**
  123. * Gets the measured size for an element. Method defined as protected to
  124. * allow separate implementation for IE8.
  125. *
  126. * @param element
  127. * The element to get measured size for
  128. * @param defaultSize
  129. * The size to return if no measured size could be found
  130. * @return The measured size for the element or {@literal defaultSize}
  131. */
  132. protected native MeasuredSize getMeasuredSize(Element element,
  133. MeasuredSize defaultSize)
  134. /*-{
  135. return element.vMeasuredSize || defaultSize;
  136. }-*/;
  137. private final MeasuredSize getMeasuredSize(ComponentConnector connector) {
  138. Element element = connector.getWidget().getElement();
  139. MeasuredSize measuredSize = getMeasuredSize(element, null);
  140. if (measuredSize == null) {
  141. measuredSize = new MeasuredSize();
  142. setMeasuredSize(element, measuredSize);
  143. }
  144. return measuredSize;
  145. }
  146. /**
  147. * Registers that a ManagedLayout is no longer depending on the size of an
  148. * Element.
  149. *
  150. * @see #registerDependency(ManagedLayout, Element)
  151. *
  152. * @param owner
  153. * the ManagedLayout no longer depends on an element
  154. * @param element
  155. * the Element that that no longer needs to be measured
  156. */
  157. public void unregisterDependency(ManagedLayout owner, Element element) {
  158. MeasuredSize measuredSize = getMeasuredSize(element, null);
  159. if (measuredSize == null) {
  160. return;
  161. }
  162. measuredSize.removeDependent(owner.getConnectorId());
  163. stopMeasuringIfUnecessary(element);
  164. }
  165. public boolean isLayoutRunning() {
  166. return currentDependencyTree != null;
  167. }
  168. private void countLayout(Map<ManagedLayout, Integer> layoutCounts,
  169. ManagedLayout layout) {
  170. Integer count = layoutCounts.get(layout);
  171. if (count == null) {
  172. count = Integer.valueOf(0);
  173. } else {
  174. count = Integer.valueOf(count.intValue() + 1);
  175. }
  176. layoutCounts.put(layout, count);
  177. if (count.intValue() > 2) {
  178. VConsole.error(Util.getConnectorString(layout)
  179. + " has been layouted " + count.intValue() + " times");
  180. }
  181. }
  182. private void layoutLater() {
  183. if (!layoutPending) {
  184. layoutPending = true;
  185. layoutTimer.schedule(100);
  186. }
  187. }
  188. public void layoutNow() {
  189. if (isLayoutRunning()) {
  190. throw new IllegalStateException(
  191. "Can't start a new layout phase before the previous layout phase ends.");
  192. }
  193. layoutPending = false;
  194. try {
  195. currentDependencyTree = new LayoutDependencyTree();
  196. doLayout();
  197. } finally {
  198. currentDependencyTree = null;
  199. }
  200. }
  201. private void doLayout() {
  202. VConsole.log("Starting layout phase");
  203. Map<ManagedLayout, Integer> layoutCounts = new HashMap<ManagedLayout, Integer>();
  204. int passes = 0;
  205. Duration totalDuration = new Duration();
  206. for (ManagedLayout layout : needsHorizontalLayout) {
  207. currentDependencyTree.setNeedsHorizontalLayout(layout, true);
  208. }
  209. for (ManagedLayout layout : needsVerticalLayout) {
  210. currentDependencyTree.setNeedsVerticalLayout(layout, true);
  211. }
  212. needsHorizontalLayout.clear();
  213. needsVerticalLayout.clear();
  214. for (ComponentConnector connector : needsMeasure) {
  215. currentDependencyTree.setNeedsMeasure(connector, true);
  216. }
  217. needsMeasure.clear();
  218. measureNonConnectors();
  219. VConsole.log("Layout init in " + totalDuration.elapsedMillis() + " ms");
  220. while (true) {
  221. Duration passDuration = new Duration();
  222. passes++;
  223. int measuredConnectorCount = measureConnectors(
  224. currentDependencyTree, everythingNeedsMeasure);
  225. everythingNeedsMeasure = false;
  226. if (measuredConnectorCount == 0) {
  227. VConsole.log("No more changes in pass " + passes);
  228. break;
  229. }
  230. int measureTime = passDuration.elapsedMillis();
  231. VConsole.log(" Measured " + measuredConnectorCount
  232. + " elements in " + measureTime + " ms");
  233. if (!listenersToFire.isEmpty()) {
  234. for (Element element : listenersToFire) {
  235. Collection<ElementResizeListener> listeners = elementResizeListeners
  236. .get(element);
  237. ElementResizeListener[] array = listeners
  238. .toArray(new ElementResizeListener[listeners.size()]);
  239. ElementResizeEvent event = new ElementResizeEvent(this,
  240. element);
  241. for (ElementResizeListener listener : array) {
  242. try {
  243. listener.onElementResize(event);
  244. } catch (RuntimeException e) {
  245. VConsole.error(e);
  246. }
  247. }
  248. }
  249. int measureListenerTime = passDuration.elapsedMillis();
  250. VConsole.log(" Fired resize listeners for "
  251. + listenersToFire.size() + " elements in "
  252. + (measureListenerTime - measureTime) + " ms");
  253. measureTime = measuredConnectorCount;
  254. listenersToFire.clear();
  255. }
  256. FastStringSet updatedSet = FastStringSet.create();
  257. while (currentDependencyTree.hasHorizontalConnectorToLayout()
  258. || currentDependencyTree.hasVerticaConnectorToLayout()) {
  259. for (ManagedLayout layout : currentDependencyTree
  260. .getHorizontalLayoutTargets()) {
  261. if (layout instanceof DirectionalManagedLayout) {
  262. currentDependencyTree
  263. .markAsHorizontallyLayouted(layout);
  264. DirectionalManagedLayout cl = (DirectionalManagedLayout) layout;
  265. try {
  266. cl.layoutHorizontally();
  267. } catch (RuntimeException e) {
  268. VConsole.log(e);
  269. }
  270. countLayout(layoutCounts, cl);
  271. } else {
  272. currentDependencyTree
  273. .markAsHorizontallyLayouted(layout);
  274. currentDependencyTree.markAsVerticallyLayouted(layout);
  275. SimpleManagedLayout rr = (SimpleManagedLayout) layout;
  276. try {
  277. rr.layout();
  278. } catch (RuntimeException e) {
  279. VConsole.log(e);
  280. }
  281. countLayout(layoutCounts, rr);
  282. }
  283. if (debugLogging) {
  284. updatedSet.add(layout.getConnectorId());
  285. }
  286. }
  287. for (ManagedLayout layout : currentDependencyTree
  288. .getVerticalLayoutTargets()) {
  289. if (layout instanceof DirectionalManagedLayout) {
  290. currentDependencyTree.markAsVerticallyLayouted(layout);
  291. DirectionalManagedLayout cl = (DirectionalManagedLayout) layout;
  292. try {
  293. cl.layoutVertically();
  294. } catch (RuntimeException e) {
  295. VConsole.log(e);
  296. }
  297. countLayout(layoutCounts, cl);
  298. } else {
  299. currentDependencyTree
  300. .markAsHorizontallyLayouted(layout);
  301. currentDependencyTree.markAsVerticallyLayouted(layout);
  302. SimpleManagedLayout rr = (SimpleManagedLayout) layout;
  303. try {
  304. rr.layout();
  305. } catch (RuntimeException e) {
  306. VConsole.log(e);
  307. }
  308. countLayout(layoutCounts, rr);
  309. }
  310. if (debugLogging) {
  311. updatedSet.add(layout.getConnectorId());
  312. }
  313. }
  314. }
  315. if (debugLogging) {
  316. JsArrayString changedCids = updatedSet.dump();
  317. StringBuilder b = new StringBuilder(" ");
  318. b.append(changedCids.length());
  319. b.append(" requestLayout invocations in ");
  320. b.append(passDuration.elapsedMillis() - measureTime);
  321. b.append(" ms");
  322. if (changedCids.length() < 30) {
  323. for (int i = 0; i < changedCids.length(); i++) {
  324. if (i != 0) {
  325. b.append(", ");
  326. } else {
  327. b.append(": ");
  328. }
  329. String connectorString = changedCids.get(i);
  330. if (changedCids.length() < 10) {
  331. ServerConnector connector = ConnectorMap.get(
  332. connection).getConnector(connectorString);
  333. connectorString = Util
  334. .getConnectorString(connector);
  335. }
  336. b.append(connectorString);
  337. }
  338. }
  339. VConsole.log(b.toString());
  340. }
  341. VConsole.log("Pass " + passes + " completed in "
  342. + passDuration.elapsedMillis() + " ms");
  343. if (passes > 100) {
  344. VConsole.log(LOOP_ABORT_MESSAGE);
  345. VNotification.createNotification(VNotification.DELAY_FOREVER)
  346. .show(LOOP_ABORT_MESSAGE, VNotification.CENTERED,
  347. "error");
  348. break;
  349. }
  350. }
  351. int postLayoutStart = totalDuration.elapsedMillis();
  352. for (ComponentConnector connector : connection.getConnectorMap()
  353. .getComponentConnectors()) {
  354. if (connector instanceof PostLayoutListener) {
  355. ((PostLayoutListener) connector).postLayout();
  356. }
  357. }
  358. int postLayoutDone = (totalDuration.elapsedMillis() - postLayoutStart);
  359. VConsole.log("Invoke post layout listeners in " + postLayoutDone
  360. + " ms");
  361. cleanMeasuredSizes();
  362. int cleaningDone = (totalDuration.elapsedMillis() - postLayoutDone);
  363. VConsole.log("Cleaned old measured sizes in " + cleaningDone + "ms");
  364. VConsole.log("Total layout phase time: "
  365. + totalDuration.elapsedMillis() + "ms");
  366. }
  367. private void logConnectorStatus(int connectorId) {
  368. currentDependencyTree
  369. .logDependencyStatus((ComponentConnector) ConnectorMap.get(
  370. connection).getConnector(Integer.toString(connectorId)));
  371. }
  372. private int measureConnectors(LayoutDependencyTree layoutDependencyTree,
  373. boolean measureAll) {
  374. if (!pendingOverflowFixes.isEmpty()) {
  375. Duration duration = new Duration();
  376. HashMap<Element, String> originalOverflows = new HashMap<Element, String>();
  377. HashSet<ComponentConnector> delayedOverflowFixes = new HashSet<ComponentConnector>();
  378. // First set overflow to hidden (and save previous value so it can
  379. // be restored later)
  380. for (ComponentConnector componentConnector : pendingOverflowFixes) {
  381. // Delay the overflow fix if the involved connectors might still
  382. // change
  383. boolean connectorChangesExpected = !currentDependencyTree
  384. .noMoreChangesExpected(componentConnector);
  385. boolean parentChangesExcpected = componentConnector.getParent() instanceof ComponentConnector
  386. && !currentDependencyTree
  387. .noMoreChangesExpected((ComponentConnector) componentConnector
  388. .getParent());
  389. if (connectorChangesExpected || parentChangesExcpected) {
  390. delayedOverflowFixes.add(componentConnector);
  391. continue;
  392. }
  393. if (debugLogging) {
  394. VConsole.log("Doing overflow fix for "
  395. + Util.getConnectorString(componentConnector)
  396. + " in "
  397. + Util.getConnectorString(componentConnector
  398. .getParent()));
  399. }
  400. Element parentElement = componentConnector.getWidget()
  401. .getElement().getParentElement();
  402. Style style = parentElement.getStyle();
  403. String originalOverflow = style.getOverflow();
  404. if (originalOverflow != null
  405. && !originalOverflows.containsKey(parentElement)) {
  406. // Store original value for restore, but only the first time
  407. // the value is changed
  408. originalOverflows.put(parentElement, originalOverflow);
  409. }
  410. style.setOverflow(Overflow.HIDDEN);
  411. }
  412. pendingOverflowFixes.removeAll(delayedOverflowFixes);
  413. // Then ensure all scrolling elements are reflowed by measuring
  414. for (ComponentConnector componentConnector : pendingOverflowFixes) {
  415. componentConnector.getWidget().getElement().getParentElement()
  416. .getOffsetHeight();
  417. }
  418. // Finally restore old overflow value and update bookkeeping
  419. for (ComponentConnector componentConnector : pendingOverflowFixes) {
  420. Element parentElement = componentConnector.getWidget()
  421. .getElement().getParentElement();
  422. parentElement.getStyle().setProperty("overflow",
  423. originalOverflows.get(parentElement));
  424. layoutDependencyTree.setNeedsMeasure(componentConnector, true);
  425. }
  426. if (!pendingOverflowFixes.isEmpty()) {
  427. VConsole.log("Did overflow fix for "
  428. + pendingOverflowFixes.size() + " elements in "
  429. + duration.elapsedMillis() + " ms");
  430. }
  431. pendingOverflowFixes = delayedOverflowFixes;
  432. }
  433. int measureCount = 0;
  434. if (measureAll) {
  435. ComponentConnector[] connectors = ConnectorMap.get(connection)
  436. .getComponentConnectors();
  437. for (ComponentConnector connector : connectors) {
  438. measureConnector(connector);
  439. }
  440. for (ComponentConnector connector : connectors) {
  441. layoutDependencyTree.setNeedsMeasure(connector, false);
  442. }
  443. measureCount += connectors.length;
  444. }
  445. while (layoutDependencyTree.hasConnectorsToMeasure()) {
  446. Collection<ComponentConnector> measureTargets = layoutDependencyTree
  447. .getMeasureTargets();
  448. for (ComponentConnector connector : measureTargets) {
  449. measureConnector(connector);
  450. measureCount++;
  451. }
  452. for (ComponentConnector connector : measureTargets) {
  453. layoutDependencyTree.setNeedsMeasure(connector, false);
  454. }
  455. }
  456. return measureCount;
  457. }
  458. private void measureConnector(ComponentConnector connector) {
  459. Element element = connector.getWidget().getElement();
  460. MeasuredSize measuredSize = getMeasuredSize(connector);
  461. MeasureResult measureResult = measuredAndUpdate(element, measuredSize);
  462. if (measureResult.isChanged()) {
  463. onConnectorChange(connector, measureResult.isWidthChanged(),
  464. measureResult.isHeightChanged());
  465. }
  466. }
  467. private void onConnectorChange(ComponentConnector connector,
  468. boolean widthChanged, boolean heightChanged) {
  469. setNeedsOverflowFix(connector);
  470. if (heightChanged) {
  471. currentDependencyTree.markHeightAsChanged(connector);
  472. }
  473. if (widthChanged) {
  474. currentDependencyTree.markWidthAsChanged(connector);
  475. }
  476. }
  477. private void setNeedsOverflowFix(ComponentConnector connector) {
  478. // IE9 doesn't need the original fix, but for some reason it needs this
  479. if (BrowserInfo.get().requiresOverflowAutoFix()
  480. || BrowserInfo.get().isIE9()) {
  481. ComponentConnector scrollingBoundary = currentDependencyTree
  482. .getScrollingBoundary(connector);
  483. if (scrollingBoundary != null) {
  484. pendingOverflowFixes.add(scrollingBoundary);
  485. }
  486. }
  487. }
  488. private void measureNonConnectors() {
  489. for (Element element : measuredNonConnectorElements) {
  490. measuredAndUpdate(element, getMeasuredSize(element, null));
  491. }
  492. VConsole.log("Measured " + measuredNonConnectorElements.size()
  493. + " non connector elements");
  494. }
  495. private MeasureResult measuredAndUpdate(Element element,
  496. MeasuredSize measuredSize) {
  497. MeasureResult measureResult = measuredSize.measure(element);
  498. if (measureResult.isChanged()) {
  499. notifyListenersAndDepdendents(element,
  500. measureResult.isWidthChanged(),
  501. measureResult.isHeightChanged());
  502. }
  503. return measureResult;
  504. }
  505. private void notifyListenersAndDepdendents(Element element,
  506. boolean widthChanged, boolean heightChanged) {
  507. assert widthChanged || heightChanged;
  508. MeasuredSize measuredSize = getMeasuredSize(element, nullSize);
  509. JsArrayString dependents = measuredSize.getDependents();
  510. for (int i = 0; i < dependents.length(); i++) {
  511. String pid = dependents.get(i);
  512. ManagedLayout dependent = (ManagedLayout) connection
  513. .getConnectorMap().getConnector(pid);
  514. if (dependent != null) {
  515. if (heightChanged) {
  516. currentDependencyTree.setNeedsVerticalLayout(dependent,
  517. true);
  518. }
  519. if (widthChanged) {
  520. currentDependencyTree.setNeedsHorizontalLayout(dependent,
  521. true);
  522. }
  523. }
  524. }
  525. if (elementResizeListeners.containsKey(element)) {
  526. listenersToFire.add(element);
  527. }
  528. }
  529. private static boolean isManagedLayout(ComponentConnector connector) {
  530. return connector instanceof ManagedLayout;
  531. }
  532. public void forceLayout() {
  533. ConnectorMap connectorMap = connection.getConnectorMap();
  534. ComponentConnector[] componentConnectors = connectorMap
  535. .getComponentConnectors();
  536. for (ComponentConnector connector : componentConnectors) {
  537. if (connector instanceof ManagedLayout) {
  538. setNeedsLayout((ManagedLayout) connector);
  539. }
  540. }
  541. setEverythingNeedsMeasure();
  542. layoutNow();
  543. }
  544. /**
  545. * Marks that a ManagedLayout should be layouted in the next layout phase
  546. * even if none of the elements managed by the layout have been resized.
  547. *
  548. * @param layout
  549. * the managed layout that should be layouted
  550. */
  551. public final void setNeedsLayout(ManagedLayout layout) {
  552. setNeedsHorizontalLayout(layout);
  553. setNeedsVerticalLayout(layout);
  554. }
  555. /**
  556. * Marks that a ManagedLayout should be layouted horizontally in the next
  557. * layout phase even if none of the elements managed by the layout have been
  558. * resized horizontally.
  559. *
  560. * For SimpleManagedLayout which is always layouted in both directions, this
  561. * has the same effect as {@link #setNeedsLayout(ManagedLayout)}.
  562. *
  563. * @param layout
  564. * the managed layout that should be layouted
  565. */
  566. public final void setNeedsHorizontalLayout(ManagedLayout layout) {
  567. needsHorizontalLayout.add(layout);
  568. }
  569. /**
  570. * Marks that a ManagedLayout should be layouted vertically in the next
  571. * layout phase even if none of the elements managed by the layout have been
  572. * resized vertically.
  573. *
  574. * For SimpleManagedLayout which is always layouted in both directions, this
  575. * has the same effect as {@link #setNeedsLayout(ManagedLayout)}.
  576. *
  577. * @param layout
  578. * the managed layout that should be layouted
  579. */
  580. public final void setNeedsVerticalLayout(ManagedLayout layout) {
  581. needsVerticalLayout.add(layout);
  582. }
  583. /**
  584. * Gets the outer height (including margins, paddings and borders) of the
  585. * given element, provided that it has been measured. These elements are
  586. * guaranteed to be measured:
  587. * <ul>
  588. * <li>ManagedLayotus and their child Connectors
  589. * <li>Elements for which there is at least one ElementResizeListener
  590. * <li>Elements for which at least one ManagedLayout has registered a
  591. * dependency
  592. * </ul>
  593. *
  594. * -1 is returned if the element has not been measured. If 0 is returned, it
  595. * might indicate that the element is not attached to the DOM.
  596. *
  597. * @param element
  598. * the element to get the measured size for
  599. * @return the measured outer height (including margins, paddings and
  600. * borders) of the element in pixels.
  601. */
  602. public final int getOuterHeight(Element element) {
  603. return getMeasuredSize(element, nullSize).getOuterHeight();
  604. }
  605. /**
  606. * Gets the outer width (including margins, paddings and borders) of the
  607. * given element, provided that it has been measured. These elements are
  608. * guaranteed to be measured:
  609. * <ul>
  610. * <li>ManagedLayotus and their child Connectors
  611. * <li>Elements for which there is at least one ElementResizeListener
  612. * <li>Elements for which at least one ManagedLayout has registered a
  613. * dependency
  614. * </ul>
  615. *
  616. * -1 is returned if the element has not been measured. If 0 is returned, it
  617. * might indicate that the element is not attached to the DOM.
  618. *
  619. * @param element
  620. * the element to get the measured size for
  621. * @return the measured outer width (including margins, paddings and
  622. * borders) of the element in pixels.
  623. */
  624. public final int getOuterWidth(Element element) {
  625. return getMeasuredSize(element, nullSize).getOuterWidth();
  626. }
  627. /**
  628. * Gets the inner height (excluding margins, paddings and borders) of the
  629. * given element, provided that it has been measured. These elements are
  630. * guaranteed to be measured:
  631. * <ul>
  632. * <li>ManagedLayotus and their child Connectors
  633. * <li>Elements for which there is at least one ElementResizeListener
  634. * <li>Elements for which at least one ManagedLayout has registered a
  635. * dependency
  636. * </ul>
  637. *
  638. * -1 is returned if the element has not been measured. If 0 is returned, it
  639. * might indicate that the element is not attached to the DOM.
  640. *
  641. * @param element
  642. * the element to get the measured size for
  643. * @return the measured inner height (excluding margins, paddings and
  644. * borders) of the element in pixels.
  645. */
  646. public final int getInnerHeight(Element element) {
  647. return getMeasuredSize(element, nullSize).getInnerHeight();
  648. }
  649. /**
  650. * Gets the inner width (excluding margins, paddings and borders) of the
  651. * given element, provided that it has been measured. These elements are
  652. * guaranteed to be measured:
  653. * <ul>
  654. * <li>ManagedLayotus and their child Connectors
  655. * <li>Elements for which there is at least one ElementResizeListener
  656. * <li>Elements for which at least one ManagedLayout has registered a
  657. * dependency
  658. * </ul>
  659. *
  660. * -1 is returned if the element has not been measured. If 0 is returned, it
  661. * might indicate that the element is not attached to the DOM.
  662. *
  663. * @param element
  664. * the element to get the measured size for
  665. * @return the measured inner width (excluding margins, paddings and
  666. * borders) of the element in pixels.
  667. */
  668. public final int getInnerWidth(Element element) {
  669. return getMeasuredSize(element, nullSize).getInnerWidth();
  670. }
  671. /**
  672. * Gets the border height (top border + bottom border) of the given element,
  673. * provided that it has been measured. These elements are guaranteed to be
  674. * measured:
  675. * <ul>
  676. * <li>ManagedLayotus and their child Connectors
  677. * <li>Elements for which there is at least one ElementResizeListener
  678. * <li>Elements for which at least one ManagedLayout has registered a
  679. * dependency
  680. * </ul>
  681. *
  682. * A negative number is returned if the element has not been measured. If 0
  683. * is returned, it might indicate that the element is not attached to the
  684. * DOM.
  685. *
  686. * @param element
  687. * the element to get the measured size for
  688. * @return the measured border height (top border + bottom border) of the
  689. * element in pixels.
  690. */
  691. public final int getBorderHeight(Element element) {
  692. return getMeasuredSize(element, nullSize).getBorderHeight();
  693. }
  694. /**
  695. * Gets the padding height (top padding + bottom padding) of the given
  696. * element, provided that it has been measured. These elements are
  697. * guaranteed to be measured:
  698. * <ul>
  699. * <li>ManagedLayotus and their child Connectors
  700. * <li>Elements for which there is at least one ElementResizeListener
  701. * <li>Elements for which at least one ManagedLayout has registered a
  702. * dependency
  703. * </ul>
  704. *
  705. * A negative number is returned if the element has not been measured. If 0
  706. * is returned, it might indicate that the element is not attached to the
  707. * DOM.
  708. *
  709. * @param element
  710. * the element to get the measured size for
  711. * @return the measured padding height (top padding + bottom padding) of the
  712. * element in pixels.
  713. */
  714. public int getPaddingHeight(Element element) {
  715. return getMeasuredSize(element, nullSize).getPaddingHeight();
  716. }
  717. /**
  718. * Gets the border width (left border + right border) of the given element,
  719. * provided that it has been measured. These elements are guaranteed to be
  720. * measured:
  721. * <ul>
  722. * <li>ManagedLayotus and their child Connectors
  723. * <li>Elements for which there is at least one ElementResizeListener
  724. * <li>Elements for which at least one ManagedLayout has registered a
  725. * dependency
  726. * </ul>
  727. *
  728. * A negative number is returned if the element has not been measured. If 0
  729. * is returned, it might indicate that the element is not attached to the
  730. * DOM.
  731. *
  732. * @param element
  733. * the element to get the measured size for
  734. * @return the measured border width (left border + right border) of the
  735. * element in pixels.
  736. */
  737. public int getBorderWidth(Element element) {
  738. return getMeasuredSize(element, nullSize).getBorderWidth();
  739. }
  740. /**
  741. * Gets the padding width (left padding + right padding) of the given
  742. * element, provided that it has been measured. These elements are
  743. * guaranteed to be measured:
  744. * <ul>
  745. * <li>ManagedLayotus and their child Connectors
  746. * <li>Elements for which there is at least one ElementResizeListener
  747. * <li>Elements for which at least one ManagedLayout has registered a
  748. * dependency
  749. * </ul>
  750. *
  751. * A negative number is returned if the element has not been measured. If 0
  752. * is returned, it might indicate that the element is not attached to the
  753. * DOM.
  754. *
  755. * @param element
  756. * the element to get the measured size for
  757. * @return the measured padding width (left padding + right padding) of the
  758. * element in pixels.
  759. */
  760. public int getPaddingWidth(Element element) {
  761. return getMeasuredSize(element, nullSize).getPaddingWidth();
  762. }
  763. /**
  764. * Gets the top padding of the given element, provided that it has been
  765. * measured. These elements are guaranteed to be measured:
  766. * <ul>
  767. * <li>ManagedLayotus and their child Connectors
  768. * <li>Elements for which there is at least one ElementResizeListener
  769. * <li>Elements for which at least one ManagedLayout has registered a
  770. * dependency
  771. * </ul>
  772. *
  773. * A negative number is returned if the element has not been measured. If 0
  774. * is returned, it might indicate that the element is not attached to the
  775. * DOM.
  776. *
  777. * @param element
  778. * the element to get the measured size for
  779. * @return the measured top padding of the element in pixels.
  780. */
  781. public int getPaddingTop(Element element) {
  782. return getMeasuredSize(element, nullSize).getPaddingTop();
  783. }
  784. /**
  785. * Gets the left padding of the given element, provided that it has been
  786. * measured. These elements are guaranteed to be measured:
  787. * <ul>
  788. * <li>ManagedLayotus and their child Connectors
  789. * <li>Elements for which there is at least one ElementResizeListener
  790. * <li>Elements for which at least one ManagedLayout has registered a
  791. * dependency
  792. * </ul>
  793. *
  794. * A negative number is returned if the element has not been measured. If 0
  795. * is returned, it might indicate that the element is not attached to the
  796. * DOM.
  797. *
  798. * @param element
  799. * the element to get the measured size for
  800. * @return the measured left padding of the element in pixels.
  801. */
  802. public int getPaddingLeft(Element element) {
  803. return getMeasuredSize(element, nullSize).getPaddingLeft();
  804. }
  805. /**
  806. * Gets the bottom padding of the given element, provided that it has been
  807. * measured. These elements are guaranteed to be measured:
  808. * <ul>
  809. * <li>ManagedLayotus and their child Connectors
  810. * <li>Elements for which there is at least one ElementResizeListener
  811. * <li>Elements for which at least one ManagedLayout has registered a
  812. * dependency
  813. * </ul>
  814. *
  815. * A negative number is returned if the element has not been measured. If 0
  816. * is returned, it might indicate that the element is not attached to the
  817. * DOM.
  818. *
  819. * @param element
  820. * the element to get the measured size for
  821. * @return the measured bottom padding of the element in pixels.
  822. */
  823. public int getPaddingBottom(Element element) {
  824. return getMeasuredSize(element, nullSize).getPaddingBottom();
  825. }
  826. /**
  827. * Gets the right padding of the given element, provided that it has been
  828. * measured. These elements are guaranteed to be measured:
  829. * <ul>
  830. * <li>ManagedLayotus and their child Connectors
  831. * <li>Elements for which there is at least one ElementResizeListener
  832. * <li>Elements for which at least one ManagedLayout has registered a
  833. * dependency
  834. * </ul>
  835. *
  836. * A negative number is returned if the element has not been measured. If 0
  837. * is returned, it might indicate that the element is not attached to the
  838. * DOM.
  839. *
  840. * @param element
  841. * the element to get the measured size for
  842. * @return the measured right padding of the element in pixels.
  843. */
  844. public int getPaddingRight(Element element) {
  845. return getMeasuredSize(element, nullSize).getPaddingRight();
  846. }
  847. /**
  848. * Gets the top margin of the given element, provided that it has been
  849. * measured. These elements are guaranteed to be measured:
  850. * <ul>
  851. * <li>ManagedLayotus and their child Connectors
  852. * <li>Elements for which there is at least one ElementResizeListener
  853. * <li>Elements for which at least one ManagedLayout has registered a
  854. * dependency
  855. * </ul>
  856. *
  857. * A negative number is returned if the element has not been measured. If 0
  858. * is returned, it might indicate that the element is not attached to the
  859. * DOM.
  860. *
  861. * @param element
  862. * the element to get the measured size for
  863. * @return the measured top margin of the element in pixels.
  864. */
  865. public int getMarginTop(Element element) {
  866. return getMeasuredSize(element, nullSize).getMarginTop();
  867. }
  868. /**
  869. * Gets the right margin of the given element, provided that it has been
  870. * measured. These elements are guaranteed to be measured:
  871. * <ul>
  872. * <li>ManagedLayotus and their child Connectors
  873. * <li>Elements for which there is at least one ElementResizeListener
  874. * <li>Elements for which at least one ManagedLayout has registered a
  875. * dependency
  876. * </ul>
  877. *
  878. * A negative number is returned if the element has not been measured. If 0
  879. * is returned, it might indicate that the element is not attached to the
  880. * DOM.
  881. *
  882. * @param element
  883. * the element to get the measured size for
  884. * @return the measured right margin of the element in pixels.
  885. */
  886. public int getMarginRight(Element element) {
  887. return getMeasuredSize(element, nullSize).getMarginRight();
  888. }
  889. /**
  890. * Gets the bottom margin of the given element, provided that it has been
  891. * measured. These elements are guaranteed to be measured:
  892. * <ul>
  893. * <li>ManagedLayotus and their child Connectors
  894. * <li>Elements for which there is at least one ElementResizeListener
  895. * <li>Elements for which at least one ManagedLayout has registered a
  896. * dependency
  897. * </ul>
  898. *
  899. * A negative number is returned if the element has not been measured. If 0
  900. * is returned, it might indicate that the element is not attached to the
  901. * DOM.
  902. *
  903. * @param element
  904. * the element to get the measured size for
  905. * @return the measured bottom margin of the element in pixels.
  906. */
  907. public int getMarginBottom(Element element) {
  908. return getMeasuredSize(element, nullSize).getMarginBottom();
  909. }
  910. /**
  911. * Gets the left margin of the given element, provided that it has been
  912. * measured. These elements are guaranteed to be measured:
  913. * <ul>
  914. * <li>ManagedLayotus and their child Connectors
  915. * <li>Elements for which there is at least one ElementResizeListener
  916. * <li>Elements for which at least one ManagedLayout has registered a
  917. * dependency
  918. * </ul>
  919. *
  920. * A negative number is returned if the element has not been measured. If 0
  921. * is returned, it might indicate that the element is not attached to the
  922. * DOM.
  923. *
  924. * @param element
  925. * the element to get the measured size for
  926. * @return the measured left margin of the element in pixels.
  927. */
  928. public int getMarginLeft(Element element) {
  929. return getMeasuredSize(element, nullSize).getMarginLeft();
  930. }
  931. /**
  932. * Registers the outer height (including margins, borders and paddings) of a
  933. * component. This can be used as an optimization by ManagedLayouts; by
  934. * informing the LayoutManager about what size a component will have, the
  935. * layout propagation can continue directly without first measuring the
  936. * potentially resized elements.
  937. *
  938. * @param component
  939. * the component for which the size is reported
  940. * @param outerHeight
  941. * the new outer height (including margins, borders and paddings)
  942. * of the component in pixels
  943. */
  944. public void reportOuterHeight(ComponentConnector component, int outerHeight) {
  945. MeasuredSize measuredSize = getMeasuredSize(component);
  946. if (isLayoutRunning()) {
  947. boolean heightChanged = measuredSize.setOuterHeight(outerHeight);
  948. if (heightChanged) {
  949. onConnectorChange(component, false, true);
  950. notifyListenersAndDepdendents(component.getWidget()
  951. .getElement(), false, true);
  952. }
  953. currentDependencyTree.setNeedsVerticalMeasure(component, false);
  954. } else if (measuredSize.getOuterHeight() != outerHeight) {
  955. setNeedsMeasure(component);
  956. }
  957. }
  958. /**
  959. * Registers the height reserved for a relatively sized component. This can
  960. * be used as an optimization by ManagedLayouts; by informing the
  961. * LayoutManager about what size a component will have, the layout
  962. * propagation can continue directly without first measuring the potentially
  963. * resized elements.
  964. *
  965. * @param component
  966. * the relatively sized component for which the size is reported
  967. * @param assignedHeight
  968. * the inner height of the relatively sized component's parent
  969. * element in pixels
  970. */
  971. public void reportHeightAssignedToRelative(ComponentConnector component,
  972. int assignedHeight) {
  973. assert component.isRelativeHeight();
  974. float percentSize = parsePercent(component.getState().getHeight());
  975. int effectiveHeight = Math.round(assignedHeight * (percentSize / 100));
  976. reportOuterHeight(component, effectiveHeight);
  977. }
  978. /**
  979. * Registers the width reserved for a relatively sized component. This can
  980. * be used as an optimization by ManagedLayouts; by informing the
  981. * LayoutManager about what size a component will have, the layout
  982. * propagation can continue directly without first measuring the potentially
  983. * resized elements.
  984. *
  985. * @param component
  986. * the relatively sized component for which the size is reported
  987. * @param assignedWidth
  988. * the inner width of the relatively sized component's parent
  989. * element in pixels
  990. */
  991. public void reportWidthAssignedToRelative(ComponentConnector component,
  992. int assignedWidth) {
  993. assert component.isRelativeWidth();
  994. float percentSize = parsePercent(component.getState().getWidth());
  995. int effectiveWidth = Math.round(assignedWidth * (percentSize / 100));
  996. reportOuterWidth(component, effectiveWidth);
  997. }
  998. private static float parsePercent(String size) {
  999. return Float.parseFloat(size.substring(0, size.length() - 1));
  1000. }
  1001. /**
  1002. * Registers the outer width (including margins, borders and paddings) of a
  1003. * component. This can be used as an optimization by ManagedLayouts; by
  1004. * informing the LayoutManager about what size a component will have, the
  1005. * layout propagation can continue directly without first measuring the
  1006. * potentially resized elements.
  1007. *
  1008. * @param component
  1009. * the component for which the size is reported
  1010. * @param outerWidth
  1011. * the new outer width (including margins, borders and paddings)
  1012. * of the component in pixels
  1013. */
  1014. public void reportOuterWidth(ComponentConnector component, int outerWidth) {
  1015. MeasuredSize measuredSize = getMeasuredSize(component);
  1016. if (isLayoutRunning()) {
  1017. boolean widthChanged = measuredSize.setOuterWidth(outerWidth);
  1018. if (widthChanged) {
  1019. onConnectorChange(component, true, false);
  1020. notifyListenersAndDepdendents(component.getWidget()
  1021. .getElement(), true, false);
  1022. }
  1023. currentDependencyTree.setNeedsHorizontalMeasure(component, false);
  1024. } else if (measuredSize.getOuterWidth() != outerWidth) {
  1025. setNeedsMeasure(component);
  1026. }
  1027. }
  1028. /**
  1029. * Adds a listener that will be notified whenever the size of a specific
  1030. * element changes. Adding a listener to an element also ensures that all
  1031. * sizes for that element will be available starting from the next layout
  1032. * phase.
  1033. *
  1034. * @param element
  1035. * the element that should be checked for size changes
  1036. * @param listener
  1037. * an ElementResizeListener that will be informed whenever the
  1038. * size of the target element has changed
  1039. */
  1040. public void addElementResizeListener(Element element,
  1041. ElementResizeListener listener) {
  1042. Collection<ElementResizeListener> listeners = elementResizeListeners
  1043. .get(element);
  1044. if (listeners == null) {
  1045. listeners = new HashSet<ElementResizeListener>();
  1046. elementResizeListeners.put(element, listeners);
  1047. ensureMeasured(element);
  1048. }
  1049. listeners.add(listener);
  1050. }
  1051. /**
  1052. * Removes an element resize listener from the provided element. This might
  1053. * cause this LayoutManager to stop tracking the size of the element if no
  1054. * other sources are interested in the size.
  1055. *
  1056. * @param element
  1057. * the element to which the element resize listener was
  1058. * previously added
  1059. * @param listener
  1060. * the ElementResizeListener that should no longer get informed
  1061. * about size changes to the target element.
  1062. */
  1063. public void removeElementResizeListener(Element element,
  1064. ElementResizeListener listener) {
  1065. Collection<ElementResizeListener> listeners = elementResizeListeners
  1066. .get(element);
  1067. if (listeners != null) {
  1068. listeners.remove(listener);
  1069. if (listeners.isEmpty()) {
  1070. elementResizeListeners.remove(element);
  1071. stopMeasuringIfUnecessary(element);
  1072. }
  1073. }
  1074. }
  1075. private void stopMeasuringIfUnecessary(Element element) {
  1076. if (!needsMeasure(element)) {
  1077. measuredNonConnectorElements.remove(element);
  1078. setMeasuredSize(element, null);
  1079. }
  1080. }
  1081. /**
  1082. * Informs this LayoutManager that the size of a component might have
  1083. * changed. If there is no upcoming layout phase, a new layout phase is
  1084. * scheduled. This method should be used whenever a size might have changed
  1085. * from outside of Vaadin's normal update phase, e.g. when an icon has been
  1086. * loaded or when the user resizes some part of the UI using the mouse.
  1087. *
  1088. * @param component
  1089. * the component whose size might have changed.
  1090. */
  1091. public void setNeedsMeasure(ComponentConnector component) {
  1092. if (isLayoutRunning()) {
  1093. currentDependencyTree.setNeedsMeasure(component, true);
  1094. } else {
  1095. needsMeasure.add(component);
  1096. layoutLater();
  1097. }
  1098. }
  1099. public void setEverythingNeedsMeasure() {
  1100. everythingNeedsMeasure = true;
  1101. }
  1102. /**
  1103. * Clean measured sizes which are no longer needed. Only for IE8.
  1104. */
  1105. protected void cleanMeasuredSizes() {
  1106. }
  1107. }