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

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