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

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