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

12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849
  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 for
  145. * legacy reasons.
  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 for
  164. * legacy reasons.
  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 exists for legacy
  252. * reasons.
  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. getLogger().info("Total layout phase time: "
  531. + totalDuration.elapsedMillis() + "ms");
  532. }
  533. private void logConnectorStatus(int connectorId) {
  534. currentDependencyTree.logDependencyStatus(
  535. (ComponentConnector) ConnectorMap.get(connection)
  536. .getConnector(Integer.toString(connectorId)));
  537. }
  538. private int measureConnectors(LayoutDependencyTree layoutDependencyTree,
  539. boolean measureAll) {
  540. Profiler.enter("Layout overflow fix handling");
  541. JsArrayString pendingOverflowConnectorsIds = pendingOverflowFixes
  542. .dump();
  543. int pendingOverflowCount = pendingOverflowConnectorsIds.length();
  544. ConnectorMap connectorMap = ConnectorMap.get(connection);
  545. if (pendingOverflowCount > 0) {
  546. HashMap<Element, String> originalOverflows = new HashMap<Element, String>();
  547. FastStringSet delayedOverflowFixes = FastStringSet.create();
  548. // First set overflow to hidden (and save previous value so it can
  549. // be restored later)
  550. for (int i = 0; i < pendingOverflowCount; i++) {
  551. String connectorId = pendingOverflowConnectorsIds.get(i);
  552. ComponentConnector componentConnector = (ComponentConnector) connectorMap
  553. .getConnector(connectorId);
  554. if (delayOverflowFix(componentConnector)) {
  555. delayedOverflowFixes.add(connectorId);
  556. continue;
  557. }
  558. if (debugLogging) {
  559. getLogger().info("Doing overflow fix for "
  560. + Util.getConnectorString(componentConnector)
  561. + " in " + Util.getConnectorString(
  562. componentConnector.getParent()));
  563. }
  564. Profiler.enter("Overflow fix apply");
  565. Element parentElement = componentConnector.getWidget()
  566. .getElement().getParentElement();
  567. Style style = parentElement.getStyle();
  568. String originalOverflow = style.getOverflow();
  569. if (originalOverflow != null
  570. && !originalOverflows.containsKey(parentElement)) {
  571. // Store original value for restore, but only the first time
  572. // the value is changed
  573. originalOverflows.put(parentElement, originalOverflow);
  574. }
  575. style.setOverflow(Overflow.HIDDEN);
  576. Profiler.leave("Overflow fix apply");
  577. }
  578. pendingOverflowFixes.removeAll(delayedOverflowFixes);
  579. JsArrayString remainingOverflowFixIds = pendingOverflowFixes.dump();
  580. int remainingCount = remainingOverflowFixIds.length();
  581. Profiler.enter("Overflow fix reflow");
  582. // Then ensure all scrolling elements are reflowed by measuring
  583. for (int i = 0; i < remainingCount; i++) {
  584. ComponentConnector componentConnector = (ComponentConnector) connectorMap
  585. .getConnector(remainingOverflowFixIds.get(i));
  586. componentConnector.getWidget().getElement().getParentElement()
  587. .getOffsetHeight();
  588. }
  589. Profiler.leave("Overflow fix reflow");
  590. Profiler.enter("Overflow fix restore");
  591. // Finally restore old overflow value and update bookkeeping
  592. for (int i = 0; i < remainingCount; i++) {
  593. String connectorId = remainingOverflowFixIds.get(i);
  594. ComponentConnector componentConnector = (ComponentConnector) connectorMap
  595. .getConnector(connectorId);
  596. Element parentElement = componentConnector.getWidget()
  597. .getElement().getParentElement();
  598. parentElement.getStyle().setProperty("overflow",
  599. originalOverflows.get(parentElement));
  600. layoutDependencyTree.setNeedsMeasure(componentConnector, true);
  601. }
  602. Profiler.leave("Overflow fix restore");
  603. if (!pendingOverflowFixes.isEmpty()) {
  604. getLogger().info(
  605. "Did overflow fix for " + remainingCount + " elements");
  606. }
  607. pendingOverflowFixes = delayedOverflowFixes;
  608. }
  609. Profiler.leave("Layout overflow fix handling");
  610. int measureCount = 0;
  611. if (measureAll) {
  612. Profiler.enter("Layout measureAll");
  613. JsArrayObject<ComponentConnector> allConnectors = connectorMap
  614. .getComponentConnectorsAsJsArray();
  615. int size = allConnectors.size();
  616. // Find connectors that should actually be measured
  617. JsArrayObject<ComponentConnector> connectors = JsArrayObject
  618. .createArray().cast();
  619. for (int i = 0; i < size; i++) {
  620. ComponentConnector candidate = allConnectors.get(i);
  621. if (!Util.shouldSkipMeasurementOfConnector(candidate)
  622. && needsMeasure(candidate.getWidget().getElement())) {
  623. connectors.add(candidate);
  624. }
  625. }
  626. int connectorCount = connectors.size();
  627. for (int i = 0; i < connectorCount; i++) {
  628. measureConnector(connectors.get(i));
  629. }
  630. for (int i = 0; i < connectorCount; i++) {
  631. layoutDependencyTree.setNeedsMeasure(connectors.get(i), false);
  632. }
  633. measureCount += connectorCount;
  634. Profiler.leave("Layout measureAll");
  635. }
  636. Profiler.enter("Layout measure from tree");
  637. while (layoutDependencyTree.hasConnectorsToMeasure()) {
  638. JsArrayString measureTargets = layoutDependencyTree
  639. .getMeasureTargetsJsArray();
  640. int length = measureTargets.length();
  641. for (int i = 0; i < length; i++) {
  642. ComponentConnector connector = (ComponentConnector) connectorMap
  643. .getConnector(measureTargets.get(i));
  644. measureConnector(connector);
  645. measureCount++;
  646. }
  647. for (int i = 0; i < length; i++) {
  648. ComponentConnector connector = (ComponentConnector) connectorMap
  649. .getConnector(measureTargets.get(i));
  650. layoutDependencyTree.setNeedsMeasure(connector, false);
  651. }
  652. }
  653. Profiler.leave("Layout measure from tree");
  654. return measureCount;
  655. }
  656. /*
  657. * Delay the overflow fix if the involved connectors might still change
  658. */
  659. private boolean delayOverflowFix(ComponentConnector componentConnector) {
  660. if (!currentDependencyTree.noMoreChangesExpected(componentConnector)) {
  661. return true;
  662. }
  663. ServerConnector parent = componentConnector.getParent();
  664. if (parent instanceof ComponentConnector && !currentDependencyTree
  665. .noMoreChangesExpected((ComponentConnector) parent)) {
  666. return true;
  667. }
  668. return false;
  669. }
  670. private void measureConnector(ComponentConnector connector) {
  671. Profiler.enter("LayoutManager.measureConnector");
  672. Element element = connector.getWidget().getElement();
  673. MeasuredSize measuredSize = getMeasuredSize(element);
  674. MeasureResult measureResult = measuredAndUpdate(element, measuredSize);
  675. if (measureResult.isChanged()) {
  676. onConnectorChange(connector, measureResult.isWidthChanged(),
  677. measureResult.isHeightChanged());
  678. }
  679. Profiler.leave("LayoutManager.measureConnector");
  680. }
  681. private void onConnectorChange(ComponentConnector connector,
  682. boolean widthChanged, boolean heightChanged) {
  683. Profiler.enter("LayoutManager.onConnectorChange");
  684. Profiler.enter("LayoutManager.onConnectorChange setNeedsOverflowFix");
  685. setNeedsOverflowFix(connector);
  686. Profiler.leave("LayoutManager.onConnectorChange setNeedsOverflowFix");
  687. Profiler.enter("LayoutManager.onConnectorChange heightChanged");
  688. if (heightChanged) {
  689. currentDependencyTree.markHeightAsChanged(connector);
  690. }
  691. Profiler.leave("LayoutManager.onConnectorChange heightChanged");
  692. Profiler.enter("LayoutManager.onConnectorChange widthChanged");
  693. if (widthChanged) {
  694. currentDependencyTree.markWidthAsChanged(connector);
  695. }
  696. Profiler.leave("LayoutManager.onConnectorChange widthChanged");
  697. Profiler.leave("LayoutManager.onConnectorChange");
  698. }
  699. private void setNeedsOverflowFix(ComponentConnector connector) {
  700. // IE9 doesn't need the original fix, but for some reason it needs this
  701. if (BrowserInfo.get().requiresOverflowAutoFix()) {
  702. ComponentConnector scrollingBoundary = currentDependencyTree
  703. .getScrollingBoundary(connector);
  704. if (scrollingBoundary != null) {
  705. pendingOverflowFixes.add(scrollingBoundary.getConnectorId());
  706. }
  707. }
  708. }
  709. private void measureNonConnectors() {
  710. Profiler.enter("LayoutManager.measureNonConenctors");
  711. for (Element element : measuredNonConnectorElements) {
  712. measuredAndUpdate(element, getMeasuredSize(element, null));
  713. }
  714. Profiler.leave("LayoutManager.measureNonConenctors");
  715. getLogger().info("Measured " + measuredNonConnectorElements.size()
  716. + " non connector elements");
  717. }
  718. private MeasureResult measuredAndUpdate(Element element,
  719. MeasuredSize measuredSize) {
  720. MeasureResult measureResult = measuredSize.measure(element);
  721. if (measureResult.isChanged()) {
  722. notifyListenersAndDepdendents(element,
  723. measureResult.isWidthChanged(),
  724. measureResult.isHeightChanged());
  725. }
  726. return measureResult;
  727. }
  728. private void notifyListenersAndDepdendents(Element element,
  729. boolean widthChanged, boolean heightChanged) {
  730. assert widthChanged || heightChanged;
  731. Profiler.enter("LayoutManager.notifyListenersAndDepdendents");
  732. MeasuredSize measuredSize = getMeasuredSize(element, nullSize);
  733. JsArrayString dependents = measuredSize.getDependents();
  734. for (int i = 0; i < dependents.length(); i++) {
  735. String pid = dependents.get(i);
  736. if (pid != null) {
  737. if (heightChanged) {
  738. currentDependencyTree.setNeedsVerticalLayout(pid, true);
  739. }
  740. if (widthChanged) {
  741. currentDependencyTree.setNeedsHorizontalLayout(pid, true);
  742. }
  743. }
  744. }
  745. if (elementResizeListeners.containsKey(element)) {
  746. listenersToFire.add(element);
  747. }
  748. Profiler.leave("LayoutManager.notifyListenersAndDepdendents");
  749. }
  750. private static boolean isManagedLayout(ComponentConnector connector) {
  751. return connector instanceof ManagedLayout;
  752. }
  753. public void forceLayout() {
  754. ConnectorMap connectorMap = connection.getConnectorMap();
  755. JsArrayObject<ComponentConnector> componentConnectors = connectorMap
  756. .getComponentConnectorsAsJsArray();
  757. int size = componentConnectors.size();
  758. for (int i = 0; i < size; i++) {
  759. ComponentConnector connector = componentConnectors.get(i);
  760. if (connector instanceof ManagedLayout) {
  761. setNeedsLayout((ManagedLayout) connector);
  762. }
  763. }
  764. setEverythingNeedsMeasure();
  765. layoutNow();
  766. }
  767. /**
  768. * Marks that a ManagedLayout should be layouted in the next layout phase
  769. * even if none of the elements managed by the layout have been resized.
  770. * <p>
  771. * This method should not be invoked during a layout phase since it only
  772. * controls what will happen in the beginning of the next phase. If you want
  773. * to explicitly cause some layout to be considered in an ongoing layout
  774. * phase, you should use {@link #setNeedsMeasure(ComponentConnector)}
  775. * instead.
  776. *
  777. * @param layout
  778. * the managed layout that should be layouted
  779. */
  780. public final void setNeedsLayout(ManagedLayout layout) {
  781. setNeedsHorizontalLayout(layout);
  782. setNeedsVerticalLayout(layout);
  783. }
  784. /**
  785. * Marks that a ManagedLayout should be layouted horizontally in the next
  786. * layout phase even if none of the elements managed by the layout have been
  787. * resized horizontally.
  788. * <p>
  789. * For SimpleManagedLayout which is always layouted in both directions, this
  790. * has the same effect as {@link #setNeedsLayout(ManagedLayout)}.
  791. * <p>
  792. * This method should not be invoked during a layout phase since it only
  793. * controls what will happen in the beginning of the next phase. If you want
  794. * to explicitly cause some layout to be considered in an ongoing layout
  795. * phase, you should use {@link #setNeedsMeasure(ComponentConnector)}
  796. * instead.
  797. *
  798. * @param layout
  799. * the managed layout that should be layouted
  800. */
  801. public final void setNeedsHorizontalLayout(ManagedLayout layout) {
  802. if (isLayoutRunning()) {
  803. getLogger().warning(
  804. "setNeedsHorizontalLayout should not be run while a layout phase is in progress.");
  805. }
  806. needsHorizontalLayout.add(layout.getConnectorId());
  807. }
  808. /**
  809. * Marks that a ManagedLayout should be layouted vertically in the next
  810. * layout phase even if none of the elements managed by the layout have been
  811. * resized vertically.
  812. * <p>
  813. * For SimpleManagedLayout which is always layouted in both directions, this
  814. * has the same effect as {@link #setNeedsLayout(ManagedLayout)}.
  815. * <p>
  816. * This method should not be invoked during a layout phase since it only
  817. * controls what will happen in the beginning of the next phase. If you want
  818. * to explicitly cause some layout to be considered in an ongoing layout
  819. * phase, you should use {@link #setNeedsMeasure(ComponentConnector)}
  820. * instead.
  821. *
  822. * @param layout
  823. * the managed layout that should be layouted
  824. */
  825. public final void setNeedsVerticalLayout(ManagedLayout layout) {
  826. if (isLayoutRunning()) {
  827. getLogger().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(
  856. element) : "Getting measurement for element that is not measured";
  857. return (int) Math
  858. .ceil(getMeasuredSize(element, nullSize).getOuterHeight());
  859. }
  860. /**
  861. * Gets the outer height (including margins, paddings and borders) of the
  862. * given element, provided that it has been measured. These elements are
  863. * guaranteed to be measured:
  864. * <ul>
  865. * <li>ManagedLayouts and their child Connectors
  866. * <li>Elements for which there is at least one ElementResizeListener
  867. * <li>Elements for which at least one ManagedLayout has registered a
  868. * dependency
  869. * </ul>
  870. *
  871. * -1 is returned if the element has not been measured. If 0 is returned, it
  872. * might indicate that the element is not attached to the DOM.
  873. *
  874. * @since 7.5.1
  875. * @param element
  876. * the element to get the measured size for
  877. * @return the measured outer height (including margins, paddings and
  878. * borders) of the element in pixels.
  879. */
  880. public final double getOuterHeightDouble(Element element) {
  881. assert needsMeasure(
  882. element) : "Getting measurement for element that is not measured";
  883. return getMeasuredSize(element, nullSize).getOuterHeight();
  884. }
  885. /**
  886. * Gets the outer width (including margins, paddings and borders) of the
  887. * given element, provided that it has been measured. These elements are
  888. * guaranteed to be measured:
  889. * <ul>
  890. * <li>ManagedLayouts and their child Connectors
  891. * <li>Elements for which there is at least one ElementResizeListener
  892. * <li>Elements for which at least one ManagedLayout has registered a
  893. * dependency
  894. * </ul>
  895. *
  896. * -1 is returned if the element has not been measured. If 0 is returned, it
  897. * might indicate that the element is not attached to the DOM.
  898. * <p>
  899. * The value returned by this method is always rounded up. To get the exact
  900. * outer width, use {@link #getOuterWidthDouble(Element)}
  901. *
  902. * @since 7.5.1
  903. * @param element
  904. * the element to get the measured size for
  905. * @return the measured outer width (including margins, paddings and
  906. * borders) of the element in pixels.
  907. */
  908. public final int getOuterWidth(Element element) {
  909. assert needsMeasure(
  910. element) : "Getting measurement for element that is not measured";
  911. return (int) Math
  912. .ceil(getMeasuredSize(element, nullSize).getOuterWidth());
  913. }
  914. /**
  915. * Gets the outer width (including margins, paddings and borders) of the
  916. * given element, provided that it has been measured. These elements are
  917. * guaranteed to be measured:
  918. * <ul>
  919. * <li>ManagedLayouts and their child Connectors
  920. * <li>Elements for which there is at least one ElementResizeListener
  921. * <li>Elements for which at least one ManagedLayout has registered a
  922. * dependency
  923. * </ul>
  924. *
  925. * -1 is returned if the element has not been measured. If 0 is returned, it
  926. * might indicate that the element is not attached to the DOM.
  927. *
  928. * @param element
  929. * the element to get the measured size for
  930. * @return the measured outer width (including margins, paddings and
  931. * borders) of the element in pixels.
  932. */
  933. public final double getOuterWidthDouble(Element element) {
  934. assert needsMeasure(
  935. element) : "Getting measurement for element that is not measured";
  936. return getMeasuredSize(element, nullSize).getOuterWidth();
  937. }
  938. /**
  939. * Gets the inner height (excluding margins, paddings and borders) of the
  940. * given element, provided that it has been measured. These elements are
  941. * guaranteed to be measured:
  942. * <ul>
  943. * <li>ManagedLayouts and their child Connectors
  944. * <li>Elements for which there is at least one ElementResizeListener
  945. * <li>Elements for which at least one ManagedLayout has registered a
  946. * dependency
  947. * </ul>
  948. *
  949. * -1 is returned if the element has not been measured. If 0 is returned, it
  950. * might indicate that the element is not attached to the DOM.
  951. * <p>
  952. * The value returned by this method is always rounded up. To get the exact
  953. * outer width, use {@link #getInnerHeightDouble(Element)}
  954. *
  955. * @param element
  956. * the element to get the measured size for
  957. * @return the measured inner height (excluding margins, paddings and
  958. * borders) of the element in pixels.
  959. */
  960. public final int getInnerHeight(Element element) {
  961. assert needsMeasure(
  962. element) : "Getting measurement for element that is not measured";
  963. return (int) Math
  964. .ceil(getMeasuredSize(element, nullSize).getInnerHeight());
  965. }
  966. /**
  967. * Gets the inner height (excluding margins, paddings and borders) of the
  968. * given element, provided that it has been measured. These elements are
  969. * guaranteed to be measured:
  970. * <ul>
  971. * <li>ManagedLayouts and their child Connectors
  972. * <li>Elements for which there is at least one ElementResizeListener
  973. * <li>Elements for which at least one ManagedLayout has registered a
  974. * dependency
  975. * </ul>
  976. *
  977. * -1 is returned if the element has not been measured. If 0 is returned, it
  978. * might indicate that the element is not attached to the DOM.
  979. *
  980. * @since 7.5.1
  981. * @param element
  982. * the element to get the measured size for
  983. * @return the measured inner height (excluding margins, paddings and
  984. * borders) of the element in pixels.
  985. */
  986. public final double getInnerHeightDouble(Element element) {
  987. assert needsMeasure(
  988. element) : "Getting measurement for element that is not measured";
  989. return getMeasuredSize(element, nullSize).getInnerHeight();
  990. }
  991. /**
  992. * Gets the inner width (excluding margins, paddings and borders) of the
  993. * given element, provided that it has been measured. These elements are
  994. * guaranteed to be measured:
  995. * <ul>
  996. * <li>ManagedLayouts and their child Connectors
  997. * <li>Elements for which there is at least one ElementResizeListener
  998. * <li>Elements for which at least one ManagedLayout has registered a
  999. * dependency
  1000. * </ul>
  1001. *
  1002. * -1 is returned if the element has not been measured. If 0 is returned, it
  1003. * might indicate that the element is not attached to the DOM.
  1004. * <p>
  1005. * The value returned by this method is always rounded up. To get the exact
  1006. * outer width, use {@link #getOuterHeightDouble(Element)}
  1007. *
  1008. * @param element
  1009. * the element to get the measured size for
  1010. * @return the measured inner width (excluding margins, paddings and
  1011. * borders) of the element in pixels.
  1012. */
  1013. public final int getInnerWidth(Element element) {
  1014. assert needsMeasure(
  1015. element) : "Getting measurement for element that is not measured";
  1016. return (int) Math
  1017. .ceil(getMeasuredSize(element, nullSize).getInnerWidth());
  1018. }
  1019. /**
  1020. * Gets the inner width (excluding margins, paddings and borders) of the
  1021. * given element, provided that it has been measured. These elements are
  1022. * guaranteed to be measured:
  1023. * <ul>
  1024. * <li>ManagedLayouts and their child Connectors
  1025. * <li>Elements for which there is at least one ElementResizeListener
  1026. * <li>Elements for which at least one ManagedLayout has registered a
  1027. * dependency
  1028. * </ul>
  1029. *
  1030. * -1 is returned if the element has not been measured. If 0 is returned, it
  1031. * might indicate that the element is not attached to the DOM.
  1032. *
  1033. * @since 7.5.1
  1034. * @param element
  1035. * the element to get the measured size for
  1036. * @return the measured inner width (excluding margins, paddings and
  1037. * borders) of the element in pixels.
  1038. */
  1039. public final double getInnerWidthDouble(Element element) {
  1040. assert needsMeasure(
  1041. element) : "Getting measurement for element that is not measured";
  1042. return getMeasuredSize(element, nullSize).getInnerWidth();
  1043. }
  1044. /**
  1045. * Gets the border height (top border + bottom border) of the given element,
  1046. * provided that it has been measured. These elements are guaranteed to be
  1047. * measured:
  1048. * <ul>
  1049. * <li>ManagedLayouts and their child Connectors
  1050. * <li>Elements for which there is at least one ElementResizeListener
  1051. * <li>Elements for which at least one ManagedLayout has registered a
  1052. * dependency
  1053. * </ul>
  1054. *
  1055. * A negative number is returned if the element has not been measured. If 0
  1056. * is returned, it might indicate that the element is not attached to the
  1057. * DOM.
  1058. *
  1059. * @param element
  1060. * the element to get the measured size for
  1061. * @return the measured border height (top border + bottom border) of the
  1062. * element in pixels.
  1063. */
  1064. public final int getBorderHeight(Element element) {
  1065. assert needsMeasure(
  1066. element) : "Getting measurement for element that is not measured";
  1067. return getMeasuredSize(element, nullSize).getBorderHeight();
  1068. }
  1069. /**
  1070. * Gets the padding height (top padding + bottom padding) of the given
  1071. * element, provided that it has been measured. These elements are
  1072. * guaranteed to be measured:
  1073. * <ul>
  1074. * <li>ManagedLayouts and their child Connectors
  1075. * <li>Elements for which there is at least one ElementResizeListener
  1076. * <li>Elements for which at least one ManagedLayout has registered a
  1077. * dependency
  1078. * </ul>
  1079. *
  1080. * A negative number is returned if the element has not been measured. If 0
  1081. * is returned, it might indicate that the element is not attached to the
  1082. * DOM.
  1083. *
  1084. * @param element
  1085. * the element to get the measured size for
  1086. * @return the measured padding height (top padding + bottom padding) of the
  1087. * element in pixels.
  1088. */
  1089. public int getPaddingHeight(Element element) {
  1090. assert needsMeasure(
  1091. element) : "Getting measurement for element that is not measured";
  1092. return getMeasuredSize(element, nullSize).getPaddingHeight();
  1093. }
  1094. /**
  1095. * Gets the border width (left border + right border) of the given element,
  1096. * provided that it has been measured. These elements are guaranteed to be
  1097. * measured:
  1098. * <ul>
  1099. * <li>ManagedLayouts and their child Connectors
  1100. * <li>Elements for which there is at least one ElementResizeListener
  1101. * <li>Elements for which at least one ManagedLayout has registered a
  1102. * dependency
  1103. * </ul>
  1104. *
  1105. * A negative number is returned if the element has not been measured. If 0
  1106. * is returned, it might indicate that the element is not attached to the
  1107. * DOM.
  1108. *
  1109. * @param element
  1110. * the element to get the measured size for
  1111. * @return the measured border width (left border + right border) of the
  1112. * element in pixels.
  1113. */
  1114. public int getBorderWidth(Element element) {
  1115. assert needsMeasure(
  1116. element) : "Getting measurement for element that is not measured";
  1117. return getMeasuredSize(element, nullSize).getBorderWidth();
  1118. }
  1119. /**
  1120. * Gets the top border of the given element, provided that it has been
  1121. * measured. These elements are guaranteed to be measured:
  1122. * <ul>
  1123. * <li>ManagedLayouts and their child Connectors
  1124. * <li>Elements for which there is at least one ElementResizeListener
  1125. * <li>Elements for which at least one ManagedLayout has registered a
  1126. * dependency
  1127. * </ul>
  1128. *
  1129. * A negative number is returned if the element has not been measured. If 0
  1130. * is returned, it might indicate that the element is not attached to the
  1131. * DOM.
  1132. *
  1133. * @param element
  1134. * the element to get the measured size for
  1135. * @return the measured top border of the element in pixels.
  1136. */
  1137. public int getBorderTop(Element element) {
  1138. assert needsMeasure(
  1139. element) : "Getting measurement for element that is not measured";
  1140. return getMeasuredSize(element, nullSize).getBorderTop();
  1141. }
  1142. /**
  1143. * Gets the left border of the given element, provided that it has been
  1144. * measured. These elements are guaranteed to be measured:
  1145. * <ul>
  1146. * <li>ManagedLayouts and their child Connectors
  1147. * <li>Elements for which there is at least one ElementResizeListener
  1148. * <li>Elements for which at least one ManagedLayout has registered a
  1149. * dependency
  1150. * </ul>
  1151. *
  1152. * A negative number is returned if the element has not been measured. If 0
  1153. * is returned, it might indicate that the element is not attached to the
  1154. * DOM.
  1155. *
  1156. * @param element
  1157. * the element to get the measured size for
  1158. * @return the measured left border of the element in pixels.
  1159. */
  1160. public int getBorderLeft(Element element) {
  1161. assert needsMeasure(
  1162. element) : "Getting measurement for element that is not measured";
  1163. return getMeasuredSize(element, nullSize).getBorderLeft();
  1164. }
  1165. /**
  1166. * Gets the bottom border of the given element, provided that it has been
  1167. * measured. These elements are guaranteed to be measured:
  1168. * <ul>
  1169. * <li>ManagedLayouts and their child Connectors
  1170. * <li>Elements for which there is at least one ElementResizeListener
  1171. * <li>Elements for which at least one ManagedLayout has registered a
  1172. * dependency
  1173. * </ul>
  1174. *
  1175. * A negative number is returned if the element has not been measured. If 0
  1176. * is returned, it might indicate that the element is not attached to the
  1177. * DOM.
  1178. *
  1179. * @param element
  1180. * the element to get the measured size for
  1181. * @return the measured bottom border of the element in pixels.
  1182. */
  1183. public int getBorderBottom(Element element) {
  1184. assert needsMeasure(
  1185. element) : "Getting measurement for element that is not measured";
  1186. return getMeasuredSize(element, nullSize).getBorderBottom();
  1187. }
  1188. /**
  1189. * Gets the right border of the given element, provided that it has been
  1190. * measured. These elements are guaranteed to be measured:
  1191. * <ul>
  1192. * <li>ManagedLayouts and their child Connectors
  1193. * <li>Elements for which there is at least one ElementResizeListener
  1194. * <li>Elements for which at least one ManagedLayout has registered a
  1195. * dependency
  1196. * </ul>
  1197. *
  1198. * A negative number is returned if the element has not been measured. If 0
  1199. * is returned, it might indicate that the element is not attached to the
  1200. * DOM.
  1201. *
  1202. * @param element
  1203. * the element to get the measured size for
  1204. * @return the measured right border of the element in pixels.
  1205. */
  1206. public int getBorderRight(Element element) {
  1207. assert needsMeasure(
  1208. element) : "Getting measurement for element that is not measured";
  1209. return getMeasuredSize(element, nullSize).getBorderRight();
  1210. }
  1211. /**
  1212. * Gets the padding width (left padding + right padding) of the given
  1213. * element, provided that it has been measured. These elements are
  1214. * guaranteed to be measured:
  1215. * <ul>
  1216. * <li>ManagedLayouts and their child Connectors
  1217. * <li>Elements for which there is at least one ElementResizeListener
  1218. * <li>Elements for which at least one ManagedLayout has registered a
  1219. * dependency
  1220. * </ul>
  1221. *
  1222. * A negative number is returned if the element has not been measured. If 0
  1223. * is returned, it might indicate that the element is not attached to the
  1224. * DOM.
  1225. *
  1226. * @param element
  1227. * the element to get the measured size for
  1228. * @return the measured padding width (left padding + right padding) of the
  1229. * element in pixels.
  1230. */
  1231. public int getPaddingWidth(Element element) {
  1232. assert needsMeasure(
  1233. element) : "Getting measurement for element that is not measured";
  1234. return getMeasuredSize(element, nullSize).getPaddingWidth();
  1235. }
  1236. /**
  1237. * Gets the top padding of the given element, provided that it has been
  1238. * measured. These elements are guaranteed to be measured:
  1239. * <ul>
  1240. * <li>ManagedLayouts and their child Connectors
  1241. * <li>Elements for which there is at least one ElementResizeListener
  1242. * <li>Elements for which at least one ManagedLayout has registered a
  1243. * dependency
  1244. * </ul>
  1245. *
  1246. * A negative number is returned if the element has not been measured. If 0
  1247. * is returned, it might indicate that the element is not attached to the
  1248. * DOM.
  1249. *
  1250. * @param element
  1251. * the element to get the measured size for
  1252. * @return the measured top padding of the element in pixels.
  1253. */
  1254. public int getPaddingTop(Element element) {
  1255. assert needsMeasure(
  1256. element) : "Getting measurement for element that is not measured";
  1257. return getMeasuredSize(element, nullSize).getPaddingTop();
  1258. }
  1259. /**
  1260. * Gets the left padding of the given element, provided that it has been
  1261. * measured. These elements are guaranteed to be measured:
  1262. * <ul>
  1263. * <li>ManagedLayouts and their child Connectors
  1264. * <li>Elements for which there is at least one ElementResizeListener
  1265. * <li>Elements for which at least one ManagedLayout has registered a
  1266. * dependency
  1267. * </ul>
  1268. *
  1269. * A negative number is returned if the element has not been measured. If 0
  1270. * is returned, it might indicate that the element is not attached to the
  1271. * DOM.
  1272. *
  1273. * @param element
  1274. * the element to get the measured size for
  1275. * @return the measured left padding of the element in pixels.
  1276. */
  1277. public int getPaddingLeft(Element element) {
  1278. assert needsMeasure(
  1279. element) : "Getting measurement for element that is not measured";
  1280. return getMeasuredSize(element, nullSize).getPaddingLeft();
  1281. }
  1282. /**
  1283. * Gets the bottom 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 bottom padding of the element in pixels.
  1299. */
  1300. public int getPaddingBottom(Element element) {
  1301. assert needsMeasure(
  1302. element) : "Getting measurement for element that is not measured";
  1303. return getMeasuredSize(element, nullSize).getPaddingBottom();
  1304. }
  1305. /**
  1306. * Gets the right padding of the given element, provided that it has been
  1307. * measured. These elements are guaranteed to be measured:
  1308. * <ul>
  1309. * <li>ManagedLayouts and their child Connectors
  1310. * <li>Elements for which there is at least one ElementResizeListener
  1311. * <li>Elements for which at least one ManagedLayout has registered a
  1312. * dependency
  1313. * </ul>
  1314. *
  1315. * A negative number is returned if the element has not been measured. If 0
  1316. * is returned, it might indicate that the element is not attached to the
  1317. * DOM.
  1318. *
  1319. * @param element
  1320. * the element to get the measured size for
  1321. * @return the measured right padding of the element in pixels.
  1322. */
  1323. public int getPaddingRight(Element element) {
  1324. assert needsMeasure(
  1325. element) : "Getting measurement for element that is not measured";
  1326. return getMeasuredSize(element, nullSize).getPaddingRight();
  1327. }
  1328. /**
  1329. * Gets the top margin of the given element, provided that it has been
  1330. * measured. These elements are guaranteed to be measured:
  1331. * <ul>
  1332. * <li>ManagedLayouts and their child Connectors
  1333. * <li>Elements for which there is at least one ElementResizeListener
  1334. * <li>Elements for which at least one ManagedLayout has registered a
  1335. * dependency
  1336. * </ul>
  1337. *
  1338. * A negative number is returned if the element has not been measured. If 0
  1339. * is returned, it might indicate that the element is not attached to the
  1340. * DOM.
  1341. *
  1342. * @param element
  1343. * the element to get the measured size for
  1344. * @return the measured top margin of the element in pixels.
  1345. */
  1346. public int getMarginTop(Element element) {
  1347. assert needsMeasure(
  1348. element) : "Getting measurement for element that is not measured";
  1349. return getMeasuredSize(element, nullSize).getMarginTop();
  1350. }
  1351. /**
  1352. * Gets the right margin of the given element, provided that it has been
  1353. * measured. These elements are guaranteed to be measured:
  1354. * <ul>
  1355. * <li>ManagedLayouts and their child Connectors
  1356. * <li>Elements for which there is at least one ElementResizeListener
  1357. * <li>Elements for which at least one ManagedLayout has registered a
  1358. * dependency
  1359. * </ul>
  1360. *
  1361. * A negative number is returned if the element has not been measured. If 0
  1362. * is returned, it might indicate that the element is not attached to the
  1363. * DOM.
  1364. *
  1365. * @param element
  1366. * the element to get the measured size for
  1367. * @return the measured right margin of the element in pixels.
  1368. */
  1369. public int getMarginRight(Element element) {
  1370. assert needsMeasure(
  1371. element) : "Getting measurement for element that is not measured";
  1372. return getMeasuredSize(element, nullSize).getMarginRight();
  1373. }
  1374. /**
  1375. * Gets the bottom margin of the given element, provided that it has been
  1376. * measured. These elements are guaranteed to be measured:
  1377. * <ul>
  1378. * <li>ManagedLayouts and their child Connectors
  1379. * <li>Elements for which there is at least one ElementResizeListener
  1380. * <li>Elements for which at least one ManagedLayout has registered a
  1381. * dependency
  1382. * </ul>
  1383. *
  1384. * A negative number is returned if the element has not been measured. If 0
  1385. * is returned, it might indicate that the element is not attached to the
  1386. * DOM.
  1387. *
  1388. * @param element
  1389. * the element to get the measured size for
  1390. * @return the measured bottom margin of the element in pixels.
  1391. */
  1392. public int getMarginBottom(Element element) {
  1393. assert needsMeasure(
  1394. element) : "Getting measurement for element that is not measured";
  1395. return getMeasuredSize(element, nullSize).getMarginBottom();
  1396. }
  1397. /**
  1398. * Gets the left margin of the given element, provided that it has been
  1399. * measured. These elements are guaranteed to be measured:
  1400. * <ul>
  1401. * <li>ManagedLayouts and their child Connectors
  1402. * <li>Elements for which there is at least one ElementResizeListener
  1403. * <li>Elements for which at least one ManagedLayout has registered a
  1404. * dependency
  1405. * </ul>
  1406. *
  1407. * A negative number is returned if the element has not been measured. If 0
  1408. * is returned, it might indicate that the element is not attached to the
  1409. * DOM.
  1410. *
  1411. * @param element
  1412. * the element to get the measured size for
  1413. * @return the measured left margin of the element in pixels.
  1414. */
  1415. public int getMarginLeft(Element element) {
  1416. assert needsMeasure(
  1417. element) : "Getting measurement for element that is not measured";
  1418. return getMeasuredSize(element, nullSize).getMarginLeft();
  1419. }
  1420. /**
  1421. * Gets the combined top & bottom margin of the given element, provided that
  1422. * they have been measured. These elements are guaranteed to be measured:
  1423. * <ul>
  1424. * <li>ManagedLayouts and their child Connectors
  1425. * <li>Elements for which there is at least one ElementResizeListener
  1426. * <li>Elements for which at least one ManagedLayout has registered a
  1427. * dependency
  1428. * </ul>
  1429. *
  1430. * A negative number is returned if the element has not been measured. If 0
  1431. * is returned, it might indicate that the element is not attached to the
  1432. * DOM.
  1433. *
  1434. * @param element
  1435. * the element to get the measured margin for
  1436. * @return the measured top+bottom margin of the element in pixels.
  1437. */
  1438. public int getMarginHeight(Element element) {
  1439. return getMarginTop(element) + getMarginBottom(element);
  1440. }
  1441. /**
  1442. * Gets the combined left & right margin of the given element, provided that
  1443. * they have been measured. These elements are guaranteed to be measured:
  1444. * <ul>
  1445. * <li>ManagedLayouts and their child Connectors
  1446. * <li>Elements for which there is at least one ElementResizeListener
  1447. * <li>Elements for which at least one ManagedLayout has registered a
  1448. * dependency
  1449. * </ul>
  1450. *
  1451. * A negative number is returned if the element has not been measured. If 0
  1452. * is returned, it might indicate that the element is not attached to the
  1453. * DOM.
  1454. *
  1455. * @param element
  1456. * the element to get the measured margin for
  1457. * @return the measured left+right margin of the element in pixels.
  1458. */
  1459. public int getMarginWidth(Element element) {
  1460. return getMarginLeft(element) + getMarginRight(element);
  1461. }
  1462. /**
  1463. * Registers the outer height (including margins, borders and paddings) of a
  1464. * component. This can be used as an optimization by ManagedLayouts; by
  1465. * informing the LayoutManager about what size a component will have, the
  1466. * layout propagation can continue directly without first measuring the
  1467. * potentially resized elements.
  1468. *
  1469. * @param component
  1470. * the component for which the size is reported
  1471. * @param outerHeight
  1472. * the new outer height (including margins, borders and paddings)
  1473. * of the component in pixels
  1474. */
  1475. public void reportOuterHeight(ComponentConnector component,
  1476. int outerHeight) {
  1477. Element element = component.getWidget().getElement();
  1478. MeasuredSize measuredSize = getMeasuredSize(element);
  1479. if (isLayoutRunning()) {
  1480. boolean heightChanged = measuredSize.setOuterHeight(outerHeight);
  1481. if (heightChanged) {
  1482. onConnectorChange(component, false, true);
  1483. notifyListenersAndDepdendents(element, false, true);
  1484. }
  1485. currentDependencyTree.setNeedsVerticalMeasure(component, false);
  1486. } else if (measuredSize.getOuterHeight() != outerHeight) {
  1487. setNeedsMeasure(component);
  1488. }
  1489. }
  1490. /**
  1491. * Registers the height reserved for a relatively sized component. This can
  1492. * be used as an optimization by ManagedLayouts; by informing the
  1493. * LayoutManager about what size a component will have, the layout
  1494. * propagation can continue directly without first measuring the potentially
  1495. * resized elements.
  1496. *
  1497. * @param component
  1498. * the relatively sized component for which the size is reported
  1499. * @param assignedHeight
  1500. * the inner height of the relatively sized component's parent
  1501. * element in pixels
  1502. */
  1503. public void reportHeightAssignedToRelative(ComponentConnector component,
  1504. int assignedHeight) {
  1505. assert component.isRelativeHeight();
  1506. float percentSize = parsePercent(component.getState().height == null
  1507. ? "" : component.getState().height);
  1508. int effectiveHeight = Math.round(assignedHeight * (percentSize / 100));
  1509. reportOuterHeight(component, effectiveHeight);
  1510. }
  1511. /**
  1512. * Registers the width reserved for a relatively sized component. This can
  1513. * be used as an optimization by ManagedLayouts; by informing the
  1514. * LayoutManager about what size a component will have, the layout
  1515. * propagation can continue directly without first measuring the potentially
  1516. * resized elements.
  1517. *
  1518. * @param component
  1519. * the relatively sized component for which the size is reported
  1520. * @param assignedWidth
  1521. * the inner width of the relatively sized component's parent
  1522. * element in pixels
  1523. */
  1524. public void reportWidthAssignedToRelative(ComponentConnector component,
  1525. int assignedWidth) {
  1526. assert component.isRelativeWidth();
  1527. float percentSize = parsePercent(component.getState().width == null ? ""
  1528. : component.getState().width);
  1529. int effectiveWidth = Math.round(assignedWidth * (percentSize / 100));
  1530. reportOuterWidth(component, effectiveWidth);
  1531. }
  1532. private static float parsePercent(String size) {
  1533. return Float.parseFloat(size.substring(0, size.length() - 1));
  1534. }
  1535. /**
  1536. * Registers the outer width (including margins, borders and paddings) of a
  1537. * component. This can be used as an optimization by ManagedLayouts; by
  1538. * informing the LayoutManager about what size a component will have, the
  1539. * layout propagation can continue directly without first measuring the
  1540. * potentially resized elements.
  1541. *
  1542. * @param component
  1543. * the component for which the size is reported
  1544. * @param outerWidth
  1545. * the new outer width (including margins, borders and paddings)
  1546. * of the component in pixels
  1547. */
  1548. public void reportOuterWidth(ComponentConnector component, int outerWidth) {
  1549. Element element = component.getWidget().getElement();
  1550. MeasuredSize measuredSize = getMeasuredSize(element);
  1551. if (isLayoutRunning()) {
  1552. boolean widthChanged = measuredSize.setOuterWidth(outerWidth);
  1553. if (widthChanged) {
  1554. onConnectorChange(component, true, false);
  1555. notifyListenersAndDepdendents(element, true, false);
  1556. }
  1557. currentDependencyTree.setNeedsHorizontalMeasure(component, false);
  1558. } else if (measuredSize.getOuterWidth() != outerWidth) {
  1559. setNeedsMeasure(component);
  1560. }
  1561. }
  1562. /**
  1563. * Adds a listener that will be notified whenever the size of a specific
  1564. * element changes. Adding a listener to an element also ensures that all
  1565. * sizes for that element will be available starting from the next layout
  1566. * phase.
  1567. *
  1568. * @param element
  1569. * the element that should be checked for size changes
  1570. * @param listener
  1571. * an ElementResizeListener that will be informed whenever the
  1572. * size of the target element has changed
  1573. */
  1574. public void addElementResizeListener(Element element,
  1575. ElementResizeListener listener) {
  1576. Collection<ElementResizeListener> listeners = elementResizeListeners
  1577. .get(element);
  1578. if (listeners == null) {
  1579. listeners = new HashSet<ElementResizeListener>();
  1580. elementResizeListeners.put(element, listeners);
  1581. ensureMeasured(element);
  1582. }
  1583. listeners.add(listener);
  1584. }
  1585. /**
  1586. * Removes an element resize listener from the provided element. This might
  1587. * cause this LayoutManager to stop tracking the size of the element if no
  1588. * other sources are interested in the size.
  1589. *
  1590. * @param element
  1591. * the element to which the element resize listener was
  1592. * previously added
  1593. * @param listener
  1594. * the ElementResizeListener that should no longer get informed
  1595. * about size changes to the target element.
  1596. */
  1597. public void removeElementResizeListener(Element element,
  1598. ElementResizeListener listener) {
  1599. Collection<ElementResizeListener> listeners = elementResizeListeners
  1600. .get(element);
  1601. if (listeners != null) {
  1602. listeners.remove(listener);
  1603. if (listeners.isEmpty()) {
  1604. elementResizeListeners.remove(element);
  1605. stopMeasuringIfUnecessary(element);
  1606. }
  1607. }
  1608. }
  1609. private void stopMeasuringIfUnecessary(Element element) {
  1610. if (!needsMeasure(element)) {
  1611. measuredNonConnectorElements.remove(element);
  1612. setMeasuredSize(element, null);
  1613. }
  1614. }
  1615. /**
  1616. * Informs this LayoutManager that the size of a component might have
  1617. * changed. This method should be used whenever the size of an individual
  1618. * component might have changed from outside of Vaadin's normal update
  1619. * phase, e.g. when an icon has been loaded or when the user resizes some
  1620. * part of the UI using the mouse.
  1621. * <p>
  1622. * To set an entire component hierarchy to be measured, use
  1623. * {@link #setNeedsMeasureRecursively(ComponentConnector)} instead.
  1624. * <p>
  1625. * If there is no upcoming layout phase, a new layout phase is scheduled.
  1626. *
  1627. * @param component
  1628. * the component whose size might have changed.
  1629. */
  1630. public void setNeedsMeasure(ComponentConnector component) {
  1631. if (isLayoutRunning()) {
  1632. currentDependencyTree.setNeedsMeasure(component, true);
  1633. } else {
  1634. needsMeasure.add(component.getConnectorId());
  1635. layoutLater();
  1636. }
  1637. }
  1638. /**
  1639. * Informs this LayoutManager that some sizes in a component hierarchy might
  1640. * have changed. This method should be used whenever the size of any child
  1641. * component might have changed from outside of Vaadin's normal update
  1642. * phase, e.g. when a CSS class name related to sizing has been changed.
  1643. * <p>
  1644. * To set a single component to be measured, use
  1645. * {@link #setNeedsMeasure(ComponentConnector)} instead.
  1646. * <p>
  1647. * If there is no upcoming layout phase, a new layout phase is scheduled.
  1648. *
  1649. * @since 7.2
  1650. * @param component
  1651. * the component at the root of the component hierarchy to
  1652. * measure
  1653. */
  1654. public void setNeedsMeasureRecursively(ComponentConnector component) {
  1655. setNeedsMeasure(component);
  1656. if (component instanceof HasComponentsConnector) {
  1657. HasComponentsConnector hasComponents = (HasComponentsConnector) component;
  1658. for (ComponentConnector child : hasComponents
  1659. .getChildComponents()) {
  1660. setNeedsMeasureRecursively(child);
  1661. }
  1662. }
  1663. }
  1664. public void setEverythingNeedsMeasure() {
  1665. everythingNeedsMeasure = true;
  1666. }
  1667. private static Logger getLogger() {
  1668. return Logger.getLogger(LayoutManager.class.getName());
  1669. }
  1670. /**
  1671. * Checks if there is something waiting for a layout to take place.
  1672. *
  1673. * @since 7.5.6
  1674. * @return true if there are connectors waiting for measurement or layout,
  1675. * false otherwise
  1676. */
  1677. public boolean isLayoutNeeded() {
  1678. if (!needsHorizontalLayout.isEmpty()
  1679. || !needsVerticalLayout.isEmpty()) {
  1680. return true;
  1681. }
  1682. if (!needsMeasure.isEmpty()) {
  1683. return true;
  1684. }
  1685. if (everythingNeedsMeasure) {
  1686. return true;
  1687. }
  1688. return false;
  1689. }
  1690. }