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 74KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857
  1. /*
  2. * Copyright 2000-2016 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.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 java.util.logging.Level;
  23. import java.util.logging.Logger;
  24. import com.google.gwt.core.client.Duration;
  25. import com.google.gwt.core.client.JsArrayString;
  26. import com.google.gwt.dom.client.Element;
  27. import com.google.gwt.dom.client.Style;
  28. import com.google.gwt.dom.client.Style.Overflow;
  29. import com.google.gwt.user.client.Timer;
  30. import com.vaadin.client.MeasuredSize.MeasureResult;
  31. import com.vaadin.client.ui.ManagedLayout;
  32. import com.vaadin.client.ui.PostLayoutListener;
  33. import com.vaadin.client.ui.SimpleManagedLayout;
  34. import com.vaadin.client.ui.VNotification;
  35. import com.vaadin.client.ui.layout.ElementResizeEvent;
  36. import com.vaadin.client.ui.layout.ElementResizeListener;
  37. import com.vaadin.client.ui.layout.LayoutDependencyTree;
  38. public class LayoutManager {
  39. private static final String STATE_CHANGE_MESSAGE = "Cannot run layout while processing state change from the server.";
  40. private static final String LOOP_ABORT_MESSAGE = "Aborting layout after 100 passes. This would probably be an infinite loop.";
  41. private static final boolean debugLogging = false;
  42. private ApplicationConnection connection;
  43. private final Set<Element> measuredNonConnectorElements = new HashSet<Element>();
  44. private final MeasuredSize nullSize = new MeasuredSize();
  45. private LayoutDependencyTree currentDependencyTree;
  46. private FastStringSet needsHorizontalLayout = FastStringSet.create();
  47. private FastStringSet needsVerticalLayout = FastStringSet.create();
  48. private FastStringSet needsMeasure = FastStringSet.create();
  49. private FastStringSet pendingOverflowFixes = FastStringSet.create();
  50. private final Map<Element, Collection<ElementResizeListener>> elementResizeListeners = new HashMap<Element, Collection<ElementResizeListener>>();
  51. private final Set<Element> listenersToFire = new HashSet<Element>();
  52. private boolean layoutPending = false;
  53. private Timer layoutTimer = new Timer() {
  54. @Override
  55. public void run() {
  56. layoutNow();
  57. }
  58. };
  59. private boolean everythingNeedsMeasure = false;
  60. /**
  61. * Sets the application connection this instance is connected to. Called
  62. * internally by the framework.
  63. *
  64. * @param connection
  65. * the application connection this instance is connected to
  66. */
  67. public void setConnection(ApplicationConnection connection) {
  68. if (this.connection != null) {
  69. throw new RuntimeException(
  70. "LayoutManager connection can never be changed");
  71. }
  72. this.connection = connection;
  73. }
  74. /**
  75. * Returns the application connection for this layout manager.
  76. *
  77. * @return connection
  78. */
  79. protected ApplicationConnection getConnection() {
  80. return connection;
  81. }
  82. /**
  83. * Gets the layout manager associated with the given
  84. * {@link ApplicationConnection}.
  85. *
  86. * @param connection
  87. * the application connection to get a layout manager for
  88. * @return the layout manager associated with the provided application
  89. * connection
  90. */
  91. public static LayoutManager get(ApplicationConnection connection) {
  92. return connection.getLayoutManager();
  93. }
  94. /**
  95. * Registers that a ManagedLayout is depending on the size of an Element.
  96. * This causes this layout manager to measure the element in the beginning
  97. * of every layout phase and call the appropriate layout method of the
  98. * managed layout if the size of the element has changed.
  99. *
  100. * @param owner
  101. * the ManagedLayout that depends on an element
  102. * @param element
  103. * the Element that should be measured
  104. */
  105. public void registerDependency(ManagedLayout owner, Element element) {
  106. MeasuredSize measuredSize = ensureMeasured(element);
  107. setNeedsLayout(owner);
  108. measuredSize.addDependent(owner.getConnectorId());
  109. }
  110. private MeasuredSize ensureMeasured(Element element) {
  111. MeasuredSize measuredSize = getMeasuredSize(element, null);
  112. if (measuredSize == null) {
  113. measuredSize = new MeasuredSize();
  114. if (ConnectorMap.get(connection).getConnector(element) == null) {
  115. measuredNonConnectorElements.add(element);
  116. }
  117. setMeasuredSize(element, measuredSize);
  118. }
  119. return measuredSize;
  120. }
  121. private boolean needsMeasure(Element e) {
  122. ComponentConnector connector = connection.getConnectorMap()
  123. .getConnector(e);
  124. if (connector != null && needsMeasureForManagedLayout(connector)) {
  125. return true;
  126. } else if (elementResizeListeners.containsKey(e)) {
  127. return true;
  128. } else if (getMeasuredSize(e, nullSize).hasDependents()) {
  129. return true;
  130. } else {
  131. return false;
  132. }
  133. }
  134. private boolean needsMeasureForManagedLayout(ComponentConnector connector) {
  135. if (connector instanceof ManagedLayout) {
  136. return true;
  137. } else if (connector.getParent() instanceof ManagedLayout) {
  138. return true;
  139. } else {
  140. return false;
  141. }
  142. }
  143. /**
  144. * Assigns a measured size to an element. Method defined as protected to
  145. * allow separate implementation for IE8.
  146. *
  147. * @param element
  148. * the dom element to attach the measured size to
  149. * @param measuredSize
  150. * the measured size to attach to the element. If
  151. * <code>null</code>, any previous measured size is removed.
  152. */
  153. protected native void setMeasuredSize(Element element,
  154. MeasuredSize measuredSize)
  155. /*-{
  156. if (measuredSize) {
  157. element.vMeasuredSize = measuredSize;
  158. } else {
  159. delete element.vMeasuredSize;
  160. }
  161. }-*/;
  162. /**
  163. * Gets the measured size for an element. Method defined as protected to
  164. * allow separate implementation for IE8.
  165. *
  166. * @param element
  167. * The element to get measured size for
  168. * @param defaultSize
  169. * The size to return if no measured size could be found
  170. * @return The measured size for the element or {@literal defaultSize}
  171. */
  172. protected native MeasuredSize getMeasuredSize(Element element,
  173. MeasuredSize defaultSize)
  174. /*-{
  175. return element.vMeasuredSize || defaultSize;
  176. }-*/;
  177. private final MeasuredSize getMeasuredSize(Element element) {
  178. MeasuredSize measuredSize = getMeasuredSize(element, null);
  179. if (measuredSize == null) {
  180. measuredSize = new MeasuredSize();
  181. setMeasuredSize(element, measuredSize);
  182. }
  183. return measuredSize;
  184. }
  185. /**
  186. * Registers that a ManagedLayout is no longer depending on the size of an
  187. * Element.
  188. *
  189. * @see #registerDependency(ManagedLayout, Element)
  190. *
  191. * @param owner
  192. * the ManagedLayout no longer depends on an element
  193. * @param element
  194. * the Element that that no longer needs to be measured
  195. */
  196. public void unregisterDependency(ManagedLayout owner, Element element) {
  197. MeasuredSize measuredSize = getMeasuredSize(element, null);
  198. if (measuredSize == null) {
  199. return;
  200. }
  201. measuredSize.removeDependent(owner.getConnectorId());
  202. stopMeasuringIfUnecessary(element);
  203. }
  204. public boolean isLayoutRunning() {
  205. return currentDependencyTree != null;
  206. }
  207. private void countLayout(FastStringMap<Integer> layoutCounts,
  208. ManagedLayout layout) {
  209. Integer count = layoutCounts.get(layout.getConnectorId());
  210. if (count == null) {
  211. count = Integer.valueOf(0);
  212. } else {
  213. count = Integer.valueOf(count.intValue() + 1);
  214. }
  215. layoutCounts.put(layout.getConnectorId(), count);
  216. if (count.intValue() > 2) {
  217. getLogger().severe(Util.getConnectorString(layout)
  218. + " has been layouted " + count.intValue() + " times");
  219. }
  220. }
  221. public void layoutLater() {
  222. if (!layoutPending) {
  223. layoutPending = true;
  224. layoutTimer.schedule(100);
  225. }
  226. }
  227. public void layoutNow() {
  228. if (isLayoutRunning()) {
  229. throw new IllegalStateException(
  230. "Can't start a new layout phase before the previous layout phase ends.");
  231. }
  232. if (connection.getMessageHandler().isUpdatingState()) {
  233. // If assertions are enabled, throw an exception
  234. assert false : STATE_CHANGE_MESSAGE;
  235. // Else just log a warning and postpone the layout
  236. getLogger().warning(STATE_CHANGE_MESSAGE);
  237. // Framework will call layoutNow when the state update is completed
  238. return;
  239. }
  240. layoutPending = false;
  241. layoutTimer.cancel();
  242. try {
  243. currentDependencyTree = new LayoutDependencyTree(connection);
  244. doLayout();
  245. } finally {
  246. currentDependencyTree = null;
  247. }
  248. }
  249. /**
  250. * Called once per iteration in the layout loop before size calculations so
  251. * different browsers quirks can be handled. Mainly this is currently for
  252. * the IE8 permutation.
  253. */
  254. protected void performBrowserLayoutHacks() {
  255. // Permutations implement this
  256. }
  257. private void doLayout() {
  258. getLogger().info("Starting layout phase");
  259. Profiler.enter("LayoutManager phase init");
  260. FastStringMap<Integer> layoutCounts = FastStringMap.create();
  261. int passes = 0;
  262. Duration totalDuration = new Duration();
  263. ConnectorMap connectorMap = ConnectorMap.get(connection);
  264. JsArrayString dump = needsHorizontalLayout.dump();
  265. int dumpLength = dump.length();
  266. for (int i = 0; i < dumpLength; i++) {
  267. String layoutId = dump.get(i);
  268. currentDependencyTree.setNeedsHorizontalLayout(layoutId, true);
  269. }
  270. dump = needsVerticalLayout.dump();
  271. dumpLength = dump.length();
  272. for (int i = 0; i < dumpLength; i++) {
  273. String layoutId = dump.get(i);
  274. currentDependencyTree.setNeedsVerticalLayout(layoutId, true);
  275. }
  276. needsHorizontalLayout = FastStringSet.create();
  277. needsVerticalLayout = FastStringSet.create();
  278. dump = needsMeasure.dump();
  279. dumpLength = dump.length();
  280. for (int i = 0; i < dumpLength; i++) {
  281. ServerConnector connector = connectorMap.getConnector(dump.get(i));
  282. if (connector != null) {
  283. currentDependencyTree
  284. .setNeedsMeasure((ComponentConnector) connector, true);
  285. }
  286. }
  287. needsMeasure = FastStringSet.create();
  288. measureNonConnectors();
  289. Profiler.leave("LayoutManager phase init");
  290. while (true) {
  291. Profiler.enter("Layout pass");
  292. passes++;
  293. performBrowserLayoutHacks();
  294. Profiler.enter("Layout measure connectors");
  295. int measuredConnectorCount = measureConnectors(
  296. currentDependencyTree, everythingNeedsMeasure);
  297. Profiler.leave("Layout measure connectors");
  298. everythingNeedsMeasure = false;
  299. if (measuredConnectorCount == 0) {
  300. getLogger().info("No more changes in pass " + passes);
  301. Profiler.leave("Layout pass");
  302. break;
  303. }
  304. int firedListeners = 0;
  305. if (!listenersToFire.isEmpty()) {
  306. firedListeners = listenersToFire.size();
  307. Profiler.enter("Layout fire resize events");
  308. for (Element element : listenersToFire) {
  309. Collection<ElementResizeListener> listeners = elementResizeListeners
  310. .get(element);
  311. if (listeners != null) {
  312. Profiler.enter(
  313. "Layout fire resize events - listeners not null");
  314. Profiler.enter(
  315. "ElementResizeListener.onElementResize copy list");
  316. ElementResizeListener[] array = listeners.toArray(
  317. new ElementResizeListener[listeners.size()]);
  318. Profiler.leave(
  319. "ElementResizeListener.onElementResize copy list");
  320. ElementResizeEvent event = new ElementResizeEvent(this,
  321. element);
  322. for (ElementResizeListener listener : array) {
  323. try {
  324. String key = null;
  325. if (Profiler.isEnabled()) {
  326. Profiler.enter(
  327. "ElementResizeListener.onElementResize construct profiler key");
  328. key = "ElementResizeListener.onElementResize for "
  329. + listener.getClass()
  330. .getSimpleName();
  331. Profiler.leave(
  332. "ElementResizeListener.onElementResize construct profiler key");
  333. Profiler.enter(key);
  334. }
  335. listener.onElementResize(event);
  336. if (Profiler.isEnabled()) {
  337. Profiler.leave(key);
  338. }
  339. } catch (RuntimeException e) {
  340. getLogger().log(Level.SEVERE,
  341. "Error in resize listener", e);
  342. }
  343. }
  344. Profiler.leave(
  345. "Layout fire resize events - listeners not null");
  346. }
  347. }
  348. listenersToFire.clear();
  349. Profiler.leave("Layout fire resize events");
  350. }
  351. Profiler.enter("LayoutManager handle ManagedLayout");
  352. FastStringSet updatedSet = FastStringSet.create();
  353. int layoutCount = 0;
  354. while (currentDependencyTree.hasHorizontalConnectorToLayout()
  355. || currentDependencyTree.hasVerticaConnectorToLayout()) {
  356. JsArrayString layoutTargets = currentDependencyTree
  357. .getHorizontalLayoutTargetsJsArray();
  358. int length = layoutTargets.length();
  359. for (int i = 0; i < length; i++) {
  360. ManagedLayout layout = (ManagedLayout) connectorMap
  361. .getConnector(layoutTargets.get(i));
  362. if (layout instanceof DirectionalManagedLayout) {
  363. currentDependencyTree
  364. .markAsHorizontallyLayouted(layout);
  365. DirectionalManagedLayout cl = (DirectionalManagedLayout) layout;
  366. try {
  367. String key = null;
  368. if (Profiler.isEnabled()) {
  369. key = "layoutHorizontally() for "
  370. + cl.getClass().getSimpleName();
  371. Profiler.enter(key);
  372. }
  373. cl.layoutHorizontally();
  374. layoutCount++;
  375. if (Profiler.isEnabled()) {
  376. Profiler.leave(key);
  377. }
  378. } catch (RuntimeException e) {
  379. getLogger().log(Level.SEVERE,
  380. "Error in ManagedLayout handling", e);
  381. }
  382. countLayout(layoutCounts, cl);
  383. } else {
  384. currentDependencyTree
  385. .markAsHorizontallyLayouted(layout);
  386. currentDependencyTree.markAsVerticallyLayouted(layout);
  387. SimpleManagedLayout rr = (SimpleManagedLayout) layout;
  388. try {
  389. String key = null;
  390. if (Profiler.isEnabled()) {
  391. key = "layout() for "
  392. + rr.getClass().getSimpleName();
  393. Profiler.enter(key);
  394. }
  395. rr.layout();
  396. layoutCount++;
  397. if (Profiler.isEnabled()) {
  398. Profiler.leave(key);
  399. }
  400. } catch (RuntimeException e) {
  401. getLogger().log(Level.SEVERE,
  402. "Error in SimpleManagedLayout (horizontal) handling",
  403. e);
  404. }
  405. countLayout(layoutCounts, rr);
  406. }
  407. if (debugLogging) {
  408. updatedSet.add(layout.getConnectorId());
  409. }
  410. }
  411. layoutTargets = currentDependencyTree
  412. .getVerticalLayoutTargetsJsArray();
  413. length = layoutTargets.length();
  414. for (int i = 0; i < length; i++) {
  415. ManagedLayout layout = (ManagedLayout) connectorMap
  416. .getConnector(layoutTargets.get(i));
  417. if (layout instanceof DirectionalManagedLayout) {
  418. currentDependencyTree.markAsVerticallyLayouted(layout);
  419. DirectionalManagedLayout cl = (DirectionalManagedLayout) layout;
  420. try {
  421. String key = null;
  422. if (Profiler.isEnabled()) {
  423. key = "layoutVertically() for "
  424. + cl.getClass().getSimpleName();
  425. Profiler.enter(key);
  426. }
  427. cl.layoutVertically();
  428. layoutCount++;
  429. if (Profiler.isEnabled()) {
  430. Profiler.leave(key);
  431. }
  432. } catch (RuntimeException e) {
  433. getLogger().log(Level.SEVERE,
  434. "Error in DirectionalManagedLayout handling",
  435. e);
  436. }
  437. countLayout(layoutCounts, cl);
  438. } else {
  439. currentDependencyTree
  440. .markAsHorizontallyLayouted(layout);
  441. currentDependencyTree.markAsVerticallyLayouted(layout);
  442. SimpleManagedLayout rr = (SimpleManagedLayout) layout;
  443. try {
  444. String key = null;
  445. if (Profiler.isEnabled()) {
  446. key = "layout() for "
  447. + rr.getClass().getSimpleName();
  448. Profiler.enter(key);
  449. }
  450. rr.layout();
  451. layoutCount++;
  452. if (Profiler.isEnabled()) {
  453. Profiler.leave(key);
  454. }
  455. } catch (RuntimeException e) {
  456. getLogger().log(Level.SEVERE,
  457. "Error in SimpleManagedLayout (vertical) handling",
  458. e);
  459. }
  460. countLayout(layoutCounts, rr);
  461. }
  462. if (debugLogging) {
  463. updatedSet.add(layout.getConnectorId());
  464. }
  465. }
  466. }
  467. Profiler.leave("LayoutManager handle ManagedLayout");
  468. if (debugLogging) {
  469. JsArrayString changedCids = updatedSet.dump();
  470. StringBuilder b = new StringBuilder(" ");
  471. b.append(changedCids.length());
  472. b.append(" requestLayout invocations ");
  473. if (changedCids.length() < 30) {
  474. for (int i = 0; i < changedCids.length(); i++) {
  475. if (i != 0) {
  476. b.append(", ");
  477. } else {
  478. b.append(": ");
  479. }
  480. String connectorString = changedCids.get(i);
  481. if (changedCids.length() < 10) {
  482. ServerConnector connector = ConnectorMap
  483. .get(connection)
  484. .getConnector(connectorString);
  485. connectorString = Util
  486. .getConnectorString(connector);
  487. }
  488. b.append(connectorString);
  489. }
  490. }
  491. getLogger().info(b.toString());
  492. }
  493. Profiler.leave("Layout pass");
  494. getLogger().info("Pass " + passes + " measured "
  495. + measuredConnectorCount + " elements, fired "
  496. + firedListeners + " listeners and did " + layoutCount
  497. + " layouts.");
  498. if (passes > 100) {
  499. getLogger().severe(LOOP_ABORT_MESSAGE);
  500. if (ApplicationConfiguration.isDebugMode()) {
  501. VNotification
  502. .createNotification(VNotification.DELAY_FOREVER,
  503. connection.getUIConnector().getWidget())
  504. .show(LOOP_ABORT_MESSAGE, VNotification.CENTERED,
  505. "error");
  506. }
  507. break;
  508. }
  509. }
  510. Profiler.enter("layout PostLayoutListener");
  511. JsArrayObject<ComponentConnector> componentConnectors = connectorMap
  512. .getComponentConnectorsAsJsArray();
  513. int size = componentConnectors.size();
  514. for (int i = 0; i < size; i++) {
  515. ComponentConnector connector = componentConnectors.get(i);
  516. if (connector instanceof PostLayoutListener) {
  517. String key = null;
  518. if (Profiler.isEnabled()) {
  519. key = "layout PostLayoutListener for "
  520. + connector.getClass().getSimpleName();
  521. Profiler.enter(key);
  522. }
  523. ((PostLayoutListener) connector).postLayout();
  524. if (Profiler.isEnabled()) {
  525. Profiler.leave(key);
  526. }
  527. }
  528. }
  529. Profiler.leave("layout PostLayoutListener");
  530. cleanMeasuredSizes();
  531. getLogger().info("Total layout phase time: "
  532. + totalDuration.elapsedMillis() + "ms");
  533. }
  534. private void logConnectorStatus(int connectorId) {
  535. currentDependencyTree.logDependencyStatus(
  536. (ComponentConnector) ConnectorMap.get(connection)
  537. .getConnector(Integer.toString(connectorId)));
  538. }
  539. private int measureConnectors(LayoutDependencyTree layoutDependencyTree,
  540. boolean measureAll) {
  541. Profiler.enter("Layout overflow fix handling");
  542. JsArrayString pendingOverflowConnectorsIds = pendingOverflowFixes
  543. .dump();
  544. int pendingOverflowCount = pendingOverflowConnectorsIds.length();
  545. ConnectorMap connectorMap = ConnectorMap.get(connection);
  546. if (pendingOverflowCount > 0) {
  547. HashMap<Element, String> originalOverflows = new HashMap<Element, String>();
  548. FastStringSet delayedOverflowFixes = FastStringSet.create();
  549. // First set overflow to hidden (and save previous value so it can
  550. // be restored later)
  551. for (int i = 0; i < pendingOverflowCount; i++) {
  552. String connectorId = pendingOverflowConnectorsIds.get(i);
  553. ComponentConnector componentConnector = (ComponentConnector) connectorMap
  554. .getConnector(connectorId);
  555. if (delayOverflowFix(componentConnector)) {
  556. delayedOverflowFixes.add(connectorId);
  557. continue;
  558. }
  559. if (debugLogging) {
  560. getLogger().info("Doing overflow fix for "
  561. + Util.getConnectorString(componentConnector)
  562. + " in " + Util.getConnectorString(
  563. componentConnector.getParent()));
  564. }
  565. Profiler.enter("Overflow fix apply");
  566. Element parentElement = componentConnector.getWidget()
  567. .getElement().getParentElement();
  568. Style style = parentElement.getStyle();
  569. String originalOverflow = style.getOverflow();
  570. if (originalOverflow != null
  571. && !originalOverflows.containsKey(parentElement)) {
  572. // Store original value for restore, but only the first time
  573. // the value is changed
  574. originalOverflows.put(parentElement, originalOverflow);
  575. }
  576. style.setOverflow(Overflow.HIDDEN);
  577. Profiler.leave("Overflow fix apply");
  578. }
  579. pendingOverflowFixes.removeAll(delayedOverflowFixes);
  580. JsArrayString remainingOverflowFixIds = pendingOverflowFixes.dump();
  581. int remainingCount = remainingOverflowFixIds.length();
  582. Profiler.enter("Overflow fix reflow");
  583. // Then ensure all scrolling elements are reflowed by measuring
  584. for (int i = 0; i < remainingCount; i++) {
  585. ComponentConnector componentConnector = (ComponentConnector) connectorMap
  586. .getConnector(remainingOverflowFixIds.get(i));
  587. componentConnector.getWidget().getElement().getParentElement()
  588. .getOffsetHeight();
  589. }
  590. Profiler.leave("Overflow fix reflow");
  591. Profiler.enter("Overflow fix restore");
  592. // Finally restore old overflow value and update bookkeeping
  593. for (int i = 0; i < remainingCount; i++) {
  594. String connectorId = remainingOverflowFixIds.get(i);
  595. ComponentConnector componentConnector = (ComponentConnector) connectorMap
  596. .getConnector(connectorId);
  597. Element parentElement = componentConnector.getWidget()
  598. .getElement().getParentElement();
  599. parentElement.getStyle().setProperty("overflow",
  600. originalOverflows.get(parentElement));
  601. layoutDependencyTree.setNeedsMeasure(componentConnector, true);
  602. }
  603. Profiler.leave("Overflow fix restore");
  604. if (!pendingOverflowFixes.isEmpty()) {
  605. getLogger().info(
  606. "Did overflow fix for " + remainingCount + " elements");
  607. }
  608. pendingOverflowFixes = delayedOverflowFixes;
  609. }
  610. Profiler.leave("Layout overflow fix handling");
  611. int measureCount = 0;
  612. if (measureAll) {
  613. Profiler.enter("Layout measureAll");
  614. JsArrayObject<ComponentConnector> allConnectors = connectorMap
  615. .getComponentConnectorsAsJsArray();
  616. int size = allConnectors.size();
  617. // Find connectors that should actually be measured
  618. JsArrayObject<ComponentConnector> connectors = JsArrayObject
  619. .createArray().cast();
  620. for (int i = 0; i < size; i++) {
  621. ComponentConnector candidate = allConnectors.get(i);
  622. if (!Util.shouldSkipMeasurementOfConnector(candidate)
  623. && needsMeasure(candidate.getWidget().getElement())) {
  624. connectors.add(candidate);
  625. }
  626. }
  627. int connectorCount = connectors.size();
  628. for (int i = 0; i < connectorCount; i++) {
  629. measureConnector(connectors.get(i));
  630. }
  631. for (int i = 0; i < connectorCount; i++) {
  632. layoutDependencyTree.setNeedsMeasure(connectors.get(i), false);
  633. }
  634. measureCount += connectorCount;
  635. Profiler.leave("Layout measureAll");
  636. }
  637. Profiler.enter("Layout measure from tree");
  638. while (layoutDependencyTree.hasConnectorsToMeasure()) {
  639. JsArrayString measureTargets = layoutDependencyTree
  640. .getMeasureTargetsJsArray();
  641. int length = measureTargets.length();
  642. for (int i = 0; i < length; i++) {
  643. ComponentConnector connector = (ComponentConnector) connectorMap
  644. .getConnector(measureTargets.get(i));
  645. measureConnector(connector);
  646. measureCount++;
  647. }
  648. for (int i = 0; i < length; i++) {
  649. ComponentConnector connector = (ComponentConnector) connectorMap
  650. .getConnector(measureTargets.get(i));
  651. layoutDependencyTree.setNeedsMeasure(connector, false);
  652. }
  653. }
  654. Profiler.leave("Layout measure from tree");
  655. return measureCount;
  656. }
  657. /*
  658. * Delay the overflow fix if the involved connectors might still change
  659. */
  660. private boolean delayOverflowFix(ComponentConnector componentConnector) {
  661. if (!currentDependencyTree.noMoreChangesExpected(componentConnector)) {
  662. return true;
  663. }
  664. ServerConnector parent = componentConnector.getParent();
  665. if (parent instanceof ComponentConnector && !currentDependencyTree
  666. .noMoreChangesExpected((ComponentConnector) parent)) {
  667. return true;
  668. }
  669. return false;
  670. }
  671. private void measureConnector(ComponentConnector connector) {
  672. Profiler.enter("LayoutManager.measureConnector");
  673. Element element = connector.getWidget().getElement();
  674. MeasuredSize measuredSize = getMeasuredSize(element);
  675. MeasureResult measureResult = measuredAndUpdate(element, measuredSize);
  676. if (measureResult.isChanged()) {
  677. onConnectorChange(connector, measureResult.isWidthChanged(),
  678. measureResult.isHeightChanged());
  679. }
  680. Profiler.leave("LayoutManager.measureConnector");
  681. }
  682. private void onConnectorChange(ComponentConnector connector,
  683. boolean widthChanged, boolean heightChanged) {
  684. Profiler.enter("LayoutManager.onConnectorChange");
  685. Profiler.enter("LayoutManager.onConnectorChange setNeedsOverflowFix");
  686. setNeedsOverflowFix(connector);
  687. Profiler.leave("LayoutManager.onConnectorChange setNeedsOverflowFix");
  688. Profiler.enter("LayoutManager.onConnectorChange heightChanged");
  689. if (heightChanged) {
  690. currentDependencyTree.markHeightAsChanged(connector);
  691. }
  692. Profiler.leave("LayoutManager.onConnectorChange heightChanged");
  693. Profiler.enter("LayoutManager.onConnectorChange widthChanged");
  694. if (widthChanged) {
  695. currentDependencyTree.markWidthAsChanged(connector);
  696. }
  697. Profiler.leave("LayoutManager.onConnectorChange widthChanged");
  698. Profiler.leave("LayoutManager.onConnectorChange");
  699. }
  700. private void setNeedsOverflowFix(ComponentConnector connector) {
  701. // IE9 doesn't need the original fix, but for some reason it needs this
  702. if (BrowserInfo.get().requiresOverflowAutoFix()
  703. || BrowserInfo.get().isIE9()) {
  704. ComponentConnector scrollingBoundary = currentDependencyTree
  705. .getScrollingBoundary(connector);
  706. if (scrollingBoundary != null) {
  707. pendingOverflowFixes.add(scrollingBoundary.getConnectorId());
  708. }
  709. }
  710. }
  711. private void measureNonConnectors() {
  712. Profiler.enter("LayoutManager.measureNonConenctors");
  713. for (Element element : measuredNonConnectorElements) {
  714. measuredAndUpdate(element, getMeasuredSize(element, null));
  715. }
  716. Profiler.leave("LayoutManager.measureNonConenctors");
  717. getLogger().info("Measured " + measuredNonConnectorElements.size()
  718. + " non connector elements");
  719. }
  720. private MeasureResult measuredAndUpdate(Element element,
  721. MeasuredSize measuredSize) {
  722. MeasureResult measureResult = measuredSize.measure(element);
  723. if (measureResult.isChanged()) {
  724. notifyListenersAndDepdendents(element,
  725. measureResult.isWidthChanged(),
  726. measureResult.isHeightChanged());
  727. }
  728. return measureResult;
  729. }
  730. private void notifyListenersAndDepdendents(Element element,
  731. boolean widthChanged, boolean heightChanged) {
  732. assert widthChanged || heightChanged;
  733. Profiler.enter("LayoutManager.notifyListenersAndDepdendents");
  734. MeasuredSize measuredSize = getMeasuredSize(element, nullSize);
  735. JsArrayString dependents = measuredSize.getDependents();
  736. for (int i = 0; i < dependents.length(); i++) {
  737. String pid = dependents.get(i);
  738. if (pid != null) {
  739. if (heightChanged) {
  740. currentDependencyTree.setNeedsVerticalLayout(pid, true);
  741. }
  742. if (widthChanged) {
  743. currentDependencyTree.setNeedsHorizontalLayout(pid, true);
  744. }
  745. }
  746. }
  747. if (elementResizeListeners.containsKey(element)) {
  748. listenersToFire.add(element);
  749. }
  750. Profiler.leave("LayoutManager.notifyListenersAndDepdendents");
  751. }
  752. private static boolean isManagedLayout(ComponentConnector connector) {
  753. return connector instanceof ManagedLayout;
  754. }
  755. public void forceLayout() {
  756. ConnectorMap connectorMap = connection.getConnectorMap();
  757. JsArrayObject<ComponentConnector> componentConnectors = connectorMap
  758. .getComponentConnectorsAsJsArray();
  759. int size = componentConnectors.size();
  760. for (int i = 0; i < size; i++) {
  761. ComponentConnector connector = componentConnectors.get(i);
  762. if (connector instanceof ManagedLayout) {
  763. setNeedsLayout((ManagedLayout) connector);
  764. }
  765. }
  766. setEverythingNeedsMeasure();
  767. layoutNow();
  768. }
  769. /**
  770. * Marks that a ManagedLayout should be layouted in the next layout phase
  771. * even if none of the elements managed by the layout have been resized.
  772. * <p>
  773. * This method should not be invoked during a layout phase since it only
  774. * controls what will happen in the beginning of the next phase. If you want
  775. * to explicitly cause some layout to be considered in an ongoing layout
  776. * phase, you should use {@link #setNeedsMeasure(ComponentConnector)}
  777. * instead.
  778. *
  779. * @param layout
  780. * the managed layout that should be layouted
  781. */
  782. public final void setNeedsLayout(ManagedLayout layout) {
  783. setNeedsHorizontalLayout(layout);
  784. setNeedsVerticalLayout(layout);
  785. }
  786. /**
  787. * Marks that a ManagedLayout should be layouted horizontally in the next
  788. * layout phase even if none of the elements managed by the layout have been
  789. * resized horizontally.
  790. * <p>
  791. * For SimpleManagedLayout which is always layouted in both directions, this
  792. * has the same effect as {@link #setNeedsLayout(ManagedLayout)}.
  793. * <p>
  794. * This method should not be invoked during a layout phase since it only
  795. * controls what will happen in the beginning of the next phase. If you want
  796. * to explicitly cause some layout to be considered in an ongoing layout
  797. * phase, you should use {@link #setNeedsMeasure(ComponentConnector)}
  798. * instead.
  799. *
  800. * @param layout
  801. * the managed layout that should be layouted
  802. */
  803. public final void setNeedsHorizontalLayout(ManagedLayout layout) {
  804. if (isLayoutRunning()) {
  805. getLogger().warning(
  806. "setNeedsHorizontalLayout should not be run while a layout phase is in progress.");
  807. }
  808. needsHorizontalLayout.add(layout.getConnectorId());
  809. }
  810. /**
  811. * Marks that a ManagedLayout should be layouted vertically in the next
  812. * layout phase even if none of the elements managed by the layout have been
  813. * resized vertically.
  814. * <p>
  815. * For SimpleManagedLayout which is always layouted in both directions, this
  816. * has the same effect as {@link #setNeedsLayout(ManagedLayout)}.
  817. * <p>
  818. * This method should not be invoked during a layout phase since it only
  819. * controls what will happen in the beginning of the next phase. If you want
  820. * to explicitly cause some layout to be considered in an ongoing layout
  821. * phase, you should use {@link #setNeedsMeasure(ComponentConnector)}
  822. * instead.
  823. *
  824. * @param layout
  825. * the managed layout that should be layouted
  826. */
  827. public final void setNeedsVerticalLayout(ManagedLayout layout) {
  828. if (isLayoutRunning()) {
  829. getLogger().warning(
  830. "setNeedsVerticalLayout should not be run while a layout phase is in progress.");
  831. }
  832. needsVerticalLayout.add(layout.getConnectorId());
  833. }
  834. /**
  835. * Gets the outer height (including margins, paddings and borders) of the
  836. * given element, provided that it has been measured. These elements are
  837. * guaranteed to be measured:
  838. * <ul>
  839. * <li>ManagedLayouts and their child Connectors
  840. * <li>Elements for which there is at least one ElementResizeListener
  841. * <li>Elements for which at least one ManagedLayout has registered a
  842. * dependency
  843. * </ul>
  844. *
  845. * -1 is returned if the element has not been measured. If 0 is returned, it
  846. * might indicate that the element is not attached to the DOM.
  847. * <p>
  848. * The value returned by this method is always rounded up. To get the exact
  849. * outer width, use {@link #getOuterHeightDouble(Element)}
  850. *
  851. * @param element
  852. * the element to get the measured size for
  853. * @return the measured outer height (including margins, paddings and
  854. * borders) of the element in pixels.
  855. */
  856. public final int getOuterHeight(Element element) {
  857. assert needsMeasure(
  858. element) : "Getting measurement for element that is not measured";
  859. return (int) Math
  860. .ceil(getMeasuredSize(element, nullSize).getOuterHeight());
  861. }
  862. /**
  863. * Gets the outer height (including margins, paddings and borders) of the
  864. * given element, provided that it has been measured. These elements are
  865. * guaranteed to be measured:
  866. * <ul>
  867. * <li>ManagedLayouts and their child Connectors
  868. * <li>Elements for which there is at least one ElementResizeListener
  869. * <li>Elements for which at least one ManagedLayout has registered a
  870. * dependency
  871. * </ul>
  872. *
  873. * -1 is returned if the element has not been measured. If 0 is returned, it
  874. * might indicate that the element is not attached to the DOM.
  875. *
  876. * @since 7.5.1
  877. * @param element
  878. * the element to get the measured size for
  879. * @return the measured outer height (including margins, paddings and
  880. * borders) of the element in pixels.
  881. */
  882. public final double getOuterHeightDouble(Element element) {
  883. assert needsMeasure(
  884. element) : "Getting measurement for element that is not measured";
  885. return getMeasuredSize(element, nullSize).getOuterHeight();
  886. }
  887. /**
  888. * Gets the outer width (including margins, paddings and borders) of the
  889. * given element, provided that it has been measured. These elements are
  890. * guaranteed to be measured:
  891. * <ul>
  892. * <li>ManagedLayouts and their child Connectors
  893. * <li>Elements for which there is at least one ElementResizeListener
  894. * <li>Elements for which at least one ManagedLayout has registered a
  895. * dependency
  896. * </ul>
  897. *
  898. * -1 is returned if the element has not been measured. If 0 is returned, it
  899. * might indicate that the element is not attached to the DOM.
  900. * <p>
  901. * The value returned by this method is always rounded up. To get the exact
  902. * outer width, use {@link #getOuterWidthDouble(Element)}
  903. *
  904. * @since 7.5.1
  905. * @param element
  906. * the element to get the measured size for
  907. * @return the measured outer width (including margins, paddings and
  908. * borders) of the element in pixels.
  909. */
  910. public final int getOuterWidth(Element element) {
  911. assert needsMeasure(
  912. element) : "Getting measurement for element that is not measured";
  913. return (int) Math
  914. .ceil(getMeasuredSize(element, nullSize).getOuterWidth());
  915. }
  916. /**
  917. * Gets the outer width (including margins, paddings and borders) of the
  918. * given element, provided that it has been measured. These elements are
  919. * guaranteed to be measured:
  920. * <ul>
  921. * <li>ManagedLayouts and their child Connectors
  922. * <li>Elements for which there is at least one ElementResizeListener
  923. * <li>Elements for which at least one ManagedLayout has registered a
  924. * dependency
  925. * </ul>
  926. *
  927. * -1 is returned if the element has not been measured. If 0 is returned, it
  928. * might indicate that the element is not attached to the DOM.
  929. *
  930. * @param element
  931. * the element to get the measured size for
  932. * @return the measured outer width (including margins, paddings and
  933. * borders) of the element in pixels.
  934. */
  935. public final double getOuterWidthDouble(Element element) {
  936. assert needsMeasure(
  937. element) : "Getting measurement for element that is not measured";
  938. return getMeasuredSize(element, nullSize).getOuterWidth();
  939. }
  940. /**
  941. * Gets the inner height (excluding margins, paddings and borders) of the
  942. * given element, provided that it has been measured. These elements are
  943. * guaranteed to be measured:
  944. * <ul>
  945. * <li>ManagedLayouts and their child Connectors
  946. * <li>Elements for which there is at least one ElementResizeListener
  947. * <li>Elements for which at least one ManagedLayout has registered a
  948. * dependency
  949. * </ul>
  950. *
  951. * -1 is returned if the element has not been measured. If 0 is returned, it
  952. * might indicate that the element is not attached to the DOM.
  953. * <p>
  954. * The value returned by this method is always rounded up. To get the exact
  955. * outer width, use {@link #getInnerHeightDouble(Element)}
  956. *
  957. * @param element
  958. * the element to get the measured size for
  959. * @return the measured inner height (excluding margins, paddings and
  960. * borders) of the element in pixels.
  961. */
  962. public final int getInnerHeight(Element element) {
  963. assert needsMeasure(
  964. element) : "Getting measurement for element that is not measured";
  965. return (int) Math
  966. .ceil(getMeasuredSize(element, nullSize).getInnerHeight());
  967. }
  968. /**
  969. * Gets the inner height (excluding margins, paddings and borders) of the
  970. * given element, provided that it has been measured. These elements are
  971. * guaranteed to be measured:
  972. * <ul>
  973. * <li>ManagedLayouts and their child Connectors
  974. * <li>Elements for which there is at least one ElementResizeListener
  975. * <li>Elements for which at least one ManagedLayout has registered a
  976. * dependency
  977. * </ul>
  978. *
  979. * -1 is returned if the element has not been measured. If 0 is returned, it
  980. * might indicate that the element is not attached to the DOM.
  981. *
  982. * @since 7.5.1
  983. * @param element
  984. * the element to get the measured size for
  985. * @return the measured inner height (excluding margins, paddings and
  986. * borders) of the element in pixels.
  987. */
  988. public final double getInnerHeightDouble(Element element) {
  989. assert needsMeasure(
  990. element) : "Getting measurement for element that is not measured";
  991. return getMeasuredSize(element, nullSize).getInnerHeight();
  992. }
  993. /**
  994. * Gets the inner width (excluding margins, paddings and borders) of the
  995. * given element, provided that it has been measured. These elements are
  996. * guaranteed to be measured:
  997. * <ul>
  998. * <li>ManagedLayouts and their child Connectors
  999. * <li>Elements for which there is at least one ElementResizeListener
  1000. * <li>Elements for which at least one ManagedLayout has registered a
  1001. * dependency
  1002. * </ul>
  1003. *
  1004. * -1 is returned if the element has not been measured. If 0 is returned, it
  1005. * might indicate that the element is not attached to the DOM.
  1006. * <p>
  1007. * The value returned by this method is always rounded up. To get the exact
  1008. * outer width, use {@link #getOuterHeightDouble(Element)}
  1009. *
  1010. * @param element
  1011. * the element to get the measured size for
  1012. * @return the measured inner width (excluding margins, paddings and
  1013. * borders) of the element in pixels.
  1014. */
  1015. public final int getInnerWidth(Element element) {
  1016. assert needsMeasure(
  1017. element) : "Getting measurement for element that is not measured";
  1018. return (int) Math
  1019. .ceil(getMeasuredSize(element, nullSize).getInnerWidth());
  1020. }
  1021. /**
  1022. * Gets the inner width (excluding margins, paddings and borders) of the
  1023. * given element, provided that it has been measured. These elements are
  1024. * guaranteed to be measured:
  1025. * <ul>
  1026. * <li>ManagedLayouts and their child Connectors
  1027. * <li>Elements for which there is at least one ElementResizeListener
  1028. * <li>Elements for which at least one ManagedLayout has registered a
  1029. * dependency
  1030. * </ul>
  1031. *
  1032. * -1 is returned if the element has not been measured. If 0 is returned, it
  1033. * might indicate that the element is not attached to the DOM.
  1034. *
  1035. * @since 7.5.1
  1036. * @param element
  1037. * the element to get the measured size for
  1038. * @return the measured inner width (excluding margins, paddings and
  1039. * borders) of the element in pixels.
  1040. */
  1041. public final double getInnerWidthDouble(Element element) {
  1042. assert needsMeasure(
  1043. element) : "Getting measurement for element that is not measured";
  1044. return getMeasuredSize(element, nullSize).getInnerWidth();
  1045. }
  1046. /**
  1047. * Gets the border height (top border + bottom border) of the given element,
  1048. * provided that it has been measured. These elements are guaranteed to be
  1049. * measured:
  1050. * <ul>
  1051. * <li>ManagedLayouts and their child Connectors
  1052. * <li>Elements for which there is at least one ElementResizeListener
  1053. * <li>Elements for which at least one ManagedLayout has registered a
  1054. * dependency
  1055. * </ul>
  1056. *
  1057. * A negative number is returned if the element has not been measured. If 0
  1058. * is returned, it might indicate that the element is not attached to the
  1059. * DOM.
  1060. *
  1061. * @param element
  1062. * the element to get the measured size for
  1063. * @return the measured border height (top border + bottom border) of the
  1064. * element in pixels.
  1065. */
  1066. public final int getBorderHeight(Element element) {
  1067. assert needsMeasure(
  1068. element) : "Getting measurement for element that is not measured";
  1069. return getMeasuredSize(element, nullSize).getBorderHeight();
  1070. }
  1071. /**
  1072. * Gets the padding height (top padding + bottom padding) of the given
  1073. * element, provided that it has been measured. These elements are
  1074. * guaranteed to be measured:
  1075. * <ul>
  1076. * <li>ManagedLayouts and their child Connectors
  1077. * <li>Elements for which there is at least one ElementResizeListener
  1078. * <li>Elements for which at least one ManagedLayout has registered a
  1079. * dependency
  1080. * </ul>
  1081. *
  1082. * A negative number is returned if the element has not been measured. If 0
  1083. * is returned, it might indicate that the element is not attached to the
  1084. * DOM.
  1085. *
  1086. * @param element
  1087. * the element to get the measured size for
  1088. * @return the measured padding height (top padding + bottom padding) of the
  1089. * element in pixels.
  1090. */
  1091. public int getPaddingHeight(Element element) {
  1092. assert needsMeasure(
  1093. element) : "Getting measurement for element that is not measured";
  1094. return getMeasuredSize(element, nullSize).getPaddingHeight();
  1095. }
  1096. /**
  1097. * Gets the border width (left border + right border) of the given element,
  1098. * provided that it has been measured. These elements are guaranteed to be
  1099. * measured:
  1100. * <ul>
  1101. * <li>ManagedLayouts and their child Connectors
  1102. * <li>Elements for which there is at least one ElementResizeListener
  1103. * <li>Elements for which at least one ManagedLayout has registered a
  1104. * dependency
  1105. * </ul>
  1106. *
  1107. * A negative number is returned if the element has not been measured. If 0
  1108. * is returned, it might indicate that the element is not attached to the
  1109. * DOM.
  1110. *
  1111. * @param element
  1112. * the element to get the measured size for
  1113. * @return the measured border width (left border + right border) of the
  1114. * element in pixels.
  1115. */
  1116. public int getBorderWidth(Element element) {
  1117. assert needsMeasure(
  1118. element) : "Getting measurement for element that is not measured";
  1119. return getMeasuredSize(element, nullSize).getBorderWidth();
  1120. }
  1121. /**
  1122. * Gets the top border of the given element, provided that it has been
  1123. * measured. These elements are guaranteed to be measured:
  1124. * <ul>
  1125. * <li>ManagedLayouts and their child Connectors
  1126. * <li>Elements for which there is at least one ElementResizeListener
  1127. * <li>Elements for which at least one ManagedLayout has registered a
  1128. * dependency
  1129. * </ul>
  1130. *
  1131. * A negative number is returned if the element has not been measured. If 0
  1132. * is returned, it might indicate that the element is not attached to the
  1133. * DOM.
  1134. *
  1135. * @param element
  1136. * the element to get the measured size for
  1137. * @return the measured top border of the element in pixels.
  1138. */
  1139. public int getBorderTop(Element element) {
  1140. assert needsMeasure(
  1141. element) : "Getting measurement for element that is not measured";
  1142. return getMeasuredSize(element, nullSize).getBorderTop();
  1143. }
  1144. /**
  1145. * Gets the left border of the given element, provided that it has been
  1146. * measured. These elements are guaranteed to be measured:
  1147. * <ul>
  1148. * <li>ManagedLayouts and their child Connectors
  1149. * <li>Elements for which there is at least one ElementResizeListener
  1150. * <li>Elements for which at least one ManagedLayout has registered a
  1151. * dependency
  1152. * </ul>
  1153. *
  1154. * A negative number is returned if the element has not been measured. If 0
  1155. * is returned, it might indicate that the element is not attached to the
  1156. * DOM.
  1157. *
  1158. * @param element
  1159. * the element to get the measured size for
  1160. * @return the measured left border of the element in pixels.
  1161. */
  1162. public int getBorderLeft(Element element) {
  1163. assert needsMeasure(
  1164. element) : "Getting measurement for element that is not measured";
  1165. return getMeasuredSize(element, nullSize).getBorderLeft();
  1166. }
  1167. /**
  1168. * Gets the bottom border of the given element, provided that it has been
  1169. * measured. These elements are guaranteed to be measured:
  1170. * <ul>
  1171. * <li>ManagedLayouts and their child Connectors
  1172. * <li>Elements for which there is at least one ElementResizeListener
  1173. * <li>Elements for which at least one ManagedLayout has registered a
  1174. * dependency
  1175. * </ul>
  1176. *
  1177. * A negative number is returned if the element has not been measured. If 0
  1178. * is returned, it might indicate that the element is not attached to the
  1179. * DOM.
  1180. *
  1181. * @param element
  1182. * the element to get the measured size for
  1183. * @return the measured bottom border of the element in pixels.
  1184. */
  1185. public int getBorderBottom(Element element) {
  1186. assert needsMeasure(
  1187. element) : "Getting measurement for element that is not measured";
  1188. return getMeasuredSize(element, nullSize).getBorderBottom();
  1189. }
  1190. /**
  1191. * Gets the right border of the given element, provided that it has been
  1192. * measured. These elements are guaranteed to be measured:
  1193. * <ul>
  1194. * <li>ManagedLayouts and their child Connectors
  1195. * <li>Elements for which there is at least one ElementResizeListener
  1196. * <li>Elements for which at least one ManagedLayout has registered a
  1197. * dependency
  1198. * </ul>
  1199. *
  1200. * A negative number is returned if the element has not been measured. If 0
  1201. * is returned, it might indicate that the element is not attached to the
  1202. * DOM.
  1203. *
  1204. * @param element
  1205. * the element to get the measured size for
  1206. * @return the measured right border of the element in pixels.
  1207. */
  1208. public int getBorderRight(Element element) {
  1209. assert needsMeasure(
  1210. element) : "Getting measurement for element that is not measured";
  1211. return getMeasuredSize(element, nullSize).getBorderRight();
  1212. }
  1213. /**
  1214. * Gets the padding width (left padding + right padding) of the given
  1215. * element, provided that it has been measured. These elements are
  1216. * guaranteed to be measured:
  1217. * <ul>
  1218. * <li>ManagedLayouts and their child Connectors
  1219. * <li>Elements for which there is at least one ElementResizeListener
  1220. * <li>Elements for which at least one ManagedLayout has registered a
  1221. * dependency
  1222. * </ul>
  1223. *
  1224. * A negative number is returned if the element has not been measured. If 0
  1225. * is returned, it might indicate that the element is not attached to the
  1226. * DOM.
  1227. *
  1228. * @param element
  1229. * the element to get the measured size for
  1230. * @return the measured padding width (left padding + right padding) of the
  1231. * element in pixels.
  1232. */
  1233. public int getPaddingWidth(Element element) {
  1234. assert needsMeasure(
  1235. element) : "Getting measurement for element that is not measured";
  1236. return getMeasuredSize(element, nullSize).getPaddingWidth();
  1237. }
  1238. /**
  1239. * Gets the top padding of the given element, provided that it has been
  1240. * measured. These elements are guaranteed to be measured:
  1241. * <ul>
  1242. * <li>ManagedLayouts and their child Connectors
  1243. * <li>Elements for which there is at least one ElementResizeListener
  1244. * <li>Elements for which at least one ManagedLayout has registered a
  1245. * dependency
  1246. * </ul>
  1247. *
  1248. * A negative number is returned if the element has not been measured. If 0
  1249. * is returned, it might indicate that the element is not attached to the
  1250. * DOM.
  1251. *
  1252. * @param element
  1253. * the element to get the measured size for
  1254. * @return the measured top padding of the element in pixels.
  1255. */
  1256. public int getPaddingTop(Element element) {
  1257. assert needsMeasure(
  1258. element) : "Getting measurement for element that is not measured";
  1259. return getMeasuredSize(element, nullSize).getPaddingTop();
  1260. }
  1261. /**
  1262. * Gets the left padding of the given element, provided that it has been
  1263. * measured. These elements are guaranteed to be measured:
  1264. * <ul>
  1265. * <li>ManagedLayouts and their child Connectors
  1266. * <li>Elements for which there is at least one ElementResizeListener
  1267. * <li>Elements for which at least one ManagedLayout has registered a
  1268. * dependency
  1269. * </ul>
  1270. *
  1271. * A negative number is returned if the element has not been measured. If 0
  1272. * is returned, it might indicate that the element is not attached to the
  1273. * DOM.
  1274. *
  1275. * @param element
  1276. * the element to get the measured size for
  1277. * @return the measured left padding of the element in pixels.
  1278. */
  1279. public int getPaddingLeft(Element element) {
  1280. assert needsMeasure(
  1281. element) : "Getting measurement for element that is not measured";
  1282. return getMeasuredSize(element, nullSize).getPaddingLeft();
  1283. }
  1284. /**
  1285. * Gets the bottom padding of the given element, provided that it has been
  1286. * measured. These elements are guaranteed to be measured:
  1287. * <ul>
  1288. * <li>ManagedLayouts and their child Connectors
  1289. * <li>Elements for which there is at least one ElementResizeListener
  1290. * <li>Elements for which at least one ManagedLayout has registered a
  1291. * dependency
  1292. * </ul>
  1293. *
  1294. * A negative number is returned if the element has not been measured. If 0
  1295. * is returned, it might indicate that the element is not attached to the
  1296. * DOM.
  1297. *
  1298. * @param element
  1299. * the element to get the measured size for
  1300. * @return the measured bottom padding of the element in pixels.
  1301. */
  1302. public int getPaddingBottom(Element element) {
  1303. assert needsMeasure(
  1304. element) : "Getting measurement for element that is not measured";
  1305. return getMeasuredSize(element, nullSize).getPaddingBottom();
  1306. }
  1307. /**
  1308. * Gets the right padding of the given element, provided that it has been
  1309. * measured. These elements are guaranteed to be measured:
  1310. * <ul>
  1311. * <li>ManagedLayouts and their child Connectors
  1312. * <li>Elements for which there is at least one ElementResizeListener
  1313. * <li>Elements for which at least one ManagedLayout has registered a
  1314. * dependency
  1315. * </ul>
  1316. *
  1317. * A negative number is returned if the element has not been measured. If 0
  1318. * is returned, it might indicate that the element is not attached to the
  1319. * DOM.
  1320. *
  1321. * @param element
  1322. * the element to get the measured size for
  1323. * @return the measured right padding of the element in pixels.
  1324. */
  1325. public int getPaddingRight(Element element) {
  1326. assert needsMeasure(
  1327. element) : "Getting measurement for element that is not measured";
  1328. return getMeasuredSize(element, nullSize).getPaddingRight();
  1329. }
  1330. /**
  1331. * Gets the top margin of the given element, provided that it has been
  1332. * measured. These elements are guaranteed to be measured:
  1333. * <ul>
  1334. * <li>ManagedLayouts and their child Connectors
  1335. * <li>Elements for which there is at least one ElementResizeListener
  1336. * <li>Elements for which at least one ManagedLayout has registered a
  1337. * dependency
  1338. * </ul>
  1339. *
  1340. * A negative number is returned if the element has not been measured. If 0
  1341. * is returned, it might indicate that the element is not attached to the
  1342. * DOM.
  1343. *
  1344. * @param element
  1345. * the element to get the measured size for
  1346. * @return the measured top margin of the element in pixels.
  1347. */
  1348. public int getMarginTop(Element element) {
  1349. assert needsMeasure(
  1350. element) : "Getting measurement for element that is not measured";
  1351. return getMeasuredSize(element, nullSize).getMarginTop();
  1352. }
  1353. /**
  1354. * Gets the right margin of the given element, provided that it has been
  1355. * measured. These elements are guaranteed to be measured:
  1356. * <ul>
  1357. * <li>ManagedLayouts and their child Connectors
  1358. * <li>Elements for which there is at least one ElementResizeListener
  1359. * <li>Elements for which at least one ManagedLayout has registered a
  1360. * dependency
  1361. * </ul>
  1362. *
  1363. * A negative number is returned if the element has not been measured. If 0
  1364. * is returned, it might indicate that the element is not attached to the
  1365. * DOM.
  1366. *
  1367. * @param element
  1368. * the element to get the measured size for
  1369. * @return the measured right margin of the element in pixels.
  1370. */
  1371. public int getMarginRight(Element element) {
  1372. assert needsMeasure(
  1373. element) : "Getting measurement for element that is not measured";
  1374. return getMeasuredSize(element, nullSize).getMarginRight();
  1375. }
  1376. /**
  1377. * Gets the bottom margin of the given element, provided that it has been
  1378. * measured. These elements are guaranteed to be measured:
  1379. * <ul>
  1380. * <li>ManagedLayouts and their child Connectors
  1381. * <li>Elements for which there is at least one ElementResizeListener
  1382. * <li>Elements for which at least one ManagedLayout has registered a
  1383. * dependency
  1384. * </ul>
  1385. *
  1386. * A negative number is returned if the element has not been measured. If 0
  1387. * is returned, it might indicate that the element is not attached to the
  1388. * DOM.
  1389. *
  1390. * @param element
  1391. * the element to get the measured size for
  1392. * @return the measured bottom margin of the element in pixels.
  1393. */
  1394. public int getMarginBottom(Element element) {
  1395. assert needsMeasure(
  1396. element) : "Getting measurement for element that is not measured";
  1397. return getMeasuredSize(element, nullSize).getMarginBottom();
  1398. }
  1399. /**
  1400. * Gets the left margin of the given element, provided that it has been
  1401. * measured. These elements are guaranteed to be measured:
  1402. * <ul>
  1403. * <li>ManagedLayouts and their child Connectors
  1404. * <li>Elements for which there is at least one ElementResizeListener
  1405. * <li>Elements for which at least one ManagedLayout has registered a
  1406. * dependency
  1407. * </ul>
  1408. *
  1409. * A negative number is returned if the element has not been measured. If 0
  1410. * is returned, it might indicate that the element is not attached to the
  1411. * DOM.
  1412. *
  1413. * @param element
  1414. * the element to get the measured size for
  1415. * @return the measured left margin of the element in pixels.
  1416. */
  1417. public int getMarginLeft(Element element) {
  1418. assert needsMeasure(
  1419. element) : "Getting measurement for element that is not measured";
  1420. return getMeasuredSize(element, nullSize).getMarginLeft();
  1421. }
  1422. /**
  1423. * Gets the combined top & bottom margin of the given element, provided that
  1424. * they have been measured. These elements are guaranteed to be measured:
  1425. * <ul>
  1426. * <li>ManagedLayouts and their child Connectors
  1427. * <li>Elements for which there is at least one ElementResizeListener
  1428. * <li>Elements for which at least one ManagedLayout has registered a
  1429. * dependency
  1430. * </ul>
  1431. *
  1432. * A negative number is returned if the element has not been measured. If 0
  1433. * is returned, it might indicate that the element is not attached to the
  1434. * DOM.
  1435. *
  1436. * @param element
  1437. * the element to get the measured margin for
  1438. * @return the measured top+bottom margin of the element in pixels.
  1439. */
  1440. public int getMarginHeight(Element element) {
  1441. return getMarginTop(element) + getMarginBottom(element);
  1442. }
  1443. /**
  1444. * Gets the combined left & right margin of the given element, provided that
  1445. * they have been measured. These elements are guaranteed to be measured:
  1446. * <ul>
  1447. * <li>ManagedLayouts and their child Connectors
  1448. * <li>Elements for which there is at least one ElementResizeListener
  1449. * <li>Elements for which at least one ManagedLayout has registered a
  1450. * dependency
  1451. * </ul>
  1452. *
  1453. * A negative number is returned if the element has not been measured. If 0
  1454. * is returned, it might indicate that the element is not attached to the
  1455. * DOM.
  1456. *
  1457. * @param element
  1458. * the element to get the measured margin for
  1459. * @return the measured left+right margin of the element in pixels.
  1460. */
  1461. public int getMarginWidth(Element element) {
  1462. return getMarginLeft(element) + getMarginRight(element);
  1463. }
  1464. /**
  1465. * Registers the outer height (including margins, borders and paddings) of a
  1466. * component. This can be used as an optimization by ManagedLayouts; by
  1467. * informing the LayoutManager about what size a component will have, the
  1468. * layout propagation can continue directly without first measuring the
  1469. * potentially resized elements.
  1470. *
  1471. * @param component
  1472. * the component for which the size is reported
  1473. * @param outerHeight
  1474. * the new outer height (including margins, borders and paddings)
  1475. * of the component in pixels
  1476. */
  1477. public void reportOuterHeight(ComponentConnector component,
  1478. int outerHeight) {
  1479. Element element = component.getWidget().getElement();
  1480. MeasuredSize measuredSize = getMeasuredSize(element);
  1481. if (isLayoutRunning()) {
  1482. boolean heightChanged = measuredSize.setOuterHeight(outerHeight);
  1483. if (heightChanged) {
  1484. onConnectorChange(component, false, true);
  1485. notifyListenersAndDepdendents(element, false, true);
  1486. }
  1487. currentDependencyTree.setNeedsVerticalMeasure(component, false);
  1488. } else if (measuredSize.getOuterHeight() != outerHeight) {
  1489. setNeedsMeasure(component);
  1490. }
  1491. }
  1492. /**
  1493. * Registers the height reserved for a relatively sized component. This can
  1494. * be used as an optimization by ManagedLayouts; by informing the
  1495. * LayoutManager about what size a component will have, the layout
  1496. * propagation can continue directly without first measuring the potentially
  1497. * resized elements.
  1498. *
  1499. * @param component
  1500. * the relatively sized component for which the size is reported
  1501. * @param assignedHeight
  1502. * the inner height of the relatively sized component's parent
  1503. * element in pixels
  1504. */
  1505. public void reportHeightAssignedToRelative(ComponentConnector component,
  1506. int assignedHeight) {
  1507. assert component.isRelativeHeight();
  1508. float percentSize = parsePercent(component.getState().height == null
  1509. ? "" : component.getState().height);
  1510. int effectiveHeight = Math.round(assignedHeight * (percentSize / 100));
  1511. reportOuterHeight(component, effectiveHeight);
  1512. }
  1513. /**
  1514. * Registers the width reserved for a relatively sized component. This can
  1515. * be used as an optimization by ManagedLayouts; by informing the
  1516. * LayoutManager about what size a component will have, the layout
  1517. * propagation can continue directly without first measuring the potentially
  1518. * resized elements.
  1519. *
  1520. * @param component
  1521. * the relatively sized component for which the size is reported
  1522. * @param assignedWidth
  1523. * the inner width of the relatively sized component's parent
  1524. * element in pixels
  1525. */
  1526. public void reportWidthAssignedToRelative(ComponentConnector component,
  1527. int assignedWidth) {
  1528. assert component.isRelativeWidth();
  1529. float percentSize = parsePercent(component.getState().width == null ? ""
  1530. : component.getState().width);
  1531. int effectiveWidth = Math.round(assignedWidth * (percentSize / 100));
  1532. reportOuterWidth(component, effectiveWidth);
  1533. }
  1534. private static float parsePercent(String size) {
  1535. return Float.parseFloat(size.substring(0, size.length() - 1));
  1536. }
  1537. /**
  1538. * Registers the outer width (including margins, borders and paddings) of a
  1539. * component. This can be used as an optimization by ManagedLayouts; by
  1540. * informing the LayoutManager about what size a component will have, the
  1541. * layout propagation can continue directly without first measuring the
  1542. * potentially resized elements.
  1543. *
  1544. * @param component
  1545. * the component for which the size is reported
  1546. * @param outerWidth
  1547. * the new outer width (including margins, borders and paddings)
  1548. * of the component in pixels
  1549. */
  1550. public void reportOuterWidth(ComponentConnector component, int outerWidth) {
  1551. Element element = component.getWidget().getElement();
  1552. MeasuredSize measuredSize = getMeasuredSize(element);
  1553. if (isLayoutRunning()) {
  1554. boolean widthChanged = measuredSize.setOuterWidth(outerWidth);
  1555. if (widthChanged) {
  1556. onConnectorChange(component, true, false);
  1557. notifyListenersAndDepdendents(element, true, false);
  1558. }
  1559. currentDependencyTree.setNeedsHorizontalMeasure(component, false);
  1560. } else if (measuredSize.getOuterWidth() != outerWidth) {
  1561. setNeedsMeasure(component);
  1562. }
  1563. }
  1564. /**
  1565. * Adds a listener that will be notified whenever the size of a specific
  1566. * element changes. Adding a listener to an element also ensures that all
  1567. * sizes for that element will be available starting from the next layout
  1568. * phase.
  1569. *
  1570. * @param element
  1571. * the element that should be checked for size changes
  1572. * @param listener
  1573. * an ElementResizeListener that will be informed whenever the
  1574. * size of the target element has changed
  1575. */
  1576. public void addElementResizeListener(Element element,
  1577. ElementResizeListener listener) {
  1578. Collection<ElementResizeListener> listeners = elementResizeListeners
  1579. .get(element);
  1580. if (listeners == null) {
  1581. listeners = new HashSet<ElementResizeListener>();
  1582. elementResizeListeners.put(element, listeners);
  1583. ensureMeasured(element);
  1584. }
  1585. listeners.add(listener);
  1586. }
  1587. /**
  1588. * Removes an element resize listener from the provided element. This might
  1589. * cause this LayoutManager to stop tracking the size of the element if no
  1590. * other sources are interested in the size.
  1591. *
  1592. * @param element
  1593. * the element to which the element resize listener was
  1594. * previously added
  1595. * @param listener
  1596. * the ElementResizeListener that should no longer get informed
  1597. * about size changes to the target element.
  1598. */
  1599. public void removeElementResizeListener(Element element,
  1600. ElementResizeListener listener) {
  1601. Collection<ElementResizeListener> listeners = elementResizeListeners
  1602. .get(element);
  1603. if (listeners != null) {
  1604. listeners.remove(listener);
  1605. if (listeners.isEmpty()) {
  1606. elementResizeListeners.remove(element);
  1607. stopMeasuringIfUnecessary(element);
  1608. }
  1609. }
  1610. }
  1611. private void stopMeasuringIfUnecessary(Element element) {
  1612. if (!needsMeasure(element)) {
  1613. measuredNonConnectorElements.remove(element);
  1614. setMeasuredSize(element, null);
  1615. }
  1616. }
  1617. /**
  1618. * Informs this LayoutManager that the size of a component might have
  1619. * changed. This method should be used whenever the size of an individual
  1620. * component might have changed from outside of Vaadin's normal update
  1621. * phase, e.g. when an icon has been loaded or when the user resizes some
  1622. * part of the UI using the mouse.
  1623. * <p>
  1624. * To set an entire component hierarchy to be measured, use
  1625. * {@link #setNeedsMeasureRecursively(ComponentConnector)} instead.
  1626. * <p>
  1627. * If there is no upcoming layout phase, a new layout phase is scheduled.
  1628. *
  1629. * @param component
  1630. * the component whose size might have changed.
  1631. */
  1632. public void setNeedsMeasure(ComponentConnector component) {
  1633. if (isLayoutRunning()) {
  1634. currentDependencyTree.setNeedsMeasure(component, true);
  1635. } else {
  1636. needsMeasure.add(component.getConnectorId());
  1637. layoutLater();
  1638. }
  1639. }
  1640. /**
  1641. * Informs this LayoutManager that some sizes in a component hierarchy might
  1642. * have changed. This method should be used whenever the size of any child
  1643. * component might have changed from outside of Vaadin's normal update
  1644. * phase, e.g. when a CSS class name related to sizing has been changed.
  1645. * <p>
  1646. * To set a single component to be measured, use
  1647. * {@link #setNeedsMeasure(ComponentConnector)} instead.
  1648. * <p>
  1649. * If there is no upcoming layout phase, a new layout phase is scheduled.
  1650. *
  1651. * @since 7.2
  1652. * @param component
  1653. * the component at the root of the component hierarchy to
  1654. * measure
  1655. */
  1656. public void setNeedsMeasureRecursively(ComponentConnector component) {
  1657. setNeedsMeasure(component);
  1658. if (component instanceof HasComponentsConnector) {
  1659. HasComponentsConnector hasComponents = (HasComponentsConnector) component;
  1660. for (ComponentConnector child : hasComponents
  1661. .getChildComponents()) {
  1662. setNeedsMeasureRecursively(child);
  1663. }
  1664. }
  1665. }
  1666. public void setEverythingNeedsMeasure() {
  1667. everythingNeedsMeasure = true;
  1668. }
  1669. /**
  1670. * Clean measured sizes which are no longer needed. Only for IE8.
  1671. */
  1672. public void cleanMeasuredSizes() {
  1673. }
  1674. private static Logger getLogger() {
  1675. return Logger.getLogger(LayoutManager.class.getName());
  1676. }
  1677. /**
  1678. * Checks if there is something waiting for a layout to take place.
  1679. *
  1680. * @since 7.5.6
  1681. * @return true if there are connectors waiting for measurement or layout,
  1682. * false otherwise
  1683. */
  1684. public boolean isLayoutNeeded() {
  1685. if (!needsHorizontalLayout.isEmpty()
  1686. || !needsVerticalLayout.isEmpty()) {
  1687. return true;
  1688. }
  1689. if (!needsMeasure.isEmpty()) {
  1690. return true;
  1691. }
  1692. if (everythingNeedsMeasure) {
  1693. return true;
  1694. }
  1695. return false;
  1696. }
  1697. }