com.vaadin.client.debug.internal.VDebugWindow.java Source code

Java tutorial

Introduction

Here is the source code for com.vaadin.client.debug.internal.VDebugWindow.java

Source

/*
 * Copyright 2000-2018 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.debug.internal;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Cursor;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseEvent;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.http.client.UrlBuilder;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.storage.client.Storage;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.Location;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.ValueMap;
import com.vaadin.client.ui.VOverlay;

/**
 * Debug window implementation.
 *
 * @since 7.1
 * @author Vaadin Ltd
 */
public final class VDebugWindow extends VOverlay {

    // CSS classes
    static final String STYLENAME = "v-debugwindow";
    static final String STYLENAME_BUTTON = STYLENAME + "-button";
    static final String STYLENAME_ACTIVE = "active";

    protected static final String STYLENAME_HEAD = STYLENAME + "-head";
    protected static final String STYLENAME_TABS = STYLENAME + "-tabs";
    protected static final String STYLENAME_TAB = STYLENAME + "-tab";
    protected static final String STYLENAME_CONTROLS = STYLENAME + "-controls";
    protected static final String STYLENAME_SECTION_HEAD = STYLENAME + "-section-head";
    protected static final String STYLENAME_CONTENT = STYLENAME + "-content";
    protected static final String STYLENAME_SELECTED = "selected";

    // drag this far before actually moving window
    protected static final int MOVE_TRESHOLD = 5;

    // window minimum height, minimum width comes from tab+controls
    protected static final int MIN_HEIGHT = 40;

    // size of area to grab for resize; bottom corners size in CSS
    protected static final int HANDLE_SIZE = 7;

    // identifiers for localStorage
    private static final String STORAGE_PREFIX = "v-debug-";
    private static final String STORAGE_FULL_X = "x";
    private static final String STORAGE_FULL_Y = "y";
    private static final String STORAGE_FULL_W = "w";
    private static final String STORAGE_FULL_H = "h";
    private static final String STORAGE_MIN_X = "mx";
    private static final String STORAGE_MIN_Y = "my";
    private static final String STORAGE_ACTIVE_SECTION = "t";
    private static final String STORAGE_IS_MINIMIZED = "m";
    private static final String STORAGE_FONT_SIZE = "s";

    // state, these are persisted
    protected Section activeSection;
    protected boolean minimized = false;
    protected int fullX = -10;
    protected int fullY = -10;
    protected int fullW = 300;
    protected int fullH = 150;
    protected int minX = -10;
    protected int minY = 10;
    protected int fontSize = 1; // 0-2

    // Timers since application start, and last timer reset
    private static final Duration START = new Duration();
    private static Duration lastReset = null;

    // outer panel
    protected FlowPanel window = new FlowPanel();
    // top (tabs + controls)
    protected FlowPanel head = new FlowPanel();
    protected FlowPanel tabs = new FlowPanel();
    protected FlowPanel controls = new FlowPanel();
    protected Button minimize = new DebugButton(Icon.MINIMIZE, "Minimize");
    protected Button menu = new DebugButton(Icon.MENU, "Menu");
    protected Button close = new DebugButton(Icon.CLOSE, "Close");

    // menu
    protected Menu menuPopup = new Menu();

    // section specific area
    protected FlowPanel sectionHead = new FlowPanel();
    // content wrapper
    protected SimplePanel content = new SimplePanel();

    // sections
    protected List<Section> sections = new ArrayList<>();

    // handles resize/move
    protected HandlerRegistration mouseDownHandler = null;
    protected HandlerRegistration mouseMoveHandler = null;

    // TODO this class should really be a singleton.
    static VDebugWindow instance;

    /**
     * This class should only be instantiated by the framework, use
     * {@link #get()} instead to get the singleton instance.
     * <p>
     * {@link VDebugWindow} provides windowing functionality and shows
     * {@link Section}s added with {@link #addSection(Section)} as tabs.
     * </p>
     * <p>
     * {@link Section#getTabButton()} is called to obtain a unique id for the
     * Sections; the id should actually be an identifier for an icon in the
     * icon-font in use.
     * </p>
     * <p>
     * {@link Section#getControls()} and {@link Section#getContent()} is called
     * when the Section is activated (displayed). Additionally
     * {@link Section#show()} is called to allow the Section to initialize
     * itself as needed when shown. Conversely {@link Section#hide()} is called
     * when the Section is deactivated.
     * </p>
     * <p>
     * Sections should take care to prefix CSS classnames used with
     * {@link VDebugWindow}.{@link #STYLENAME} to avoid that application theme
     * interferes with the debug window content.
     * </p>
     * <p>
     * Some of the window state, such as position and size, is persisted to
     * localStorage. Sections can use
     * {@link #writeState(Storage, String, Object)} and
     * {@link #readState(Storage, String, String)} (and relatives) to write and
     * read own persisted settings, keys will automatically be prefixed with
     * {@value #STORAGE_PREFIX}.
     * </p>
     */
    public VDebugWindow() {
        super(false, false);
        getElement().getStyle().setOverflow(Overflow.HIDDEN);
        setStylePrimaryName(STYLENAME);

        setWidget(window);
        window.add(head);
        head.add(tabs);
        head.add(controls);
        head.add(sectionHead);
        window.add(content);

        addHandles();

        head.setStylePrimaryName(STYLENAME_HEAD);
        tabs.setStylePrimaryName(STYLENAME_TABS);
        controls.setStylePrimaryName(STYLENAME_CONTROLS);
        sectionHead.setStylePrimaryName(STYLENAME_SECTION_HEAD);
        content.setStylePrimaryName(STYLENAME_CONTENT);

        // add controls TODO move these
        controls.add(menu);
        menu.addClickHandler(event -> menuPopup.showRelativeTo(menu));

        controls.add(minimize);
        minimize.addClickHandler(event -> {
            toggleMinimized();
            writeStoredState();
        });
        controls.add(close);
        close.addClickHandler(event -> close());

        Style s = content.getElement().getStyle();
        s.setOverflow(Overflow.AUTO);

        // move/resize
        final MouseHandler mouseHandler = new MouseHandler();
        mouseDownHandler = this.addDomHandler(mouseHandler, MouseDownEvent.getType());
        mouseMoveHandler = this.addDomHandler(mouseHandler, MouseMoveEvent.getType());

    }

    /**
     * Adds dummy handle elements to the bottom corners that might have
     * scrollbars that interfere with resizing on some platforms.
     *
     * @since 7.1
     */
    private void addHandles() {
        Element el = DOM.createDiv();
        el.setClassName(VDebugWindow.STYLENAME + "-handle " + VDebugWindow.STYLENAME + "-handle-sw");
        content.getElement().appendChild(el);

        el = DOM.createDiv();
        el.setClassName(VDebugWindow.STYLENAME + "-handle " + VDebugWindow.STYLENAME + "-handle-se");
        content.getElement().appendChild(el);
    }

    /**
     * Gets the {@link #VDebugWindow()} singleton instance.
     *
     * @return
     */
    public static VDebugWindow get() {
        if (instance == null) {
            instance = GWT.create(VDebugWindow.class);
        }
        return instance;
    }

    /**
     * Closes the window and stops visual logging.
     */
    public void close() {
        // TODO disable even more
        if (mouseDownHandler != null) {
            mouseDownHandler.removeHandler();
            mouseMoveHandler.removeHandler();
            mouseDownHandler = null;
            mouseMoveHandler = null;
        }
        Highlight.hideAll();
        hide();

    }

    boolean isClosed() {
        return !isShowing();
    }

    /**
     * Reads the stored state from localStorage.
     */
    private void readStoredState() {
        Storage storage = Storage.getLocalStorageIfSupported();
        if (storage == null) {
            return;
        }

        fullX = readState(storage, STORAGE_FULL_X, -510);
        fullY = readState(storage, STORAGE_FULL_Y, -230);
        fullW = readState(storage, STORAGE_FULL_W, 500);
        fullH = readState(storage, STORAGE_FULL_H, 150);
        minX = readState(storage, STORAGE_MIN_X, -40);
        minY = readState(storage, STORAGE_MIN_Y, -70);
        setFontSize(readState(storage, STORAGE_FONT_SIZE, 1));

        activateSection(readState(storage, STORAGE_ACTIVE_SECTION, 0));

        setMinimized(readState(storage, STORAGE_IS_MINIMIZED, false));

        applyPositionAndSize();
    }

    /**
     * Writes the persistent state to localStorage.
     */
    private void writeStoredState() {
        if (isClosed()) {
            return;
        }
        Storage storage = Storage.getLocalStorageIfSupported();
        if (storage == null) {
            return;
        }

        writeState(storage, STORAGE_FULL_X, fullX);
        writeState(storage, STORAGE_FULL_Y, fullY);
        writeState(storage, STORAGE_FULL_W, fullW);
        writeState(storage, STORAGE_FULL_H, fullH);
        writeState(storage, STORAGE_MIN_X, minX);
        writeState(storage, STORAGE_MIN_Y, minY);
        writeState(storage, STORAGE_FONT_SIZE, fontSize);

        int activeIdx = getActiveSection();
        if (activeIdx >= 0) {
            writeState(storage, STORAGE_ACTIVE_SECTION, activeIdx);
        }

        writeState(storage, STORAGE_IS_MINIMIZED, minimized);
    }

    /**
     * Writes the given value to the given {@link Storage} using the given key
     * (automatically prefixed with {@value #STORAGE_PREFIX}).
     *
     * @param storage
     * @param key
     * @param value
     */
    static void writeState(Storage storage, String key, Object value) {
        storage.setItem(STORAGE_PREFIX + key, String.valueOf(value));
    }

    /**
     * Returns the item with the given key (automatically prefixed with
     * {@value #STORAGE_PREFIX}) as an int from the given {@link Storage},
     * returning the given default value instead if not successful (e.g missing
     * item).
     *
     * @param storage
     * @param key
     * @param def
     * @return stored or default value
     */
    static int readState(Storage storage, String key, int def) {
        try {
            return Integer.parseInt(storage.getItem(STORAGE_PREFIX + key));
        } catch (Exception e) {
            return def;
        }
    }

    /**
     * Returns the item with the given key (automatically prefixed with
     * {@value #STORAGE_PREFIX}) as a boolean from the given {@link Storage},
     * returning the given default value instead if not successful (e.g missing
     * item).
     *
     * @param storage
     * @param key
     * @param def
     * @return stored or default value
     */
    static boolean readState(Storage storage, String key, boolean def) {
        try {
            return Boolean.parseBoolean(storage.getItem(STORAGE_PREFIX + key));
        } catch (Exception e) {
            return def;
        }
    }

    /**
     * Returns the item with the given key (automatically prefixed with
     * {@value #STORAGE_PREFIX}) as a String from the given {@link Storage},
     * returning the given default value instead if not successful (e.g missing
     * item).
     *
     * @param storage
     * @param key
     * @param def
     * @return stored or default value
     */
    static String readState(Storage storage, String key, String def) {
        String val = storage.getItem(STORAGE_PREFIX + key);
        return val != null ? val : def;
    }

    /**
     * Resets (clears) the stored state from localStorage.
     */
    private void resetStoredState() {
        Storage storage = Storage.getLocalStorageIfSupported();
        if (storage == null) {
            return;
        }
        // note: length is live
        for (int i = 0; i < storage.getLength();) {
            String key = storage.key(i);
            if (key.startsWith(STORAGE_PREFIX)) {
                removeState(storage, key.substring(STORAGE_PREFIX.length()));
            } else {
                i++;
            }
        }
    }

    /**
     * Removes the item with the given key (automatically prefixed with
     * {@value #STORAGE_PREFIX}) from the given {@link Storage}.
     *
     * @param storage
     * @param key
     */
    private void removeState(Storage storage, String key) {
        storage.removeItem(STORAGE_PREFIX + key);
    }

    /**
     * Applies the appropriate instance variables for width, height, x, y
     * depending on if the window is minimized or not.
     *
     * If the value is negative, the window is positioned that amount of pixels
     * from the right/bottom instead of left/top.
     *
     * Finally, the position is bounds-checked so that the window is not moved
     * off-screen (the adjusted values are not saved).
     */
    private void applyPositionAndSize() {
        int x = 0;
        int y = 0;
        if (minimized) {
            x = minX;
            if (minX < 0) {
                x = Window.getClientWidth() + minX;
            }
            y = minY;
            if (minY < 0) {
                y = Window.getClientHeight() + minY;
            }

        } else {
            x = fullX;
            if (fullX < 0) {
                x = Window.getClientWidth() + fullX;
            }
            y = fullY;
            if (y < 0) {
                y = Window.getClientHeight() + fullY;
            }
            content.setWidth(fullW + "px");
            content.setHeight(fullH + "px");
        }

        applyBounds(x, y);
    }

    private void applyBounds() {
        int x = getPopupLeft();
        int y = getPopupTop();
        applyBounds(x, y);
    }

    private void applyBounds(int x, int y) {
        // bounds check
        if (x < 0) {
            x = 0;
        }
        if (x > Window.getClientWidth() - getOffsetWidth()) {
            // not allowed off-screen to the right
            x = Window.getClientWidth() - getOffsetWidth();
        }
        if (y > Window.getClientHeight() - getOffsetHeight()) {
            y = Window.getClientHeight() - getOffsetHeight();
        }
        if (y < 0) {
            y = 0;
        }

        setPopupPosition(x, y);

    }

    /**
     * Reads position and size from the DOM to local variables (which in turn
     * can be stored to localStorage)
     */
    private void readPositionAndSize() {
        int x = getPopupLeft();
        int fromRight = Window.getClientWidth() - x - getOffsetWidth();
        if (fromRight < x) {
            x -= Window.getClientWidth();
        }

        int y = getPopupTop();
        int fromBottom = Window.getClientHeight() - y - getOffsetHeight();
        if (fromBottom < y) {
            y -= Window.getClientHeight();
        }

        if (minimized) {
            minY = y;
            minX = x;
        } else {
            fullY = y;
            fullX = x;
            fullW = content.getOffsetWidth();
            fullH = content.getOffsetHeight();
        }

    }

    /**
     * Adds the given {@link Section} as a tab in the {@link VDebugWindow} UI.
     * {@link Section#getTabButton()} is called to obtain a button which is used
     * tab.
     *
     * @param section
     */
    public void addSection(final Section section) {
        Button b = section.getTabButton();
        b.addClickHandler(event -> {
            activateSection(section);
            writeStoredState();
        });
        b.setStylePrimaryName(STYLENAME_TAB);
        tabs.add(b);
        sections.add(section);

        if (activeSection == null) {
            activateSection(section);
        }
    }

    /**
     * Activates the given {@link Section}
     *
     * @param section
     */
    void activateSection(Section section) {
        if (section != null && section != activeSection) {
            Highlight.hideAll();
            // remove old stuff
            if (activeSection != null) {
                activeSection.hide();
                content.remove(activeSection.getContent());
                sectionHead.remove(activeSection.getControls());
            }
            // update tab styles
            for (int i = 0; i < tabs.getWidgetCount(); i++) {
                Widget tab = tabs.getWidget(i);
                tab.setStyleDependentName(STYLENAME_SELECTED, tab == section.getTabButton());
            }
            // add new stuff
            content.add(section.getContent());
            sectionHead.add(section.getControls());
            activeSection = section;
            activeSection.show();
        }
    }

    void activateSection(int n) {
        if (n < sections.size()) {
            activateSection(sections.get(n));
        }
    }

    int getActiveSection() {
        return sections.indexOf(activeSection);
    }

    /**
     * Toggles the window between minimized and full states.
     */
    private void toggleMinimized() {
        setMinimized(!minimized);
        writeStoredState();
    }

    /**
     * Sets whether or not the window is minimized.
     *
     * @param minimized
     */
    private void setMinimized(boolean minimized) {
        this.minimized = minimized;

        tabs.setVisible(!minimized);
        content.setVisible(!minimized);
        sectionHead.setVisible(!minimized);
        menu.setVisible(!minimized);

        applyPositionAndSize();
    }

    /**
     * Sets the font size in use.
     *
     * @param size
     */
    private void setFontSize(int size) {
        removeStyleDependentName("size" + fontSize);
        fontSize = size;
        addStyleDependentName("size" + size);
    }

    /**
     * Gets the font size currently in use.
     *
     * @return
     */
    private int getFontSize() {
        return fontSize;
    }

    /**
     * Gets the milliseconds since application start.
     *
     * @return
     */
    static int getMillisSinceStart() {
        return START.elapsedMillis();
    }

    /**
     * Gets the milliseconds since last {@link #resetTimer()} call.
     *
     * @return
     */
    static int getMillisSinceReset() {
        if (lastReset == null) {
            lastReset = new Duration();
        }

        return lastReset.elapsedMillis();
    }

    /**
     * Resets the timer.
     *
     * @return Milliseconds elapsed since the timer was last reset.
     */
    static int resetTimer() {
        int sinceLast = lastReset.elapsedMillis();
        lastReset = null;
        return sinceLast;
    }

    /**
     * Gets a nicely formatted string with timing information suitable for
     * display in tooltips.
     *
     * @param sinceStart
     * @param sinceReset
     * @return
     */
    static String getTimingTooltip(int sinceStart, int sinceReset) {
        String title = formatDuration(sinceStart) + " since start";
        title += ", &#10; " + formatDuration(sinceReset) + " since timer reset";
        title += " &#10; @ " + DateTimeFormat.getFormat("HH:mm:ss.SSS").format(new Date());
        return title;
    }

    /**
     * Formats the given milliseconds as hours, minutes, seconds and
     * milliseconds.
     *
     * @param ms
     * @return
     */
    static String formatDuration(int ms) {
        NumberFormat fmt = NumberFormat.getFormat("00");
        String seconds = fmt.format((ms / 1000) % 60);
        String minutes = fmt.format((ms / (1000 * 60)) % 60);
        String hours = fmt.format((ms / (1000 * 60 * 60)) % 24);

        String millis = NumberFormat.getFormat("000").format(ms % 1000);

        return hours + "h " + minutes + "m " + seconds + "s " + millis + "ms";
    }

    /**
     * Called when the window is initialized.
     */
    public void init() {

        show();

        /*
         * Finalize initialization when all entry points have had the chance to
         * e.g. register new sections.
         */
        Scheduler.get().scheduleFinally(() -> {
            readStoredState();

            Window.addResizeHandler(new com.google.gwt.event.logical.shared.ResizeHandler() {

                Timer t = new Timer() {
                    @Override
                    public void run() {
                        applyPositionAndSize();
                    }
                };

                @Override
                public void onResize(ResizeEvent event) {
                    t.cancel();
                    // TODO less
                    t.schedule(1000);
                }
            });
        });
    }

    /**
     * Called when the result from analyzeLayouts is received.
     *
     * @param ac
     * @param meta
     */
    public void meta(ApplicationConnection ac, ValueMap meta) {
        if (isClosed()) {
            return;
        }
        for (Section s : sections) {
            s.meta(ac, meta);
        }
    }

    /**
     * Called when a response is received.
     *
     * @param ac
     * @param uidl
     */
    public void uidl(ApplicationConnection ac, ValueMap uidl) {
        if (isClosed()) {
            return;
        }
        for (Section s : sections) {
            s.uidl(ac, uidl);
        }
    }

    /**
     * Gets the container element for this window. The debug window is always
     * global to the document and not related to any
     * {@link ApplicationConnection} in particular.
     *
     * @return The global overlay container element.
     */
    @Override
    public com.google.gwt.user.client.Element getOverlayContainer() {
        return RootPanel.get().getElement();
    }

    /*
     * Inner classes
     */

    /**
     * Popup menu for {@link VDebugWindow}.
     *
     * @since 7.1
     * @author Vaadin Ltd
     */
    protected class Menu extends VOverlay {
        FlowPanel content = new FlowPanel();

        DebugButton[] sizes = { new DebugButton(null, "Small", "A"), new DebugButton(null, "Medium", "A"),
                new DebugButton(null, "Large", "A") };

        DebugButton[] modes = { new DebugButton(Icon.DEVMODE_OFF, "Debug only (causes page reload)"),
                new DebugButton(Icon.DEVMODE_ON, "DevMode (causes page reload)"),
                new DebugButton(Icon.DEVMODE_SUPER, "SuperDevMode (causes page reload)") };

        Menu() {
            super(true, true);
            setWidget(content);

            setStylePrimaryName(STYLENAME + "-menu");
            content.setStylePrimaryName(STYLENAME + "-menu-content");

            FlowPanel size = new FlowPanel();
            content.add(size);

            final ClickHandler sizeHandler = event -> {
                for (int i = 0; i < sizes.length; i++) {
                    Button b = sizes[i];
                    if (b == event.getSource()) {
                        setSize(i);
                    }
                }
                hide();
            };
            for (int i = 0; i < sizes.length; i++) {
                Button b = sizes[i];
                b.setStyleDependentName("size" + i, true);
                b.addClickHandler(sizeHandler);
                size.add(b);
            }

            FlowPanel mode = new FlowPanel();
            content.add(mode);
            final ClickHandler modeHandler = event -> {
                for (int i = 0; i < modes.length; i++) {
                    Button b = modes[i];
                    if (b == event.getSource()) {
                        setDevMode(i);
                    }
                }
                hide();
            };
            modes[getDevMode()].setActive(true);
            for (Button b : modes) {
                b.addClickHandler(modeHandler);
                mode.add(b);
            }

            Button reset = new DebugButton(Icon.RESET, "Restore defaults.", " Reset");
            reset.addClickHandler(event -> {
                resetStoredState();
                readStoredState();
                hide();
            });
            content.add(reset);
        }

        private void setSize(int size) {
            for (int i = 0; i < sizes.length; i++) {
                Button b = sizes[i];
                b.setStyleDependentName(STYLENAME_ACTIVE, i == size);
            }
            setFontSize(size);
            writeStoredState();
        }

        @Override
        public void show() {
            super.show();
            setSize(getFontSize());
        }

        private int getDevMode() {
            if (Location.getParameter("superdevmode") != null) {
                return 2;
            } else if (Location.getParameter("gwt.codesvr") != null) {
                return 1;
            } else {
                return 0;
            }
        }

        private void setDevMode(int mode) {
            UrlBuilder u = Location.createUrlBuilder();
            switch (mode) {
            case 2:
                u.setParameter("superdevmode", "");
                u.removeParameter("gwt.codesvr");
                break;
            case 1:
                u.setParameter("gwt.codesvr", "localhost:9997");
                u.removeParameter("superdevmode");
                break;
            default:
                u.removeParameter("gwt.codesvr");
                u.removeParameter("superdevmode");
            }
            Location.assign(u.buildString());
        }

    }

    /**
     * Handler for resizing and moving window, also updates cursor on mousemove.
     *
     * @since 7.1
     * @author Vaadin Ltd
     */
    protected class MouseHandler implements MouseMoveHandler, MouseDownHandler, NativePreviewHandler {

        boolean resizeLeft;
        boolean resizeRight;
        boolean resizeUp;
        boolean resizeDown;
        boolean move;
        boolean sizing;

        // dragging stopped, remove handler on next event
        boolean stop;

        HandlerRegistration dragHandler;

        int startX;
        int startY;
        int startW;
        int startH;
        int startTop;
        int startLeft;

        @Override
        public void onMouseMove(MouseMoveEvent event) {
            if (null == dragHandler) {
                updateResizeFlags(event);
                updateCursor();
            }
        }

        @Override
        public void onMouseDown(MouseDownEvent event) {
            if (event.getNativeButton() != NativeEvent.BUTTON_LEFT || dragHandler != null) {
                return;
            }
            updateResizeFlags(event);
            if (sizing || move) {
                // some os/browsers don't pass events trough scrollbars; hide
                // while dragging (esp. important for resize from right/bottom)
                content.getElement().getStyle().setOverflow(Overflow.HIDDEN);

                startX = event.getClientX();
                startY = event.getClientY();

                startW = content.getOffsetWidth();
                startH = content.getOffsetHeight();

                startTop = getPopupTop();
                startLeft = getPopupLeft();

                dragHandler = Event.addNativePreviewHandler(this);

                event.preventDefault();

                stop = false;
            }

        }

        @Override
        public void onPreviewNativeEvent(NativePreviewEvent event) {
            if (event.getTypeInt() == Event.ONMOUSEMOVE && !stop && hasMoved(event.getNativeEvent())) {

                int dx = event.getNativeEvent().getClientX() - startX;
                int dy = event.getNativeEvent().getClientY() - startY;

                if (sizing) {
                    int minWidth = tabs.getOffsetWidth() + controls.getOffsetWidth();

                    if (resizeLeft) {
                        int w = startW - dx;
                        if (w < minWidth) {
                            w = minWidth;
                            dx = startW - minWidth;
                        }
                        content.setWidth(w + "px");
                        setPopupPosition(startLeft + dx, getPopupTop());

                    } else if (resizeRight) {
                        int w = startW + dx;
                        if (w < minWidth) {
                            w = minWidth;
                        }
                        content.setWidth(w + "px");
                    }
                    if (resizeUp) {
                        int h = startH - dy;
                        if (h < MIN_HEIGHT) {
                            h = MIN_HEIGHT;
                            dy = startH - MIN_HEIGHT;
                        }
                        content.setHeight(h + "px");
                        setPopupPosition(getPopupLeft(), startTop + dy);

                    } else if (resizeDown) {
                        int h = startH + dy;
                        if (h < MIN_HEIGHT) {
                            h = MIN_HEIGHT;
                        }
                        content.setHeight(h + "px");

                    }

                } else if (move) {
                    setPopupPosition(startLeft + dx, startTop + dy);
                }
                event.cancel();

            } else if (event.getTypeInt() == Event.ONMOUSEUP) {
                stop = true;
                if (hasMoved(event.getNativeEvent())) {
                    event.cancel();
                }

            } else if (event.getTypeInt() == Event.ONCLICK) {
                stop = true;
                if (hasMoved(event.getNativeEvent())) {
                    event.cancel();
                }

            } else if (stop) {
                stop = false;
                dragHandler.removeHandler();
                dragHandler = null;
                sizing = false;
                move = false;

                // restore scrollbars
                content.getElement().getStyle().setOverflow(Overflow.AUTO);

                updateCursor();

                applyBounds();
                readPositionAndSize();
                writeStoredState();

                event.cancel();
            }

        }

        private boolean hasMoved(NativeEvent event) {
            return Math.abs(startX - event.getClientX()) > MOVE_TRESHOLD
                    || Math.abs(startY - event.getClientY()) > MOVE_TRESHOLD;
        }

        private void updateCursor() {
            Element c = getElement();
            if (resizeLeft) {
                if (resizeUp) {
                    c.getStyle().setCursor(Cursor.NW_RESIZE);
                } else if (resizeDown) {
                    c.getStyle().setCursor(Cursor.SW_RESIZE);
                } else {
                    c.getStyle().setCursor(Cursor.W_RESIZE);
                }
            } else if (resizeRight) {
                if (resizeUp) {
                    c.getStyle().setCursor(Cursor.NE_RESIZE);
                } else if (resizeDown) {
                    c.getStyle().setCursor(Cursor.SE_RESIZE);
                } else {
                    c.getStyle().setCursor(Cursor.E_RESIZE);
                }
            } else if (resizeUp) {
                c.getStyle().setCursor(Cursor.N_RESIZE);
            } else if (resizeDown) {
                c.getStyle().setCursor(Cursor.S_RESIZE);
            } else if (move) {
                c.getStyle().setCursor(Cursor.MOVE);
            } else {
                c.getStyle().setCursor(Cursor.AUTO);
            }
        }

        protected void updateResizeFlags(MouseEvent event) {
            if (event.isShiftKeyDown()) {
                // resize from lower right
                resizeUp = false;
                resizeLeft = false;
                resizeRight = true;
                resizeDown = true;
                move = false;
                sizing = true;
                return;

            } else if (event.isAltKeyDown()) {
                // move it
                move = true;
                resizeUp = false;
                resizeLeft = false;
                resizeRight = false;
                resizeDown = false;
                sizing = false;
                return;
            }

            Element c = getElement();
            int w = c.getOffsetWidth();
            int h = c.getOffsetHeight();
            int x = event.getRelativeX(c);
            int y = event.getRelativeY(c);

            resizeLeft = x < HANDLE_SIZE && y > tabs.getOffsetHeight();
            resizeRight = (x > (w - HANDLE_SIZE) && y > tabs.getOffsetHeight())
                    || (x > (w - 2 * HANDLE_SIZE) && y > (h - 2 * HANDLE_SIZE));
            resizeUp = y > tabs.getOffsetHeight() && y < tabs.getOffsetHeight() + HANDLE_SIZE;
            resizeDown = y > (h - HANDLE_SIZE) || (x > (w - 2 * HANDLE_SIZE) && y > (h - 2 * HANDLE_SIZE));

            move = !resizeDown && !resizeLeft && !resizeRight && !resizeUp && y < head.getOffsetHeight();

            sizing = resizeLeft || resizeRight || resizeUp || resizeDown;

        }

    }

}