All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.client.LayoutManager Maven / Gradle / Ivy

Go to download

Vaadin is a web application framework for Rich Internet Applications (RIA). Vaadin enables easy development and maintenance of fast and secure rich web applications with a stunning look and feel and a wide browser support. It features a server-side architecture with the majority of the logic running on the server. Ajax technology is used at the browser-side to ensure a rich and interactive user experience.

There is a newer version: 8.27.1
Show newest version
/*
 * Copyright 2000-2016 Vaadin Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.vaadin.client;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.user.client.Timer;
import com.vaadin.client.MeasuredSize.MeasureResult;
import com.vaadin.client.ui.ManagedLayout;
import com.vaadin.client.ui.PostLayoutListener;
import com.vaadin.client.ui.SimpleManagedLayout;
import com.vaadin.client.ui.VNotification;
import com.vaadin.client.ui.layout.ElementResizeEvent;
import com.vaadin.client.ui.layout.ElementResizeListener;
import com.vaadin.client.ui.layout.LayoutDependencyTree;

public class LayoutManager {
    private static final String STATE_CHANGE_MESSAGE = "Cannot run layout while processing state change from the server.";

    private static final String LOOP_ABORT_MESSAGE = "Aborting layout after 100 passes. This would probably be an infinite loop.";

    private static final boolean debugLogging = false;

    private ApplicationConnection connection;
    private final Set measuredNonConnectorElements = new HashSet<>();
    private final MeasuredSize nullSize = new MeasuredSize();

    private LayoutDependencyTree currentDependencyTree;

    private FastStringSet needsHorizontalLayout = FastStringSet.create();
    private FastStringSet needsVerticalLayout = FastStringSet.create();

    private FastStringSet needsMeasure = FastStringSet.create();

    private FastStringSet pendingOverflowFixes = FastStringSet.create();

    private final Map> elementResizeListeners = new HashMap<>();
    private final Set listenersToFire = new HashSet<>();

    private boolean layoutPending = false;
    private Timer layoutTimer = new Timer() {
        @Override
        public void run() {
            layoutNow();
        }
    };
    private boolean everythingNeedsMeasure = false;

    /**
     * Sets the application connection this instance is connected to. Called
     * internally by the framework.
     *
     * @param connection
     *            the application connection this instance is connected to
     */
    public void setConnection(ApplicationConnection connection) {
        if (this.connection != null) {
            throw new RuntimeException(
                    "LayoutManager connection can never be changed");
        }
        this.connection = connection;
    }

    /**
     * Returns the application connection for this layout manager.
     *
     * @return connection
     */
    protected ApplicationConnection getConnection() {
        return connection;
    }

    /**
     * Gets the layout manager associated with the given
     * {@link ApplicationConnection}.
     *
     * @param connection
     *            the application connection to get a layout manager for
     * @return the layout manager associated with the provided application
     *         connection
     */
    public static LayoutManager get(ApplicationConnection connection) {
        return connection.getLayoutManager();
    }

    /**
     * Registers that a ManagedLayout is depending on the size of an Element.
     * This causes this layout manager to measure the element in the beginning
     * of every layout phase and call the appropriate layout method of the
     * managed layout if the size of the element has changed.
     *
     * @param owner
     *            the ManagedLayout that depends on an element
     * @param element
     *            the Element that should be measured
     */
    public void registerDependency(ManagedLayout owner, Element element) {
        MeasuredSize measuredSize = ensureMeasured(element);
        setNeedsLayout(owner);
        measuredSize.addDependent(owner.getConnectorId());
    }

    private MeasuredSize ensureMeasured(Element element) {
        MeasuredSize measuredSize = getMeasuredSize(element, null);
        if (measuredSize == null) {
            measuredSize = new MeasuredSize();

            if (ConnectorMap.get(connection).getConnector(element) == null) {
                measuredNonConnectorElements.add(element);
            }
            setMeasuredSize(element, measuredSize);
        }
        return measuredSize;
    }

    private boolean needsMeasure(Element e) {
        ComponentConnector connector = connection.getConnectorMap()
                .getConnector(e);
        if (connector != null && needsMeasureForManagedLayout(connector)) {
            return true;
        } else if (elementResizeListeners.containsKey(e)) {
            return true;
        } else if (getMeasuredSize(e, nullSize).hasDependents()) {
            return true;
        } else {
            return false;
        }
    }

    private boolean needsMeasureForManagedLayout(ComponentConnector connector) {
        if (connector instanceof ManagedLayout) {
            return true;
        } else if (connector.getParent() instanceof ManagedLayout) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Assigns a measured size to an element. Method defined as protected for
     * legacy reasons.
     *
     * @param element
     *            the dom element to attach the measured size to
     * @param measuredSize
     *            the measured size to attach to the element. If
     *            null, any previous measured size is removed.
     */
    protected native void setMeasuredSize(Element element,
            MeasuredSize measuredSize)
    /*-{
        if (measuredSize) {
            element.vMeasuredSize = measuredSize;
        } else {
            delete element.vMeasuredSize;
        }
    }-*/;

    /**
     * Gets the measured size for an element. Method defined as protected for
     * legacy reasons.
     *
     * @param element
     *            The element to get measured size for
     * @param defaultSize
     *            The size to return if no measured size could be found
     * @return The measured size for the element or {@literal defaultSize}
     */
    protected native MeasuredSize getMeasuredSize(Element element,
            MeasuredSize defaultSize)
    /*-{
        return element.vMeasuredSize || defaultSize;
    }-*/;

    private final MeasuredSize getMeasuredSize(Element element) {
        MeasuredSize measuredSize = getMeasuredSize(element, null);
        if (measuredSize == null) {
            measuredSize = new MeasuredSize();
            setMeasuredSize(element, measuredSize);
        }
        return measuredSize;
    }

    /**
     * Registers that a ManagedLayout is no longer depending on the size of an
     * Element.
     *
     * @see #registerDependency(ManagedLayout, Element)
     *
     * @param owner
     *            the ManagedLayout no longer depends on an element
     * @param element
     *            the Element that that no longer needs to be measured
     */
    public void unregisterDependency(ManagedLayout owner, Element element) {
        MeasuredSize measuredSize = getMeasuredSize(element, null);
        if (measuredSize == null) {
            return;
        }
        measuredSize.removeDependent(owner.getConnectorId());
        stopMeasuringIfUnecessary(element);
    }

    public boolean isLayoutRunning() {
        return currentDependencyTree != null;
    }

    private void countLayout(FastStringMap layoutCounts,
            ManagedLayout layout) {
        Integer count = layoutCounts.get(layout.getConnectorId());
        if (count == null) {
            count = Integer.valueOf(0);
        } else {
            count = Integer.valueOf(count.intValue() + 1);
        }
        layoutCounts.put(layout.getConnectorId(), count);
        if (count.intValue() > 2) {
            getLogger().severe(Util.getConnectorString(layout)
                    + " has been layouted " + count.intValue() + " times");
        }
    }

    public void layoutLater() {
        if (!layoutPending) {
            layoutPending = true;
            layoutTimer.schedule(100);
        }
    }

    public void layoutNow() {
        if (isLayoutRunning()) {
            throw new IllegalStateException(
                    "Can't start a new layout phase before the previous layout phase ends.");
        }

        if (connection.getMessageHandler().isUpdatingState()) {
            // If assertions are enabled, throw an exception
            assert false : STATE_CHANGE_MESSAGE;

            // Else just log a warning and postpone the layout
            getLogger().warning(STATE_CHANGE_MESSAGE);

            // Framework will call layoutNow when the state update is completed
            return;
        }

        layoutPending = false;
        layoutTimer.cancel();
        try {
            currentDependencyTree = new LayoutDependencyTree(connection);
            doLayout();
        } finally {
            currentDependencyTree = null;
        }
    }

    /**
     * Called once per iteration in the layout loop before size calculations so
     * different browsers quirks can be handled. Mainly this exists for legacy
     * reasons.
     */
    protected void performBrowserLayoutHacks() {
        // Permutations implement this
    }

    private void doLayout() {
        getLogger().info("Starting layout phase");
        Profiler.enter("LayoutManager phase init");

        FastStringMap layoutCounts = FastStringMap.create();

        int passes = 0;
        Duration totalDuration = new Duration();

        ConnectorMap connectorMap = ConnectorMap.get(connection);

        JsArrayString dump = needsHorizontalLayout.dump();
        int dumpLength = dump.length();
        for (int i = 0; i < dumpLength; i++) {
            String layoutId = dump.get(i);
            currentDependencyTree.setNeedsHorizontalLayout(layoutId, true);
        }

        dump = needsVerticalLayout.dump();
        dumpLength = dump.length();
        for (int i = 0; i < dumpLength; i++) {
            String layoutId = dump.get(i);
            currentDependencyTree.setNeedsVerticalLayout(layoutId, true);
        }
        needsHorizontalLayout = FastStringSet.create();
        needsVerticalLayout = FastStringSet.create();

        dump = needsMeasure.dump();
        dumpLength = dump.length();
        for (int i = 0; i < dumpLength; i++) {
            ServerConnector connector = connectorMap.getConnector(dump.get(i));
            if (connector != null) {
                currentDependencyTree
                        .setNeedsMeasure((ComponentConnector) connector, true);
            }
        }
        needsMeasure = FastStringSet.create();

        measureNonConnectors();

        Profiler.leave("LayoutManager phase init");

        while (true) {
            Profiler.enter("Layout pass");
            passes++;

            performBrowserLayoutHacks();

            Profiler.enter("Layout measure connectors");
            int measuredConnectorCount = measureConnectors(
                    currentDependencyTree, everythingNeedsMeasure);
            Profiler.leave("Layout measure connectors");

            everythingNeedsMeasure = false;
            if (measuredConnectorCount == 0) {
                getLogger().info("No more changes in pass " + passes);
                Profiler.leave("Layout pass");
                break;
            }

            int firedListeners = 0;
            if (!listenersToFire.isEmpty()) {
                HashSet listenersCopy = new HashSet(
                        listenersToFire);
                listenersToFire.clear();
                firedListeners = listenersToFire.size();
                Profiler.enter("Layout fire resize events");
                for (Element element : listenersCopy) {
                    Collection listeners = elementResizeListeners
                            .get(element);
                    if (listeners != null) {
                        Profiler.enter(
                                "Layout fire resize events - listeners not null");
                        Profiler.enter(
                                "ElementResizeListener.onElementResize copy list");
                        ElementResizeListener[] array = listeners.toArray(
                                new ElementResizeListener[listeners.size()]);
                        Profiler.leave(
                                "ElementResizeListener.onElementResize copy list");
                        ElementResizeEvent event = new ElementResizeEvent(this,
                                element);
                        for (ElementResizeListener listener : array) {
                            try {
                                String key = null;
                                if (Profiler.isEnabled()) {
                                    Profiler.enter(
                                            "ElementResizeListener.onElementResize construct profiler key");
                                    key = "ElementResizeListener.onElementResize for "
                                            + listener.getClass()
                                                    .getSimpleName();
                                    Profiler.leave(
                                            "ElementResizeListener.onElementResize construct profiler key");
                                    Profiler.enter(key);
                                }

                                listener.onElementResize(event);
                                if (Profiler.isEnabled()) {
                                    Profiler.leave(key);
                                }
                            } catch (RuntimeException e) {
                                getLogger().log(Level.SEVERE,
                                        "Error in resize listener", e);
                            }
                        }
                        Profiler.leave(
                                "Layout fire resize events - listeners not null");
                    }
                }
                Profiler.leave("Layout fire resize events");
            }

            Profiler.enter("LayoutManager handle ManagedLayout");

            FastStringSet updatedSet = FastStringSet.create();

            int layoutCount = 0;
            while (currentDependencyTree.hasHorizontalConnectorToLayout()
                    || currentDependencyTree.hasVerticaConnectorToLayout()) {

                JsArrayString layoutTargets = currentDependencyTree
                        .getHorizontalLayoutTargetsJsArray();
                int length = layoutTargets.length();
                for (int i = 0; i < length; i++) {
                    ManagedLayout layout = (ManagedLayout) connectorMap
                            .getConnector(layoutTargets.get(i));
                    if (layout instanceof DirectionalManagedLayout) {
                        currentDependencyTree
                                .markAsHorizontallyLayouted(layout);
                        DirectionalManagedLayout cl = (DirectionalManagedLayout) layout;
                        try {
                            String key = null;
                            if (Profiler.isEnabled()) {
                                key = "layoutHorizontally() for "
                                        + cl.getClass().getSimpleName();
                                Profiler.enter(key);
                            }

                            cl.layoutHorizontally();
                            layoutCount++;

                            if (Profiler.isEnabled()) {
                                Profiler.leave(key);
                            }
                        } catch (RuntimeException e) {
                            getLogger().log(Level.SEVERE,
                                    "Error in ManagedLayout handling", e);
                        }
                        countLayout(layoutCounts, cl);
                    } else {
                        currentDependencyTree
                                .markAsHorizontallyLayouted(layout);
                        currentDependencyTree.markAsVerticallyLayouted(layout);
                        SimpleManagedLayout rr = (SimpleManagedLayout) layout;
                        try {
                            String key = null;
                            if (Profiler.isEnabled()) {
                                key = "layout() for "
                                        + rr.getClass().getSimpleName();
                                Profiler.enter(key);
                            }

                            rr.layout();
                            layoutCount++;

                            if (Profiler.isEnabled()) {
                                Profiler.leave(key);
                            }
                        } catch (RuntimeException e) {
                            getLogger().log(Level.SEVERE,
                                    "Error in SimpleManagedLayout (horizontal) handling",
                                    e);

                        }
                        countLayout(layoutCounts, rr);
                    }
                    if (debugLogging) {
                        updatedSet.add(layout.getConnectorId());
                    }
                }

                layoutTargets = currentDependencyTree
                        .getVerticalLayoutTargetsJsArray();
                length = layoutTargets.length();
                for (int i = 0; i < length; i++) {
                    ManagedLayout layout = (ManagedLayout) connectorMap
                            .getConnector(layoutTargets.get(i));
                    if (layout instanceof DirectionalManagedLayout) {
                        currentDependencyTree.markAsVerticallyLayouted(layout);
                        DirectionalManagedLayout cl = (DirectionalManagedLayout) layout;
                        try {
                            String key = null;
                            if (Profiler.isEnabled()) {
                                key = "layoutVertically() for "
                                        + cl.getClass().getSimpleName();
                                Profiler.enter(key);
                            }

                            cl.layoutVertically();
                            layoutCount++;

                            if (Profiler.isEnabled()) {
                                Profiler.leave(key);
                            }
                        } catch (RuntimeException e) {
                            getLogger().log(Level.SEVERE,
                                    "Error in DirectionalManagedLayout handling",
                                    e);
                        }
                        countLayout(layoutCounts, cl);
                    } else {
                        currentDependencyTree
                                .markAsHorizontallyLayouted(layout);
                        currentDependencyTree.markAsVerticallyLayouted(layout);
                        SimpleManagedLayout rr = (SimpleManagedLayout) layout;
                        try {
                            String key = null;
                            if (Profiler.isEnabled()) {
                                key = "layout() for "
                                        + rr.getClass().getSimpleName();
                                Profiler.enter(key);
                            }

                            rr.layout();
                            layoutCount++;

                            if (Profiler.isEnabled()) {
                                Profiler.leave(key);
                            }
                        } catch (RuntimeException e) {
                            getLogger().log(Level.SEVERE,
                                    "Error in SimpleManagedLayout (vertical) handling",
                                    e);
                        }
                        countLayout(layoutCounts, rr);
                    }
                    if (debugLogging) {
                        updatedSet.add(layout.getConnectorId());
                    }
                }
            }

            Profiler.leave("LayoutManager handle ManagedLayout");

            if (debugLogging) {
                JsArrayString changedCids = updatedSet.dump();

                StringBuilder b = new StringBuilder("  ");
                b.append(changedCids.length());
                b.append(" requestLayout invocations ");
                if (changedCids.length() < 30) {
                    for (int i = 0; i < changedCids.length(); i++) {
                        if (i != 0) {
                            b.append(", ");
                        } else {
                            b.append(": ");
                        }
                        String connectorString = changedCids.get(i);
                        if (changedCids.length() < 10) {
                            ServerConnector connector = ConnectorMap
                                    .get(connection)
                                    .getConnector(connectorString);
                            connectorString = Util
                                    .getConnectorString(connector);
                        }
                        b.append(connectorString);
                    }
                }
                getLogger().info(b.toString());
            }

            Profiler.leave("Layout pass");

            getLogger().info("Pass " + passes + " measured "
                    + measuredConnectorCount + " elements, fired "
                    + firedListeners + " listeners and did " + layoutCount
                    + " layouts.");

            if (passes > 100) {
                getLogger().severe(LOOP_ABORT_MESSAGE);
                if (ApplicationConfiguration.isDebugMode()) {
                    VNotification
                            .createNotification(VNotification.DELAY_FOREVER,
                                    connection.getUIConnector().getWidget())
                            .show(LOOP_ABORT_MESSAGE, VNotification.CENTERED,
                                    "error");
                }
                break;
            }
        }

        Profiler.enter("layout PostLayoutListener");
        JsArrayObject componentConnectors = connectorMap
                .getComponentConnectorsAsJsArray();
        int size = componentConnectors.size();
        for (int i = 0; i < size; i++) {
            ComponentConnector connector = componentConnectors.get(i);
            if (connector instanceof PostLayoutListener) {
                String key = null;
                if (Profiler.isEnabled()) {
                    key = "layout PostLayoutListener for "
                            + connector.getClass().getSimpleName();
                    Profiler.enter(key);
                }

                ((PostLayoutListener) connector).postLayout();

                if (Profiler.isEnabled()) {
                    Profiler.leave(key);
                }
            }
        }
        Profiler.leave("layout PostLayoutListener");

        // Ensure temporary variables are cleaned
        if (!pendingOverflowFixes.isEmpty()) {
            getLogger().warning(
                    "pendingOverflowFixes is not empty at the end of doLayout: "
                            + pendingOverflowFixes.dump());
            pendingOverflowFixes = FastStringSet.create();
        }

        getLogger().info("Total layout phase time: "
                + totalDuration.elapsedMillis() + "ms");
    }

    private void logConnectorStatus(int connectorId) {
        currentDependencyTree.logDependencyStatus(
                (ComponentConnector) ConnectorMap.get(connection)
                        .getConnector(Integer.toString(connectorId)));
    }

    private int measureConnectors(LayoutDependencyTree layoutDependencyTree,
            boolean measureAll) {
        Profiler.enter("Layout overflow fix handling");
        JsArrayString pendingOverflowConnectorsIds = pendingOverflowFixes
                .dump();
        int pendingOverflowCount = pendingOverflowConnectorsIds.length();
        ConnectorMap connectorMap = ConnectorMap.get(connection);
        if (pendingOverflowCount > 0) {
            HashMap originalOverflows = new HashMap<>();

            FastStringSet delayedOverflowFixes = FastStringSet.create();

            // First set overflow to hidden (and save previous value so it can
            // be restored later)
            for (int i = 0; i < pendingOverflowCount; i++) {
                String connectorId = pendingOverflowConnectorsIds.get(i);
                ComponentConnector componentConnector = (ComponentConnector) connectorMap
                        .getConnector(connectorId);

                if (delayOverflowFix(componentConnector)) {
                    delayedOverflowFixes.add(connectorId);
                    continue;
                }

                if (debugLogging) {
                    getLogger().info("Doing overflow fix for "
                            + Util.getConnectorString(componentConnector)
                            + " in " + Util.getConnectorString(
                                    componentConnector.getParent()));
                }
                Profiler.enter("Overflow fix apply");

                Element parentElement = componentConnector.getWidget()
                        .getElement().getParentElement();
                Style style = parentElement.getStyle();
                String originalOverflow = style.getOverflow();

                if (originalOverflow != null
                        && !originalOverflows.containsKey(parentElement)) {
                    // Store original value for restore, but only the first time
                    // the value is changed
                    originalOverflows.put(parentElement, originalOverflow);
                }

                style.setOverflow(Overflow.HIDDEN);
                Profiler.leave("Overflow fix apply");
            }

            pendingOverflowFixes.removeAll(delayedOverflowFixes);

            JsArrayString remainingOverflowFixIds = pendingOverflowFixes.dump();
            int remainingCount = remainingOverflowFixIds.length();

            Profiler.enter("Overflow fix reflow");
            // Then ensure all scrolling elements are reflowed by measuring
            for (int i = 0; i < remainingCount; i++) {
                ComponentConnector componentConnector = (ComponentConnector) connectorMap
                        .getConnector(remainingOverflowFixIds.get(i));
                componentConnector.getWidget().getElement().getParentElement()
                        .getOffsetHeight();
            }
            Profiler.leave("Overflow fix reflow");

            Profiler.enter("Overflow fix restore");
            // Finally restore old overflow value and update bookkeeping
            for (int i = 0; i < remainingCount; i++) {
                String connectorId = remainingOverflowFixIds.get(i);
                ComponentConnector componentConnector = (ComponentConnector) connectorMap
                        .getConnector(connectorId);
                Element parentElement = componentConnector.getWidget()
                        .getElement().getParentElement();
                parentElement.getStyle().setProperty("overflow",
                        originalOverflows.get(parentElement));

                layoutDependencyTree.setNeedsMeasure(componentConnector, true);
            }
            Profiler.leave("Overflow fix restore");

            if (!pendingOverflowFixes.isEmpty()) {
                getLogger().info(
                        "Did overflow fix for " + remainingCount + " elements");
            }
            pendingOverflowFixes = delayedOverflowFixes;
        }
        Profiler.leave("Layout overflow fix handling");

        int measureCount = 0;
        if (measureAll) {
            Profiler.enter("Layout measureAll");
            JsArrayObject allConnectors = connectorMap
                    .getComponentConnectorsAsJsArray();
            int size = allConnectors.size();

            // Find connectors that should actually be measured
            JsArrayObject connectors = JsArrayObject
                    .createArray().cast();
            for (int i = 0; i < size; i++) {
                ComponentConnector candidate = allConnectors.get(i);
                if (!Util.shouldSkipMeasurementOfConnector(candidate)
                        && needsMeasure(candidate.getWidget().getElement())) {
                    connectors.add(candidate);
                }
            }

            int connectorCount = connectors.size();
            for (int i = 0; i < connectorCount; i++) {
                measureConnector(connectors.get(i));
            }
            for (int i = 0; i < connectorCount; i++) {
                layoutDependencyTree.setNeedsMeasure(connectors.get(i), false);
            }
            measureCount += connectorCount;

            Profiler.leave("Layout measureAll");
        }

        Profiler.enter("Layout measure from tree");
        while (layoutDependencyTree.hasConnectorsToMeasure()) {
            JsArrayString measureTargets = layoutDependencyTree
                    .getMeasureTargetsJsArray();
            int length = measureTargets.length();
            for (int i = 0; i < length; i++) {
                ComponentConnector connector = (ComponentConnector) connectorMap
                        .getConnector(measureTargets.get(i));
                measureConnector(connector);
                measureCount++;
            }
            for (int i = 0; i < length; i++) {
                ComponentConnector connector = (ComponentConnector) connectorMap
                        .getConnector(measureTargets.get(i));
                layoutDependencyTree.setNeedsMeasure(connector, false);
            }
        }
        Profiler.leave("Layout measure from tree");

        return measureCount;
    }

    /*
     * Delay the overflow fix if the involved connectors might still change
     */
    private boolean delayOverflowFix(ComponentConnector componentConnector) {
        if (!currentDependencyTree.noMoreChangesExpected(componentConnector)) {
            return true;
        }
        ServerConnector parent = componentConnector.getParent();
        if (parent instanceof ComponentConnector && !currentDependencyTree
                .noMoreChangesExpected((ComponentConnector) parent)) {
            return true;
        }

        return false;
    }

    private void measureConnector(ComponentConnector connector) {
        Profiler.enter("LayoutManager.measureConnector");
        Element element = connector.getWidget().getElement();
        MeasuredSize measuredSize = getMeasuredSize(element);
        MeasureResult measureResult = measuredAndUpdate(element, measuredSize);

        if (measureResult.isChanged()) {
            onConnectorChange(connector, measureResult.isWidthChanged(),
                    measureResult.isHeightChanged());
        }
        Profiler.leave("LayoutManager.measureConnector");
    }

    private void onConnectorChange(ComponentConnector connector,
            boolean widthChanged, boolean heightChanged) {
        Profiler.enter("LayoutManager.onConnectorChange");
        Profiler.enter("LayoutManager.onConnectorChange setNeedsOverflowFix");
        setNeedsOverflowFix(connector);
        Profiler.leave("LayoutManager.onConnectorChange setNeedsOverflowFix");
        Profiler.enter("LayoutManager.onConnectorChange heightChanged");
        if (heightChanged) {
            currentDependencyTree.markHeightAsChanged(connector);
        }
        Profiler.leave("LayoutManager.onConnectorChange heightChanged");
        Profiler.enter("LayoutManager.onConnectorChange widthChanged");
        if (widthChanged) {
            currentDependencyTree.markWidthAsChanged(connector);
        }
        Profiler.leave("LayoutManager.onConnectorChange widthChanged");
        Profiler.leave("LayoutManager.onConnectorChange");
    }

    private void setNeedsOverflowFix(ComponentConnector connector) {
        // IE9 doesn't need the original fix, but for some reason it needs this
        if (BrowserInfo.get().requiresOverflowAutoFix()) {
            ComponentConnector scrollingBoundary = currentDependencyTree
                    .getScrollingBoundary(connector);
            if (scrollingBoundary != null) {
                pendingOverflowFixes.add(scrollingBoundary.getConnectorId());
            }
        }
    }

    private void measureNonConnectors() {
        Profiler.enter("LayoutManager.measureNonConenctors");
        for (Element element : measuredNonConnectorElements) {
            measuredAndUpdate(element, getMeasuredSize(element, null));
        }
        Profiler.leave("LayoutManager.measureNonConenctors");
        getLogger().info("Measured " + measuredNonConnectorElements.size()
                + " non connector elements");
    }

    private MeasureResult measuredAndUpdate(Element element,
            MeasuredSize measuredSize) {
        MeasureResult measureResult = measuredSize.measure(element);
        if (measureResult.isChanged()) {
            notifyListenersAndDepdendents(element,
                    measureResult.isWidthChanged(),
                    measureResult.isHeightChanged());
        }
        return measureResult;
    }

    private void notifyListenersAndDepdendents(Element element,
            boolean widthChanged, boolean heightChanged) {
        assert widthChanged || heightChanged;

        Profiler.enter("LayoutManager.notifyListenersAndDepdendents");

        MeasuredSize measuredSize = getMeasuredSize(element, nullSize);
        JsArrayString dependents = measuredSize.getDependents();
        for (int i = 0; i < dependents.length(); i++) {
            String pid = dependents.get(i);
            if (pid != null) {
                if (heightChanged) {
                    currentDependencyTree.setNeedsVerticalLayout(pid, true);
                }
                if (widthChanged) {
                    currentDependencyTree.setNeedsHorizontalLayout(pid, true);
                }
            }
        }
        if (elementResizeListeners.containsKey(element)) {
            listenersToFire.add(element);
        }
        Profiler.leave("LayoutManager.notifyListenersAndDepdendents");
    }

    private static boolean isManagedLayout(ComponentConnector connector) {
        return connector instanceof ManagedLayout;
    }

    public void forceLayout() {
        ConnectorMap connectorMap = connection.getConnectorMap();
        JsArrayObject componentConnectors = connectorMap
                .getComponentConnectorsAsJsArray();
        int size = componentConnectors.size();
        for (int i = 0; i < size; i++) {
            ComponentConnector connector = componentConnectors.get(i);
            if (connector instanceof ManagedLayout) {
                setNeedsLayout((ManagedLayout) connector);
            }
        }
        setEverythingNeedsMeasure();
        layoutNow();
    }

    /**
     * Marks that a ManagedLayout should be layouted in the next layout phase
     * even if none of the elements managed by the layout have been resized.
     * 

* This method should not be invoked during a layout phase since it only * controls what will happen in the beginning of the next phase. If you want * to explicitly cause some layout to be considered in an ongoing layout * phase, you should use {@link #setNeedsMeasure(ComponentConnector)} * instead. * * @param layout * the managed layout that should be layouted */ public final void setNeedsLayout(ManagedLayout layout) { setNeedsHorizontalLayout(layout); setNeedsVerticalLayout(layout); } /** * Marks that a ManagedLayout should be layouted horizontally in the next * layout phase even if none of the elements managed by the layout have been * resized horizontally. *

* For SimpleManagedLayout which is always layouted in both directions, this * has the same effect as {@link #setNeedsLayout(ManagedLayout)}. *

* This method should not be invoked during a layout phase since it only * controls what will happen in the beginning of the next phase. If you want * to explicitly cause some layout to be considered in an ongoing layout * phase, you should use {@link #setNeedsMeasure(ComponentConnector)} * instead. * * @param layout * the managed layout that should be layouted */ public final void setNeedsHorizontalLayout(ManagedLayout layout) { if (isLayoutRunning()) { getLogger().warning( "setNeedsHorizontalLayout should not be run while a layout phase is in progress."); } needsHorizontalLayout.add(layout.getConnectorId()); } /** * Marks that a ManagedLayout should be layouted vertically in the next * layout phase even if none of the elements managed by the layout have been * resized vertically. *

* For SimpleManagedLayout which is always layouted in both directions, this * has the same effect as {@link #setNeedsLayout(ManagedLayout)}. *

* This method should not be invoked during a layout phase since it only * controls what will happen in the beginning of the next phase. If you want * to explicitly cause some layout to be considered in an ongoing layout * phase, you should use {@link #setNeedsMeasure(ComponentConnector)} * instead. * * @param layout * the managed layout that should be layouted */ public final void setNeedsVerticalLayout(ManagedLayout layout) { if (isLayoutRunning()) { getLogger().warning( "setNeedsVerticalLayout should not be run while a layout phase is in progress."); } needsVerticalLayout.add(layout.getConnectorId()); } /** * Gets the outer height (including margins, paddings and borders) of the * given element, provided that it has been measured. These elements are * guaranteed to be measured: *

    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * -1 is returned if the element has not been measured. If 0 is returned, it * might indicate that the element is not attached to the DOM. *

* The value returned by this method is always rounded up. To get the exact * outer width, use {@link #getOuterHeightDouble(Element)} * * @param element * the element to get the measured size for * @return the measured outer height (including margins, paddings and * borders) of the element in pixels. */ public final int getOuterHeight(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return (int) Math .ceil(getMeasuredSize(element, nullSize).getOuterHeight()); } /** * Gets the outer height (including margins, paddings and borders) of the * given element, provided that it has been measured. These elements are * guaranteed to be measured: *

    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * -1 is returned if the element has not been measured. If 0 is returned, it * might indicate that the element is not attached to the DOM. * * @since 7.5.1 * @param element * the element to get the measured size for * @return the measured outer height (including margins, paddings and * borders) of the element in pixels. */ public final double getOuterHeightDouble(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getOuterHeight(); } /** * Gets the outer width (including margins, paddings and borders) of the * given element, provided that it has been measured. These elements are * guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * -1 is returned if the element has not been measured. If 0 is returned, it * might indicate that the element is not attached to the DOM. *

* The value returned by this method is always rounded up. To get the exact * outer width, use {@link #getOuterWidthDouble(Element)} * * @since 7.5.1 * @param element * the element to get the measured size for * @return the measured outer width (including margins, paddings and * borders) of the element in pixels. */ public final int getOuterWidth(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return (int) Math .ceil(getMeasuredSize(element, nullSize).getOuterWidth()); } /** * Gets the outer width (including margins, paddings and borders) of the * given element, provided that it has been measured. These elements are * guaranteed to be measured: *

    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * -1 is returned if the element has not been measured. If 0 is returned, it * might indicate that the element is not attached to the DOM. * * @param element * the element to get the measured size for * @return the measured outer width (including margins, paddings and * borders) of the element in pixels. */ public final double getOuterWidthDouble(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getOuterWidth(); } /** * Gets the inner height (excluding margins, paddings and borders) of the * given element, provided that it has been measured. These elements are * guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * -1 is returned if the element has not been measured. If 0 is returned, it * might indicate that the element is not attached to the DOM. *

* The value returned by this method is always rounded up. To get the exact * outer width, use {@link #getInnerHeightDouble(Element)} * * @param element * the element to get the measured size for * @return the measured inner height (excluding margins, paddings and * borders) of the element in pixels. */ public final int getInnerHeight(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return (int) Math .ceil(getMeasuredSize(element, nullSize).getInnerHeight()); } /** * Gets the inner height (excluding margins, paddings and borders) of the * given element, provided that it has been measured. These elements are * guaranteed to be measured: *

    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * -1 is returned if the element has not been measured. If 0 is returned, it * might indicate that the element is not attached to the DOM. * * @since 7.5.1 * @param element * the element to get the measured size for * @return the measured inner height (excluding margins, paddings and * borders) of the element in pixels. */ public final double getInnerHeightDouble(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getInnerHeight(); } /** * Gets the inner width (excluding margins, paddings and borders) of the * given element, provided that it has been measured. These elements are * guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * -1 is returned if the element has not been measured. If 0 is returned, it * might indicate that the element is not attached to the DOM. *

* The value returned by this method is always rounded up. To get the exact * outer width, use {@link #getOuterHeightDouble(Element)} * * @param element * the element to get the measured size for * @return the measured inner width (excluding margins, paddings and * borders) of the element in pixels. */ public final int getInnerWidth(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return (int) Math .ceil(getMeasuredSize(element, nullSize).getInnerWidth()); } /** * Gets the inner width (excluding margins, paddings and borders) of the * given element, provided that it has been measured. These elements are * guaranteed to be measured: *

    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * -1 is returned if the element has not been measured. If 0 is returned, it * might indicate that the element is not attached to the DOM. * * @since 7.5.1 * @param element * the element to get the measured size for * @return the measured inner width (excluding margins, paddings and * borders) of the element in pixels. */ public final double getInnerWidthDouble(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getInnerWidth(); } /** * Gets the border height (top border + bottom border) of the given element, * provided that it has been measured. These elements are guaranteed to be * measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured border height (top border + bottom border) of the * element in pixels. */ public final int getBorderHeight(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getBorderHeight(); } /** * Gets the padding height (top padding + bottom padding) of the given * element, provided that it has been measured. These elements are * guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured padding height (top padding + bottom padding) of the * element in pixels. */ public int getPaddingHeight(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getPaddingHeight(); } /** * Gets the border width (left border + right border) of the given element, * provided that it has been measured. These elements are guaranteed to be * measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured border width (left border + right border) of the * element in pixels. */ public int getBorderWidth(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getBorderWidth(); } /** * Gets the top border of the given element, provided that it has been * measured. These elements are guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured top border of the element in pixels. */ public int getBorderTop(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getBorderTop(); } /** * Gets the left border of the given element, provided that it has been * measured. These elements are guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured left border of the element in pixels. */ public int getBorderLeft(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getBorderLeft(); } /** * Gets the bottom border of the given element, provided that it has been * measured. These elements are guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured bottom border of the element in pixels. */ public int getBorderBottom(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getBorderBottom(); } /** * Gets the right border of the given element, provided that it has been * measured. These elements are guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured right border of the element in pixels. */ public int getBorderRight(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getBorderRight(); } /** * Gets the padding width (left padding + right padding) of the given * element, provided that it has been measured. These elements are * guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured padding width (left padding + right padding) of the * element in pixels. */ public int getPaddingWidth(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getPaddingWidth(); } /** * Gets the top padding of the given element, provided that it has been * measured. These elements are guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured top padding of the element in pixels. */ public int getPaddingTop(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getPaddingTop(); } /** * Gets the left padding of the given element, provided that it has been * measured. These elements are guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured left padding of the element in pixels. */ public int getPaddingLeft(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getPaddingLeft(); } /** * Gets the bottom padding of the given element, provided that it has been * measured. These elements are guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured bottom padding of the element in pixels. */ public int getPaddingBottom(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getPaddingBottom(); } /** * Gets the right padding of the given element, provided that it has been * measured. These elements are guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured right padding of the element in pixels. */ public int getPaddingRight(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getPaddingRight(); } /** * Gets the top margin of the given element, provided that it has been * measured. These elements are guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured top margin of the element in pixels. */ public int getMarginTop(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getMarginTop(); } /** * Gets the right margin of the given element, provided that it has been * measured. These elements are guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured right margin of the element in pixels. */ public int getMarginRight(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getMarginRight(); } /** * Gets the bottom margin of the given element, provided that it has been * measured. These elements are guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured bottom margin of the element in pixels. */ public int getMarginBottom(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getMarginBottom(); } /** * Gets the left margin of the given element, provided that it has been * measured. These elements are guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured size for * @return the measured left margin of the element in pixels. */ public int getMarginLeft(Element element) { assert needsMeasure( element) : "Getting measurement for element that is not measured"; return getMeasuredSize(element, nullSize).getMarginLeft(); } /** * Gets the combined top & bottom margin of the given element, provided that * they have been measured. These elements are guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured margin for * @return the measured top+bottom margin of the element in pixels. */ public int getMarginHeight(Element element) { return getMarginTop(element) + getMarginBottom(element); } /** * Gets the combined left & right margin of the given element, provided that * they have been measured. These elements are guaranteed to be measured: *
    *
  • ManagedLayouts and their child Connectors *
  • Elements for which there is at least one ElementResizeListener *
  • Elements for which at least one ManagedLayout has registered a * dependency *
* * A negative number is returned if the element has not been measured. If 0 * is returned, it might indicate that the element is not attached to the * DOM. * * @param element * the element to get the measured margin for * @return the measured left+right margin of the element in pixels. */ public int getMarginWidth(Element element) { return getMarginLeft(element) + getMarginRight(element); } /** * Registers the outer height (including margins, borders and paddings) of a * component. This can be used as an optimization by ManagedLayouts; by * informing the LayoutManager about what size a component will have, the * layout propagation can continue directly without first measuring the * potentially resized elements. * * @param component * the component for which the size is reported * @param outerHeight * the new outer height (including margins, borders and paddings) * of the component in pixels */ public void reportOuterHeight(ComponentConnector component, int outerHeight) { Element element = component.getWidget().getElement(); MeasuredSize measuredSize = getMeasuredSize(element); if (isLayoutRunning()) { boolean heightChanged = measuredSize.setOuterHeight(outerHeight); if (heightChanged) { onConnectorChange(component, false, true); notifyListenersAndDepdendents(element, false, true); } currentDependencyTree.setNeedsVerticalMeasure(component, false); } else if (measuredSize.getOuterHeight() != outerHeight) { setNeedsMeasure(component); } } /** * Registers the height reserved for a relatively sized component. This can * be used as an optimization by ManagedLayouts; by informing the * LayoutManager about what size a component will have, the layout * propagation can continue directly without first measuring the potentially * resized elements. * * @param component * the relatively sized component for which the size is reported * @param assignedHeight * the inner height of the relatively sized component's parent * element in pixels */ public void reportHeightAssignedToRelative(ComponentConnector component, int assignedHeight) { assert component.isRelativeHeight(); float percentSize = parsePercent(component.getState().height == null ? "" : component.getState().height); int effectiveHeight = Math.round(assignedHeight * (percentSize / 100)); reportOuterHeight(component, effectiveHeight); } /** * Registers the width reserved for a relatively sized component. This can * be used as an optimization by ManagedLayouts; by informing the * LayoutManager about what size a component will have, the layout * propagation can continue directly without first measuring the potentially * resized elements. * * @param component * the relatively sized component for which the size is reported * @param assignedWidth * the inner width of the relatively sized component's parent * element in pixels */ public void reportWidthAssignedToRelative(ComponentConnector component, int assignedWidth) { assert component.isRelativeWidth(); float percentSize = parsePercent(component.getState().width == null ? "" : component.getState().width); int effectiveWidth = Math.round(assignedWidth * (percentSize / 100)); reportOuterWidth(component, effectiveWidth); } private static float parsePercent(String size) { return Float.parseFloat(size.substring(0, size.length() - 1)); } /** * Registers the outer width (including margins, borders and paddings) of a * component. This can be used as an optimization by ManagedLayouts; by * informing the LayoutManager about what size a component will have, the * layout propagation can continue directly without first measuring the * potentially resized elements. * * @param component * the component for which the size is reported * @param outerWidth * the new outer width (including margins, borders and paddings) * of the component in pixels */ public void reportOuterWidth(ComponentConnector component, int outerWidth) { Element element = component.getWidget().getElement(); MeasuredSize measuredSize = getMeasuredSize(element); if (isLayoutRunning()) { boolean widthChanged = measuredSize.setOuterWidth(outerWidth); if (widthChanged) { onConnectorChange(component, true, false); notifyListenersAndDepdendents(element, true, false); } currentDependencyTree.setNeedsHorizontalMeasure(component, false); } else if (measuredSize.getOuterWidth() != outerWidth) { setNeedsMeasure(component); } } /** * Adds a listener that will be notified whenever the size of a specific * element changes. Adding a listener to an element also ensures that all * sizes for that element will be available starting from the next layout * phase. * * @param element * the element that should be checked for size changes * @param listener * an ElementResizeListener that will be informed whenever the * size of the target element has changed */ public void addElementResizeListener(Element element, ElementResizeListener listener) { Collection listeners = elementResizeListeners .get(element); if (listeners == null) { listeners = new HashSet<>(); elementResizeListeners.put(element, listeners); ensureMeasured(element); } listeners.add(listener); } /** * Removes an element resize listener from the provided element. This might * cause this LayoutManager to stop tracking the size of the element if no * other sources are interested in the size. * * @param element * the element to which the element resize listener was * previously added * @param listener * the ElementResizeListener that should no longer get informed * about size changes to the target element. */ public void removeElementResizeListener(Element element, ElementResizeListener listener) { Collection listeners = elementResizeListeners .get(element); if (listeners != null) { listeners.remove(listener); if (listeners.isEmpty()) { elementResizeListeners.remove(element); stopMeasuringIfUnecessary(element); } } } private void stopMeasuringIfUnecessary(Element element) { if (!needsMeasure(element)) { measuredNonConnectorElements.remove(element); setMeasuredSize(element, null); } } /** * Informs this LayoutManager that the size of a component might have * changed. This method should be used whenever the size of an individual * component might have changed from outside of Vaadin's normal update * phase, e.g. when an icon has been loaded or when the user resizes some * part of the UI using the mouse. *

* To set an entire component hierarchy to be measured, use * {@link #setNeedsMeasureRecursively(ComponentConnector)} instead. *

* If there is no upcoming layout phase, a new layout phase is scheduled. * * @param component * the component whose size might have changed. */ public void setNeedsMeasure(ComponentConnector component) { if (isLayoutRunning()) { currentDependencyTree.setNeedsMeasure(component, true); } else { needsMeasure.add(component.getConnectorId()); layoutLater(); } } /** * Informs this LayoutManager that some sizes in a component hierarchy might * have changed. This method should be used whenever the size of any child * component might have changed from outside of Vaadin's normal update * phase, e.g. when a CSS class name related to sizing has been changed. *

* To set a single component to be measured, use * {@link #setNeedsMeasure(ComponentConnector)} instead. *

* If there is no upcoming layout phase, a new layout phase is scheduled. * * @since 7.2 * @param component * the component at the root of the component hierarchy to * measure */ public void setNeedsMeasureRecursively(ComponentConnector component) { setNeedsMeasure(component); if (component instanceof HasComponentsConnector) { HasComponentsConnector hasComponents = (HasComponentsConnector) component; for (ComponentConnector child : hasComponents .getChildComponents()) { setNeedsMeasureRecursively(child); } } } public void setEverythingNeedsMeasure() { everythingNeedsMeasure = true; } private static Logger getLogger() { return Logger.getLogger(LayoutManager.class.getName()); } /** * Checks if there is something waiting for a layout to take place. * * @since 7.5.6 * @return true if there are connectors waiting for measurement or layout, * false otherwise */ public boolean isLayoutNeeded() { if (!needsHorizontalLayout.isEmpty() || !needsVerticalLayout.isEmpty()) { return true; } if (!needsMeasure.isEmpty()) { return true; } if (everythingNeedsMeasure) { return true; } return false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy