com.haulmont.cuba.desktop.sys.DesktopWindowManager.java Source code

Java tutorial

Introduction

Here is the source code for com.haulmont.cuba.desktop.sys.DesktopWindowManager.java

Source

/*
 * Copyright (c) 2008-2016 Haulmont.
 *
 * 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.haulmont.cuba.desktop.sys;

import com.google.common.base.Strings;
import com.haulmont.bali.util.Dom4j;
import com.haulmont.bali.util.ParamsMap;
import com.haulmont.bali.util.Preconditions;
import com.haulmont.cuba.client.ClientConfig;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.core.sys.AppContext;
import com.haulmont.cuba.desktop.App;
import com.haulmont.cuba.desktop.DesktopConfig;
import com.haulmont.cuba.desktop.TopLevelFrame;
import com.haulmont.cuba.desktop.gui.components.DesktopAbstractComponent;
import com.haulmont.cuba.desktop.gui.components.DesktopComponentsHelper;
import com.haulmont.cuba.desktop.gui.components.DesktopWindow;
import com.haulmont.cuba.desktop.gui.icons.IconResolver;
import com.haulmont.cuba.desktop.sys.validation.ValidationAlertHolder;
import com.haulmont.cuba.desktop.sys.validation.ValidationAwareAction;
import com.haulmont.cuba.desktop.sys.validation.ValidationAwareWindowClosingListener;
import com.haulmont.cuba.gui.*;
import com.haulmont.cuba.gui.app.core.dev.LayoutAnalyzer;
import com.haulmont.cuba.gui.app.core.dev.LayoutTip;
import com.haulmont.cuba.gui.components.AbstractAction;
import com.haulmont.cuba.gui.components.Action;
import com.haulmont.cuba.gui.components.Action.Status;
import com.haulmont.cuba.gui.components.Component;
import com.haulmont.cuba.gui.components.*;
import com.haulmont.cuba.gui.components.DialogAction.Type;
import com.haulmont.cuba.gui.components.Frame;
import com.haulmont.cuba.gui.components.Frame.MessageMode;
import com.haulmont.cuba.gui.components.Window;
import com.haulmont.cuba.gui.config.WindowConfig;
import com.haulmont.cuba.gui.config.WindowInfo;
import com.haulmont.cuba.gui.executors.*;
import com.haulmont.cuba.gui.icons.Icons;
import com.haulmont.cuba.gui.logging.UserActionsLogger;
import com.haulmont.cuba.gui.settings.SettingsImpl;
import com.haulmont.cuba.security.global.NoUserSessionException;
import net.miginfocom.layout.LC;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.jdesktop.swingx.JXErrorPane;
import org.jdesktop.swingx.error.ErrorInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import javax.inject.Provider;
import javax.swing.*;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.List;

import static com.haulmont.cuba.gui.ComponentsHelper.preprocessHtmlMessage;
import static com.haulmont.cuba.gui.components.Component.AUTO_SIZE;
import static com.haulmont.cuba.gui.components.Component.AUTO_SIZE_PX;
import static com.haulmont.cuba.gui.components.Frame.MessageType;
import static com.haulmont.cuba.gui.components.Frame.NotificationType;
import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;

public class DesktopWindowManager extends WindowManager {

    private static final Logger log = LoggerFactory.getLogger(DesktopWindowManager.class);
    private Logger userActionsLog = LoggerFactory.getLogger(UserActionsLogger.class);

    protected static final float NEW_WINDOW_SCALE = 0.7f;

    protected JTabbedPane tabsPane;

    protected final Map<JComponent, WindowBreadCrumbs> tabs = new HashMap<>();
    protected final Map<Window, WindowOpenInfo> windowOpenMode = new LinkedHashMap<>();
    protected final Map<WindowBreadCrumbs, Stack<Map.Entry<Window, Integer>>> stacks = new HashMap<>();
    protected final Map<Window, Integer> windows = new HashMap<>();
    protected final TopLevelFrame frame;
    protected final boolean isMainWindowManager;

    protected boolean disableSavingScreenHistory;
    protected ScreenHistorySupport screenHistorySupport = new ScreenHistorySupport();

    protected boolean recursiveFramesClose = false;

    public DesktopWindowManager(TopLevelFrame frame) {
        this.frame = frame;
        isMainWindowManager = frame == App.getInstance().getMainFrame();
    }

    public TopLevelFrame getFrame() {
        return frame;
    }

    public void setTabsPane(final JTabbedPane tabsPane) {
        this.tabsPane = tabsPane;

        // todo move to config
        tabsPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("control W"),
                "closeTab");
        tabsPane.getActionMap().put("closeTab", new ValidationAwareAction() {
            @Override
            public void actionPerformedAfterValidation(ActionEvent e) {
                closeTab((JComponent) tabsPane.getSelectedComponent());
            }
        });
    }

    protected void closeTab(JComponent tabContent) {
        if (tabContent == null)
            return;
        WindowBreadCrumbs breadCrumbs = tabs.get(tabContent);
        // may be window already closed
        if (breadCrumbs != null) {
            Runnable closeTask = new TabCloseTask(breadCrumbs);
            closeTask.run();
        }
    }

    @Override
    public Collection<Window> getOpenWindows() {
        return new ArrayList<>(windowOpenMode.keySet());
    }

    @Override
    public void selectWindowTab(Window window) {
        if (isMainWindowManager) {
            WindowOpenInfo openInfo = windowOpenMode.get(window);
            if (openInfo != null) {
                OpenMode openMode = openInfo.getOpenMode();
                if (openMode == OpenMode.NEW_TAB || openMode == OpenMode.THIS_TAB) {
                    // show in tabsheet
                    JComponent layout = (JComponent) openInfo.getData();
                    tabsPane.setSelectedComponent(layout);
                }
            }
        }
    }

    @Override
    public void setWindowCaption(Window window, String caption, String description) {
        Window desktopWindow = window;
        if (window instanceof Window.Wrapper) {
            desktopWindow = ((Window.Wrapper) window).getWrappedWindow();
        }
        window.setCaption(caption);

        String formattedCaption = formatTabDescription(caption, description);
        WindowOpenInfo openInfo = windowOpenMode.get(desktopWindow);

        if (openInfo != null) {
            OpenMode openMode = openInfo.getOpenMode();

            if (openMode != OpenMode.DIALOG) {
                if (tabsPane != null) {
                    int selectedIndex = tabsPane.getSelectedIndex();
                    if (selectedIndex != -1) {
                        setActiveWindowCaption(caption, description, selectedIndex);
                    }
                } else if (!isMainWindowManager) {
                    setTopLevelWindowCaption(formattedCaption);
                }
            } else {
                JDialog jDialog = (JDialog) openInfo.getData();
                if (jDialog != null) {
                    jDialog.setTitle(formattedCaption);
                }
            }
        }
    }

    @Nullable
    public DialogWindow getLastDialogWindow() {
        List<Window> openedWindows = new ArrayList<>(windowOpenMode.keySet());
        if (openedWindows.size() > 0) {
            Window w = openedWindows.get(openedWindows.size() - 1);
            WindowOpenInfo mode = windowOpenMode.get(w);
            if (mode.getOpenMode() == OpenMode.DIALOG && mode.getData() instanceof DialogWindow) {
                return (DialogWindow) mode.getData();
            }
        }
        return null;
    }

    @Override
    protected void putToWindowMap(Window window, Integer hashCode) {
        if (window != null) {
            windows.put(window, hashCode);
        }
    }

    protected Integer getWindowHashCode(Window window) {
        return windows.get(window);
    }

    @Override
    protected Window getWindow(Integer hashCode) {
        Set<Map.Entry<Window, Integer>> set = windows.entrySet();
        for (Map.Entry<Window, Integer> entry : set) {
            if (hashCode.equals(entry.getValue())) {
                return entry.getKey();
            }
        }
        return null;
    }

    @Override
    protected void checkCanOpenWindow(WindowInfo windowInfo, OpenType openType, Map<String, Object> params) {
    }

    protected boolean hasModalWindow() {
        for (Map.Entry<Window, WindowOpenInfo> entry : windowOpenMode.entrySet()) {
            if (OpenMode.DIALOG == entry.getValue().getOpenMode()
                    && BooleanUtils.isTrue(entry.getKey().getDialogOptions().getModal())) {
                return true;
            }
        }
        return false;
    }

    @Override
    protected void showWindow(Window window, String caption, OpenType openType, boolean multipleOpen) {
        showWindow(window, caption, null, openType, multipleOpen);
    }

    @Override
    protected void showWindow(final Window window, final String caption, final String description,
            OpenType openType, boolean multipleOpen) {
        OpenType targetOpenType = openType.copy();

        // for backward compatibility only
        copyDialogParamsToOpenType(targetOpenType);

        overrideOpenTypeParams(targetOpenType, window.getDialogOptions());

        boolean forciblyDialog = false;
        boolean addWindowData = true;
        if (targetOpenType.getOpenMode() != OpenMode.DIALOG && hasModalWindow()) {
            targetOpenType.setOpenMode(OpenMode.DIALOG);
            forciblyDialog = true;
        }

        if (targetOpenType.getOpenMode() == OpenMode.THIS_TAB && tabs.size() == 0) {
            targetOpenType.setOpenMode(OpenMode.NEW_TAB);
        }

        window.setCaption(caption);
        window.setDescription(description);

        Object windowData;

        switch (targetOpenType.getOpenMode()) {
        case NEW_TAB:
            if (!isMainWindowManager) {
                addWindowData = false;
                showInMainWindowManager(window, caption, description, targetOpenType, multipleOpen);
                windowData = null;
            } else {
                Integer hashCode = getWindowHashCode(window);
                JComponent tab;
                if (hashCode != null && !multipleOpen && (tab = findTab(hashCode)) != null) {
                    WindowBreadCrumbs oldBreadCrumbs = tabs.get(tab);

                    final Window oldWindow = oldBreadCrumbs.getCurrentWindow();
                    selectWindowTab(((Window.Wrapper) oldBreadCrumbs.getCurrentWindow()).getWrappedWindow());

                    final int finalTabPosition = getTabPosition(tab);
                    oldWindow.closeAndRun(MAIN_MENU_ACTION_ID, new Runnable() {
                        @Override
                        public void run() {
                            showWindow(window, caption, description, OpenType.NEW_TAB, false);

                            Window wrappedWindow = window;
                            if (window instanceof Window.Wrapper) {
                                wrappedWindow = ((Window.Wrapper) window).getWrappedWindow();
                            }

                            if (finalTabPosition >= 0 && finalTabPosition < tabsPane.getComponentCount() - 1) {
                                moveWindowTab(wrappedWindow, finalTabPosition);
                            }
                        }
                    });
                    return;
                } else {
                    windowData = showWindowNewTab(window, caption, description, null);
                }
            }
            break;
        case THIS_TAB:
            windowData = showWindowThisTab(window, caption, description);
            break;
        case DIALOG:
            windowData = showWindowDialog(window, caption, description, targetOpenType, forciblyDialog);
            break;
        case NEW_WINDOW:
            addWindowData = false;
            windowData = showNewWindow(window, targetOpenType, caption);
            break;
        default:
            throw new UnsupportedOperationException();
        }

        if (addWindowData) {
            addWindowData(window, windowData, targetOpenType);
        }
        afterShowWindow(window);
    }

    protected int getTabPosition(JComponent tab) {
        int position = -1;
        for (int i = 0; i < tabsPane.getTabCount(); i++) {
            if (tab.equals(tabsPane.getComponentAt(i))) {
                position = i;
            }
        }
        return position;
    }

    /**
     * @param window   Window implementation (DesktopWindow)
     * @param position new tab position
     */
    protected void moveWindowTab(Window window, int position) {
        if (isMainWindowManager && position >= 0 && position < tabsPane.getComponentCount()) {
            WindowOpenInfo openInfo = windowOpenMode.get(window);
            if (openInfo != null) {
                OpenMode openMode = openInfo.getOpenMode();
                if (openMode == OpenMode.NEW_TAB || openMode == OpenMode.THIS_TAB) {
                    // show in tabsheet
                    JComponent layout = (JComponent) openInfo.getData();

                    int currentPosition = getTabPosition(layout);

                    String label = tabsPane.getTitleAt(currentPosition);
                    Icon icon = tabsPane.getIconAt(currentPosition);
                    Icon iconDis = tabsPane.getDisabledIconAt(currentPosition);
                    String tooltip = tabsPane.getToolTipTextAt(currentPosition);
                    boolean enabled = tabsPane.isEnabledAt(currentPosition);
                    int keycode = tabsPane.getMnemonicAt(currentPosition);
                    int mnemonicLoc = tabsPane.getDisplayedMnemonicIndexAt(currentPosition);
                    Color fg = tabsPane.getForegroundAt(currentPosition);
                    Color bg = tabsPane.getBackgroundAt(currentPosition);
                    java.awt.Component tabHeaderComponent = tabsPane.getTabComponentAt(currentPosition);

                    tabsPane.remove(layout);

                    tabsPane.insertTab(label, icon, layout, tooltip, position);

                    tabsPane.setDisabledIconAt(position, iconDis);
                    tabsPane.setEnabledAt(position, enabled);
                    tabsPane.setMnemonicAt(position, keycode);
                    tabsPane.setDisplayedMnemonicIndexAt(position, mnemonicLoc);
                    tabsPane.setForegroundAt(position, fg);
                    tabsPane.setBackgroundAt(position, bg);
                    tabsPane.setTabComponentAt(position, tabHeaderComponent);

                    tabsPane.setSelectedComponent(layout);
                }
            }
        }
    }

    protected void showInMainWindowManager(Window window, String caption, String description, OpenType openType,
            boolean multipleOpen) {
        DesktopWindowManager mainMgr = App.getInstance().getMainFrame().getWindowManager();
        windows.remove(window);
        window.setWindowManager(mainMgr);
        mainMgr.showWindow(window, caption, openType, multipleOpen);
    }

    protected TopLevelFrame createTopLevelFrame(String caption) {
        final TopLevelFrame windowFrame = new TopLevelFrame(caption);
        Dimension size = frame.getSize();
        int width = Math.round(size.width * NEW_WINDOW_SCALE);
        int height = Math.round(size.height * NEW_WINDOW_SCALE);

        windowFrame.setSize(width, height);
        windowFrame.setLocationRelativeTo(frame);

        return windowFrame;
    }

    protected void closeFrame(TopLevelFrame frame) {
        frame.setVisible(false);
        frame.dispose();
        frame.getWindowManager().dispose();
        App.getInstance().unregisterFrame(getFrame());
    }

    protected JComponent showNewWindow(Window window, OpenType openType, String caption) {
        window.setHeight("100%");
        window.setWidth("100%");

        TopLevelFrame windowFrame = createTopLevelFrame(caption);
        windowFrame.setName(window.getId());

        Dimension dimension = new Dimension();

        dimension.width = 800;
        if (openType.getWidth() != null) {
            dimension.width = openType.getWidth().intValue();
        }

        dimension.height = 500;
        if (openType.getHeight() != null) {
            dimension.height = openType.getHeight().intValue();
        }

        boolean resizable = true;
        if (openType.getResizable() != null) {
            resizable = openType.getResizable();
        }
        windowFrame.setResizable(resizable);
        windowFrame.setMinimumSize(dimension);
        windowFrame.pack();

        getDialogParams().reset();

        WindowBreadCrumbs breadCrumbs = createBreadCrumbs();
        breadCrumbs.addWindow(window);

        JComponent tabContent = createTabPanel(window, breadCrumbs);

        WindowOpenInfo openInfo = new WindowOpenInfo(window, OpenMode.NEW_WINDOW);
        openInfo.setData(tabContent);
        Map<Window, WindowOpenInfo> openInfos = new HashMap<>();
        if (window instanceof Window.Wrapper) {
            Window wrappedWindow = ((Window.Wrapper) window).getWrappedWindow();
            openInfos.put(wrappedWindow, openInfo);
        } else {
            openInfos.put(window, openInfo);
        }

        windowFrame.getWindowManager().attachTab(breadCrumbs, new Stack<>(), window, getWindowHashCode(window),
                tabContent, openInfos);

        App.getInstance().registerFrame(windowFrame);

        windowFrame.setVisible(true);
        return DesktopComponentsHelper.getComposition(window);
    }

    protected void addShortcuts(Window window, OpenType openType) {
        ClientConfig clientConfig = configuration.getConfig(ClientConfig.class);

        String closeShortcut = clientConfig.getCloseShortcut();
        window.addAction(
                new com.haulmont.cuba.gui.components.AbstractAction("closeWindowShortcutAction", closeShortcut) {
                    @Override
                    public void actionPerform(Component component) {
                        if (openType.getOpenMode() != OpenMode.DIALOG
                                || BooleanUtils.isNotFalse(window.getDialogOptions().getCloseable())) {
                            if (!isCloseWithShortcutPrevented(window)) {
                                window.close("close");
                            }
                        }
                    }
                });

        String previousTabShortcut = clientConfig.getPreviousTabShortcut();
        window.addAction(new com.haulmont.cuba.gui.components.AbstractAction("onPreviousTab", previousTabShortcut) {
            @Override
            public void actionPerform(Component component) {
                if (window.getWindowManager() != DesktopWindowManager.this) {
                    // detached tab
                    return;
                }

                if (isMainWindowManager && getLastDialogWindow() == null && tabsPane.getTabCount() > 1) {
                    int selectedIndex = getSelectedTabIndex();

                    int newIndex = (selectedIndex + tabsPane.getTabCount() - 1) % tabsPane.getTabCount();
                    java.awt.Component newTab = tabsPane.getComponentAt(newIndex);
                    tabsPane.setSelectedComponent(newTab);

                    moveFocus(newTab);
                }
            }
        });

        String nextTabShortcut = clientConfig.getNextTabShortcut();
        window.addAction(new com.haulmont.cuba.gui.components.AbstractAction("onNextTab", nextTabShortcut) {
            @Override
            public void actionPerform(Component component) {
                if (window.getWindowManager() != DesktopWindowManager.this) {
                    // detached tab
                    return;
                }

                if (isMainWindowManager && getLastDialogWindow() == null && tabsPane.getTabCount() > 1) {
                    int selectedIndex = getSelectedTabIndex();

                    int newIndex = (selectedIndex + 1) % tabsPane.getTabCount();
                    java.awt.Component newTab = tabsPane.getComponentAt(newIndex);
                    tabsPane.setSelectedComponent(newTab);

                    moveFocus(newTab);
                }
            }
        });
    }

    protected void moveFocus(java.awt.Component tab) {
        Window window = tabs.get(tab).getCurrentWindow();

        if (window != null) {
            String focusComponentId = window.getFocusComponent();

            boolean focused = false;
            if (focusComponentId != null) {
                com.haulmont.cuba.gui.components.Component focusComponent = window.getComponent(focusComponentId);
                if (focusComponent != null) {
                    if (focusComponent.isEnabled() && focusComponent.isVisible()) {
                        focusComponent.requestFocus();
                        focused = true;
                    }
                }
            }

            if (!focused && window instanceof Window.Wrapper) {
                Window.Wrapper wrapper = (Window.Wrapper) window;
                focused = ((DesktopWindow) wrapper.getWrappedWindow()).findAndFocusChildComponent();
                if (!focused) {
                    tabsPane.requestFocus();
                }
            }
        }
    }

    protected int getSelectedTabIndex() {
        java.awt.Component selectedComponent = tabsPane.getSelectedComponent();
        for (int i = 0; i < tabsPane.getTabCount(); i++) {
            if (selectedComponent == tabsPane.getComponentAt(i)) {
                return i;
            }
        }
        return -1;
    }

    protected JDialog showWindowDialog(final Window window, String caption, String description, OpenType openType,
            boolean forciblyDialog) {
        final DialogWindow dialog = new DialogWindow(frame, caption);
        dialog.setName(window.getId());
        dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

        JComponent jComponent = DesktopComponentsHelper.getComposition(window);
        dialog.add(jComponent);

        dialog.addWindowListener(new ValidationAwareWindowClosingListener() {
            @Override
            public void windowClosingAfterValidation(WindowEvent e) {
                if (BooleanUtils.isNotFalse(window.getDialogOptions().getCloseable())) {
                    if (!isCloseWithCloseButtonPrevented(window)) {
                        if (window.close("close")) {
                            dialog.dispose();
                        }
                    }
                }
            }
        });

        Dimension dim = new Dimension();
        if (forciblyDialog) {
            // todo move it to desktop application preferences
            dim.width = 800;
            dim.height = 500;

            dialog.setResizable(BooleanUtils.isNotFalse(openType.getResizable()));
            if (!dialog.isResizable()) {
                dialog.setFixedHeight(dim.height);
                dialog.setFixedWidth(dim.width);
            }

            window.setHeight("100%");
        } else {
            dialog.setResizable(BooleanUtils.isTrue(openType.getResizable()));
            if (openType.getWidth() == null) {
                dim.width = 600;
                if (!dialog.isResizable()) {
                    dialog.setFixedWidth(dim.width);
                }
            } else if (openType.getWidth() == DialogParams.AUTO_SIZE_PX) {
                window.setWidth(AUTO_SIZE);
            } else {
                if (openType.getWidthUnit() != null && openType.getWidthUnit() != SizeUnit.PIXELS) {
                    throw new UnsupportedOperationException("Dialog size can be set only in pixels");
                }
                dim.width = openType.getWidth().intValue();
                if (!dialog.isResizable()) {
                    dialog.setFixedWidth(dim.width);
                }
            }

            if (openType.getHeight() != null && openType.getHeight() != DialogParams.AUTO_SIZE_PX) {
                if (openType.getHeightUnit() != null && openType.getHeightUnit() != SizeUnit.PIXELS) {
                    throw new UnsupportedOperationException("Dialog size can be set only in pixels");
                }
                dim.height = openType.getHeight().intValue();
                if (!dialog.isResizable()) {
                    dialog.setFixedHeight(dim.height);
                }
                window.setHeight("100%");
            } else {
                window.setHeight(AUTO_SIZE);
            }
        }

        getDialogParams().reset();

        dialog.setMinimumSize(dim);
        dialog.pack();

        if (openType.getPositionY() == null && openType.getPositionX() == null) {
            dialog.setLocationRelativeTo(frame);
        } else {
            dialog.setLocation(openType.getPositionX() != null ? openType.getPositionX() : 0,
                    openType.getPositionY() != null ? openType.getPositionY() : 0);
        }

        boolean modal = true;
        if (!hasModalWindow() && openType.getModal() != null) {
            modal = openType.getModal();
        }

        if (modal) {
            DialogWindow lastDialogWindow = getLastDialogWindow();
            if (lastDialogWindow == null)
                frame.deactivate(null);
            else
                lastDialogWindow.disableWindow(null);

            dialog.setSoftModal(true);
        }

        dialog.setVisible(true);

        JPopupMenu popupMenu = createWindowPopupMenu(window);
        if (popupMenu.getComponentCount() > 0) {
            jComponent.setComponentPopupMenu(popupMenu);
        }

        return dialog;
    }

    protected JComponent showWindowThisTab(Window window, String caption, String description) {
        getDialogParams().reset();

        window.setWidth("100%");
        window.setHeight("100%");

        JComponent layout;
        if (isMainWindowManager) {
            layout = (JComponent) tabsPane.getSelectedComponent();
        } else {
            layout = (JComponent) frame.getContentPane().getComponent(0);
        }
        WindowBreadCrumbs breadCrumbs = tabs.get(layout);
        if (breadCrumbs == null)
            throw new IllegalStateException("BreadCrumbs not found");

        Window currentWindow = breadCrumbs.getCurrentWindow();
        Window currentWindowFrame = (Window) currentWindow.getFrame();
        windowOpenMode.get(currentWindowFrame).setFocusOwner(frame.getFocusOwner());

        Set<Map.Entry<Window, Integer>> set = windows.entrySet();
        boolean pushed = false;
        for (Map.Entry<Window, Integer> entry : set) {
            if (entry.getKey().equals(currentWindow)) {
                windows.remove(currentWindow);
                stacks.get(breadCrumbs).push(entry);
                pushed = true;
                break;
            }
        }
        if (!pushed) {
            stacks.get(breadCrumbs).push(new AbstractMap.SimpleEntry<>(currentWindow, null));
        }

        windows.remove(window);
        layout.remove(DesktopComponentsHelper.getComposition(currentWindow));

        JComponent component = DesktopComponentsHelper.getComposition(window);
        layout.add(component);

        breadCrumbs.addWindow(window);
        if (isMainWindowManager) {
            setActiveWindowCaption(caption, description, tabsPane.getSelectedIndex());
        } else {
            setTopLevelWindowCaption(caption);
            component.revalidate();
            component.repaint();
        }

        return layout;
    }

    protected void setActiveWindowCaption(String caption, String description, int tabIndex) {
        ButtonTabComponent tabComponent = (ButtonTabComponent) tabsPane.getTabComponentAt(tabIndex);
        String formattedCaption = formatTabCaption(caption, description);
        tabComponent.setCaption(formattedCaption);
    }

    protected void setTopLevelWindowCaption(String caption) {
        frame.setTitle(caption);
    }

    protected WindowBreadCrumbs createBreadCrumbs() {
        final WindowBreadCrumbs breadCrumbs = new WindowBreadCrumbs();
        breadCrumbs.addListener(new WindowBreadCrumbs.Listener() {
            @Override
            public void windowClick(final Window window) {
                Runnable op = new Runnable() {
                    @Override
                    public void run() {
                        Window currentWindow = breadCrumbs.getCurrentWindow();
                        if (currentWindow != null && window != currentWindow) {
                            if (!isCloseWithCloseButtonPrevented(currentWindow)) {
                                currentWindow.closeAndRun("close", this);
                            }
                        }
                    }
                };
                op.run();
            }
        });
        return breadCrumbs;
    }

    protected JComponent showWindowNewTab(Window window, String caption, String description, Integer tabPosition) {
        getDialogParams().reset();

        window.setWidth("100%");
        window.setHeight("100%");

        final WindowBreadCrumbs breadCrumbs = createBreadCrumbs();
        stacks.put(breadCrumbs, new Stack<>());

        breadCrumbs.addWindow(window);
        JComponent tabContent = createNewTab(window, caption, description, breadCrumbs, tabPosition);
        tabs.put(tabContent, breadCrumbs);
        return tabContent;
    }

    protected JPanel createTabPanel(Window window, WindowBreadCrumbs breadCrumbs) {
        JPanel panel = new JPanel(new BorderLayout());
        panel.add(breadCrumbs, BorderLayout.NORTH);
        JComponent composition = DesktopComponentsHelper.getComposition(window);
        panel.add(composition, BorderLayout.CENTER);
        return panel;
    }

    protected JComponent createNewTab(Window window, String caption, String description,
            WindowBreadCrumbs breadCrumbs, Integer tabPosition) {
        JPanel panel = createTabPanel(window, breadCrumbs);
        int idx;
        if (tabPosition != null) {
            idx = tabPosition;
        } else {
            idx = tabsPane.getTabCount();
        }
        tabsPane.insertTab(formatTabCaption(caption, description), null, panel, null, idx);

        ButtonTabComponent tabComponent = new ButtonTabComponent(tabsPane, true, true,
                new ButtonTabComponent.CloseListener() {
                    @Override
                    public void onTabClose(final int tabIndex) {
                        ValidationAlertHolder.runIfValid(new Runnable() {
                            @Override
                            public void run() {
                                JComponent tabContent = (JComponent) tabsPane.getComponentAt(tabIndex);
                                closeTab(tabContent);
                            }
                        });
                    }
                }, new ButtonTabComponent.DetachListener() {
                    @Override
                    public void onDetach(final int tabIndex) {
                        ValidationAlertHolder.runIfValid(new Runnable() {
                            @Override
                            public void run() {
                                detachTab(tabIndex);
                            }
                        });
                    }
                });
        tabsPane.setTabComponentAt(idx, tabComponent);
        tabsPane.setSelectedIndex(idx);

        initTabContextMenu(tabComponent);

        return panel;
    }

    protected void initTabContextMenu(JComponent tabComponent) {
        tabComponent.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                dispatchToParent(e);
                if (e.isPopupTrigger()) {
                    showTabPopup(e);
                }
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                dispatchToParent(e);
                if (e.isPopupTrigger()) {
                    showTabPopup(e);
                }
            }

            @Override
            public void mouseEntered(MouseEvent e) {
                dispatchToParent(e);
            }

            @Override
            public void mouseMoved(MouseEvent e) {
                dispatchToParent(e);
            }

            @Override
            public void mouseDragged(MouseEvent e) {
                dispatchToParent(e);
            }

            @Override
            public void mouseWheelMoved(MouseWheelEvent e) {
                dispatchToParent(e);
            }

            @Override
            public void mouseExited(MouseEvent e) {
                dispatchToParent(e);
            }

            @Override
            public void mouseClicked(MouseEvent e) {
                dispatchToParent(e);
            }

            public void dispatchToParent(MouseEvent e) {
                tabsPane.dispatchEvent(SwingUtilities.convertMouseEvent(e.getComponent(), e, tabsPane));
            }
        });
    }

    public void showTabPopup(MouseEvent e) {
        JComponent tabHeaderComponent = (JComponent) e.getComponent();
        int tabIndex = getTabIndex(tabHeaderComponent);
        JComponent tabContent = (JComponent) tabsPane.getComponentAt(tabIndex);
        WindowBreadCrumbs windowBreadCrumbs = tabs.get(tabContent);

        Window window = windowBreadCrumbs.getCurrentWindow();

        JPopupMenu popupMenu = createWindowPopupMenu(window);
        if (popupMenu.getComponentCount() > 0) {
            popupMenu.show(tabHeaderComponent, e.getX(), e.getY());
        }
    }

    protected int getTabIndex(java.awt.Component tabHeaderComponent) {
        for (int i = 0; i < tabsPane.getTabCount(); i++) {
            if (tabsPane.getTabComponentAt(i) == tabHeaderComponent)
                return i;
        }
        return -1;
    }

    protected JPopupMenu createWindowPopupMenu(final Window window) {
        JPopupMenu popupMenu = new JPopupMenu();

        ClientConfig clientConfig = configuration.getConfig(ClientConfig.class);

        if (clientConfig.getManualScreenSettingsSaving()) {
            JMenuItem saveSettingsItem = new JMenuItem(messages.getMainMessage("actions.saveSettings"));
            saveSettingsItem.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    window.saveSettings();
                }
            });
            popupMenu.add(saveSettingsItem);

            JMenuItem restoreToDefaultsItem = new JMenuItem(messages.getMainMessage("actions.restoreToDefaults"));
            restoreToDefaultsItem.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    window.deleteSettings();
                }
            });
            popupMenu.add(restoreToDefaultsItem);
        }
        if (clientConfig.getLayoutAnalyzerEnabled()) {
            JMenuItem analyzeLayoutItem = new JMenuItem(messages.getMainMessage("actions.analyzeLayout"));
            analyzeLayoutItem.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    LayoutAnalyzer analyzer = new LayoutAnalyzer();
                    List<LayoutTip> tipsList = analyzer.analyze(window);

                    if (tipsList.isEmpty()) {
                        showNotification("No layout problems found", NotificationType.HUMANIZED);
                    } else {
                        window.openWindow("layoutAnalyzer", OpenType.DIALOG, ParamsMap.of("tipsList", tipsList));
                    }
                }
            });
            popupMenu.add(analyzeLayoutItem);
        }
        return popupMenu;
    }

    protected void detachTab(int tabIndex) {
        //Create new top-level frame, put this tab to it with breadcrumbs.
        // remove tab data from this window manager
        JComponent tabContent = (JComponent) tabsPane.getComponentAt(tabIndex);
        WindowBreadCrumbs breadCrumbs = tabs.get(tabContent);
        Window window = breadCrumbs.getCurrentWindow();

        if (window == null) {
            throw new IllegalArgumentException("window is null");
        }

        //WindowOpenMode map
        Map<Window, WindowOpenInfo> detachOpenModes = new HashMap<>();
        detachOpenModes.put((Window) window.getFrame(), windowOpenMode.get(window.<Window>getFrame()));
        windowOpenMode.remove(window.<Window>getFrame());
        Stack<Map.Entry<Window, Integer>> stack = stacks.get(breadCrumbs);
        for (Map.Entry<Window, Integer> entry : stack) {
            WindowOpenInfo openInfo = windowOpenMode.get(entry.getKey().<Window>getFrame());
            detachOpenModes.put((Window) entry.getKey().getFrame(), openInfo);
            windowOpenMode.remove(entry.getKey().<Window>getFrame());
        }

        tabs.remove(tabContent);

        Integer hashCode = windows.remove(window);
        tabsPane.remove(tabIndex);
        stacks.remove(breadCrumbs);

        final TopLevelFrame windowFrame = createTopLevelFrame(window.getCaption());
        App.getInstance().registerFrame(windowFrame);
        windowFrame.setVisible(true);
        windowFrame.getWindowManager().attachTab(breadCrumbs, stack, window, hashCode, tabContent, detachOpenModes);
    }

    public void attachTab(WindowBreadCrumbs breadCrumbs, Stack<Map.Entry<Window, Integer>> stack, Window window,
            Integer hashCode, final JComponent tabContent, Map<Window, WindowOpenInfo> openInfos) {
        frame.add(tabContent);
        frame.addWindowListener(new ValidationAwareWindowClosingListener() {
            @Override
            public void windowClosingAfterValidation(WindowEvent e) {
                closeTab(tabContent);
            }
        });
        tabs.put(tabContent, breadCrumbs);
        windowOpenMode.putAll(openInfos);
        stacks.put(breadCrumbs, stack);
        for (Map.Entry<Window, Integer> entry : stack) {
            entry.getKey().setWindowManager(this);
        }
        window.setWindowManager(this);
        if (hashCode != null) {
            windows.put(window, hashCode);
        }

        JPopupMenu popupMenu = createWindowPopupMenu(window);
        if (popupMenu.getComponentCount() > 0) {
            frame.getRootPane().setComponentPopupMenu(popupMenu);
        }
    }

    protected String formatTabCaption(String caption, String description) {
        String s = formatTabDescription(caption, description);
        int maxLength = configuration.getConfig(DesktopConfig.class).getMainTabCaptionLength();
        if (s.length() > maxLength) {
            return s.substring(0, maxLength) + "...";
        } else {
            return s;
        }
    }

    protected String formatTabDescription(final String caption, final String description) {
        if (!StringUtils.isEmpty(description)) {
            return String.format("%s: %s", caption, description);
        } else {
            return caption;
        }
    }

    @Override
    protected void showFrame(Component parent, Frame frame) {
        // the same as web window manager does
        if (parent instanceof Component.Container) {
            Component.Container container = (Component.Container) parent;
            for (Component c : container.getComponents()) {
                if (c instanceof Component.Disposable) {
                    Component.Disposable disposable = (Component.Disposable) c;
                    if (!disposable.isDisposed()) {
                        disposable.dispose();
                    }
                }
                container.remove(c);
            }
            container.add(frame);
        } else {
            throw new IllegalStateException(
                    "Parent component must be com.haulmont.cuba.gui.components.Component.Container");
        }
    }

    @Override
    public void close(Window window) {
        if (window instanceof Window.Wrapper) {
            window = ((Window.Wrapper) window).getWrappedWindow();
        }

        final WindowOpenInfo openInfo = windowOpenMode.get(window);
        if (openInfo == null) {
            log.warn("Problem closing window " + window + " : WindowOpenMode not found");
            return;
        }
        disableSavingScreenHistory = false;
        closeWindow(window, openInfo);
        windowOpenMode.remove(window);
        windows.remove(openInfo.getWindow());
    }

    protected void closeWindow(Window window, WindowOpenInfo openInfo) {
        if (!disableSavingScreenHistory) {
            screenHistorySupport.saveScreenHistory(window, openInfo.getOpenMode());
        }

        switch (openInfo.getOpenMode()) {
        case DIALOG: {
            JDialog dialog = (JDialog) openInfo.getData();
            dialog.setVisible(false);
            dialog.dispose();
            cleanupAfterModalDialogClosed(window);

            fireListeners(window, tabs.size() != 0);
            break;
        }
        case NEW_TAB:
        case NEW_WINDOW: {
            JComponent layout = (JComponent) openInfo.getData();
            layout.remove(DesktopComponentsHelper.getComposition(window));
            if (isMainWindowManager) {
                tabsPane.remove(layout);
            }

            WindowBreadCrumbs windowBreadCrumbs = tabs.get(layout);
            if (windowBreadCrumbs != null) {
                windowBreadCrumbs.clearListeners();
                windowBreadCrumbs.removeWindow();
            }

            tabs.remove(layout);
            stacks.remove(windowBreadCrumbs);

            fireListeners(window, tabs.size() != 0);
            if (!isMainWindowManager) {
                closeFrame(getFrame());
            }
            break;
        }
        case THIS_TAB: {
            JComponent layout = (JComponent) openInfo.getData();

            final WindowBreadCrumbs breadCrumbs = tabs.get(layout);
            if (breadCrumbs == null)
                throw new IllegalStateException("Unable to close screen: breadCrumbs not found");

            breadCrumbs.removeWindow();
            Window currentWindow = breadCrumbs.getCurrentWindow();
            if (!stacks.get(breadCrumbs).empty()) {
                Map.Entry<Window, Integer> entry = stacks.get(breadCrumbs).pop();
                putToWindowMap(entry.getKey(), entry.getValue());
            }
            JComponent component = DesktopComponentsHelper.getComposition(currentWindow);
            Window currentWindowFrame = (Window) currentWindow.getFrame();
            final java.awt.Component focusedCmp = windowOpenMode.get(currentWindowFrame).getFocusOwner();
            if (focusedCmp != null) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        focusedCmp.requestFocus();
                    }
                });
            }
            layout.remove(DesktopComponentsHelper.getComposition(window));

            if (App.getInstance().getConnection().isConnected()) {
                layout.add(component);
                if (isMainWindowManager) {
                    // If user clicked on close button maybe selectedIndex != tabsPane.getSelectedIndex()
                    // Refs #1117
                    int selectedIndex = 0;
                    while ((selectedIndex < tabs.size()) && (tabsPane.getComponentAt(selectedIndex) != layout)) {
                        selectedIndex++;
                    }
                    if (selectedIndex == tabs.size()) {
                        selectedIndex = tabsPane.getSelectedIndex();
                    }

                    setActiveWindowCaption(currentWindow.getCaption(), currentWindow.getDescription(),
                            selectedIndex);
                } else {
                    setTopLevelWindowCaption(currentWindow.getCaption());
                    component.revalidate();
                    component.repaint();
                }
            }

            fireListeners(window, tabs.size() != 0);
            break;
        }

        default:
            throw new UnsupportedOperationException();
        }
    }

    protected void cleanupAfterModalDialogClosed(@Nullable Window closingWindow) {
        WindowOpenInfo previous = null;
        for (Iterator<Window> it = windowOpenMode.keySet().iterator(); it.hasNext();) {
            Window w = it.next();
            // Check if there is a modal window opened before the current
            WindowOpenInfo mode = windowOpenMode.get(w);
            if (w != closingWindow && mode.getOpenMode() == OpenMode.DIALOG) {
                previous = mode;
            }
            // If there are windows opened after the current, close them
            if (w == closingWindow && it.hasNext()) {
                close(it.next());
                break;
            }
        }
        if (previous == null) {
            frame.activate();
        } else if (previous.getData() instanceof DialogWindow) {
            ((DialogWindow) previous.getData()).enableWindow();
        } else if (previous.getData() instanceof JDialog) {
            ((JDialog) previous.getData()).requestFocus();
        }
    }

    @Override
    public void showNotification(String caption) {
        showNotification(caption, null, NotificationType.HUMANIZED);
    }

    @Override
    public void showNotification(String caption, NotificationType type) {
        showNotification(caption, null, type);
    }

    @Override
    public void showNotification(String caption, String description, NotificationType type) {
        backgroundWorker.checkUIAccess();

        DesktopConfig config = configuration.getConfig(DesktopConfig.class);

        if (!NotificationType.isHTML(type)) {
            caption = preprocessHtmlMessage(escapeHtml(Strings.nullToEmpty(caption)));
            description = preprocessHtmlMessage(escapeHtml(Strings.nullToEmpty(description)));
        }

        String text = preparePopupText(caption, description);
        if (config.isDialogNotificationsEnabled() && type != NotificationType.TRAY
                && type != NotificationType.TRAY_HTML) {
            showNotificationDialog(text, type);
        } else {
            showNotificationPopup(text, type);
        }
    }

    protected void showNotificationDialog(String text, NotificationType type) {
        String title = messages.getMainMessage("notification.title." + type);

        Icon icon = convertNotificationType(type);

        showOptionDialog(title, text, null, icon, false, new Action[] { new DesktopNotificationAction(Type.CLOSE) },
                "notificationDialog");
    }

    protected void showNotificationPopup(String popupText, NotificationType type) {
        JPanel panel = new JPanel(new MigLayout("flowy"));
        panel.setBorder(BorderFactory.createLineBorder(Color.gray));

        switch (type) {
        case WARNING:
        case WARNING_HTML:
            panel.setBackground(Color.yellow);
            break;
        case ERROR:
        case ERROR_HTML:
            panel.setBackground(Color.orange);
            break;
        default:
            panel.setBackground(Color.cyan);
        }

        JLabel label = new JLabel(popupText);
        panel.add(label);

        Dimension labelSize = DesktopComponentsHelper.measureHtmlText(popupText);

        int x = frame.getX() + frame.getWidth() - (50 + labelSize.getSize().width);
        int y = frame.getY() + frame.getHeight() - (50 + labelSize.getSize().height);

        PopupFactory factory = PopupFactory.getSharedInstance();
        final Popup popup = factory.getPopup(frame, panel, x, y);
        popup.show();

        panel.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                popup.hide();
            }
        });

        PointerInfo pointerInfo = MouseInfo.getPointerInfo();
        if (pointerInfo != null) {
            final Point location = pointerInfo.getLocation();
            final Timer timer = new Timer(3000, null);
            timer.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    PointerInfo currentPointer = MouseInfo.getPointerInfo();
                    if (currentPointer == null) {
                        timer.stop();
                    } else if (!currentPointer.getLocation().equals(location)) {
                        popup.hide();
                        timer.stop();
                    }
                }
            });
            timer.start();
        }
    }

    protected String preparePopupText(String caption, String description) {
        if (StringUtils.isNotBlank(description)) {
            caption = String.format("<b>%s</b><br>%s", caption, description);
        }
        StringBuilder sb = new StringBuilder("<html>");
        String[] strings = caption.split("(<br>)|(<br/>)");
        for (String string : strings) {
            sb.append(string).append("<br/>");
        }
        sb.append("</html>");
        return sb.toString();
    }

    @Override
    public void showMessageDialog(final String title, final String message, MessageType messageType) {
        showOptionDialog(title, message, messageType, false, new Action[] { new DialogAction(Type.OK) },
                "messageDialog");
    }

    protected JPanel createButtonsPanel(Action[] actions, final DialogWindow dialog) {
        JPanel buttonsPanel = new JPanel();

        boolean hasPrimaryAction = false;
        for (final Action action : actions) {
            JButton button = new JButton(action.getCaption());
            String icon = action.getIcon();

            if (icon != null) {
                button.setIcon(AppBeans.get(IconResolver.class).getIconResource(icon));
            }

            final DialogActionHandler dialogActionHandler = new DialogActionHandler(dialog, action);
            button.addActionListener(dialogActionHandler);
            button.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    JButton b = (JButton) e.getSource();
                    userActionsLog.trace("Button (name = {}, text = {}) was clicked in dialog", b.getName(),
                            b.getText());
                }
            });
            if (actions.length == 1) {
                dialog.addWindowListener(new WindowAdapter() {
                    @Override
                    public void windowClosing(WindowEvent e) {
                        dialogActionHandler.onClose();
                    }
                });
            }

            button.setPreferredSize(
                    new Dimension(button.getPreferredSize().width, DesktopComponentsHelper.BUTTON_HEIGHT));
            button.setMaximumSize(new Dimension(Integer.MAX_VALUE, DesktopComponentsHelper.BUTTON_HEIGHT));

            if (action instanceof AbstractAction && ((AbstractAction) action).isPrimary()) {
                hasPrimaryAction = true;

                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        button.requestFocus();
                    }
                });
            }

            buttonsPanel.add(button);
        }

        if (!hasPrimaryAction && actions.length > 0) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    buttonsPanel.getComponent(0).requestFocus();
                }
            });
        }

        return buttonsPanel;
    }

    public WindowBreadCrumbs getBreadCrumbs(JComponent container) {
        return tabs.get(container);
    }

    protected void assignDialogShortcuts(final JDialog dialog, JPanel panel, final Action[] actions) {
        ClientConfig clientConfig = configuration.getConfig(ClientConfig.class);

        InputMap inputMap = panel.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        ActionMap actionMap = panel.getActionMap();

        String commitShortcut = getConfigValueIfConnected(clientConfig::getCommitShortcut,
                "cuba.gui.commitShortcut", "CTRL-ENTER");

        KeyCombination okCombination = KeyCombination.create(commitShortcut);
        KeyStroke okKeyStroke = DesktopComponentsHelper.convertKeyCombination(okCombination);

        inputMap.put(okKeyStroke, "okAction");
        actionMap.put("okAction", new javax.swing.AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                for (Action action : actions) {
                    if (action instanceof DialogAction) {
                        switch (((DialogAction) action).getType()) {
                        case OK:
                        case YES:
                            action.actionPerform(null);
                            dialog.setVisible(false);
                            cleanupAfterModalDialogClosed(null);
                            return;
                        }
                    }
                }
            }
        });

        String closeShortcut = getConfigValueIfConnected(clientConfig::getCloseShortcut, "cuba.gui.closeShortcut",
                "ESCAPE");

        KeyCombination closeCombination = KeyCombination.create(closeShortcut);
        KeyStroke closeKeyStroke = DesktopComponentsHelper.convertKeyCombination(closeCombination);

        inputMap.put(closeKeyStroke, "closeAction");
        actionMap.put("closeAction", new javax.swing.AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (actions.length == 1) {
                    actions[0].actionPerform(null);
                    dialog.setVisible(false);
                    cleanupAfterModalDialogClosed(null);
                } else {
                    for (Action action : actions) {
                        if (action instanceof DialogAction) {
                            switch (((DialogAction) action).getType()) {
                            case CANCEL:
                            case CLOSE:
                            case NO:
                                action.actionPerform(null);
                                dialog.setVisible(false);
                                cleanupAfterModalDialogClosed(null);
                                return;
                            }
                        }
                    }
                }
            }
        });
    }

    protected void showOptionDialog(final String title, final String message, MessageType messageType,
            boolean alwaysModal, final Action[] actions, String debugName) {
        Icon icon = convertMessageType(messageType.getMessageMode());

        String msg = message;
        if (!MessageType.isHTML(messageType)) {
            msg = StringEscapeUtils.escapeHtml(msg);
            msg = ComponentsHelper.preprocessHtmlMessage("<html>" + msg + "</html>");
        } else {
            msg = "<html>" + msg + "</html>";
        }

        showOptionDialog(title, msg, messageType, icon, alwaysModal, actions, debugName);
    }

    protected void showOptionDialog(final String title, final String message, @Nullable MessageType messageType,
            final Icon icon, boolean alwaysModal, final Action[] actions, String debugName) {
        final DialogWindow dialog = new DialogWindow(frame, title);

        if (App.getInstance().isTestMode()) {
            dialog.setName(debugName);
        }
        dialog.setModal(false);

        if (actions.length > 1) {
            dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
        }

        int width = 500;
        SizeUnit unit = null;
        DialogParams dialogParams = getDialogParams();
        if (messageType != null && messageType.getWidth() != null) {
            width = messageType.getWidth().intValue();
            unit = messageType.getWidthUnit();
        } else if (dialogParams.getWidth() != null) {
            width = dialogParams.getWidth().intValue();
            unit = dialogParams.getWidthUnit();
        }

        LC lc = new LC();
        lc.insets("10");

        MigLayout layout = new MigLayout(lc);
        final JPanel panel = new JPanel(layout);
        if (icon != null) {
            JLabel iconLabel = new JLabel(icon);
            panel.add(iconLabel, "aligny top");
        }

        JLabel msgLabel = new JLabel(message);

        if (width != AUTO_SIZE_PX) {
            panel.add(msgLabel, "width 100%, wrap, growy 0");
        } else {
            panel.add(msgLabel, "wrap");
        }

        if (icon != null) {
            panel.add(new JLabel(" "));
        }

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                dialog.requestFocus();
            }
        });

        final JPanel buttonsPanel = createButtonsPanel(actions, dialog);
        panel.add(buttonsPanel, "alignx right");

        if (width != AUTO_SIZE_PX) {
            if (unit != null && unit != SizeUnit.PIXELS) {
                throw new UnsupportedOperationException("Dialog size can be set only in pixels");
            }
            dialog.setLayout(new MigLayout(new LC().insets("0").width(width + "px")));
            dialog.setFixedWidth(width);
            dialog.add(panel, "width 100%, growy 0");
        } else {
            dialog.add(panel);
        }

        assignDialogShortcuts(dialog, panel, actions);

        dialog.pack();
        dialog.setResizable(false);
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                panel.revalidate();
                panel.repaint();

                java.awt.Container container = panel.getTopLevelAncestor();
                if (container instanceof JDialog) {
                    JDialog dialog = (JDialog) container;
                    dialog.pack();
                }
            }
        });
        dialog.setLocationRelativeTo(frame);

        boolean modal = true;
        if (!alwaysModal) {
            if (!hasModalWindow()) {
                if (messageType != null && messageType.getModal() != null) {
                    modal = messageType.getModal();
                } else if (dialogParams.getModal() != null) {
                    modal = dialogParams.getModal();
                }
            }
        } else {
            if (messageType != null && messageType.getModal() != null) {
                log.warn("MessageType.modal is not supported for showOptionDialog");
            }
        }

        if (modal) {
            DialogWindow lastDialogWindow = getLastDialogWindow();
            if (lastDialogWindow == null) {
                frame.deactivate(null);
            } else {
                lastDialogWindow.disableWindow(null);
            }
        }

        dialog.setVisible(true);
    }

    @Override
    public void showExceptionDialog(Throwable throwable) {
        showExceptionDialog(throwable, null, null);
    }

    @Override
    public void showExceptionDialog(Throwable throwable, @Nullable String caption, @Nullable String message) {
        Preconditions.checkNotNullArgument(throwable);

        JXErrorPane errorPane = new JXErrorPaneExt();
        errorPane.setErrorInfo(createErrorInfo(caption, message, throwable));

        final TopLevelFrame mainFrame = App.getInstance().getMainFrame();

        JDialog dialog = JXErrorPane.createDialog(mainFrame, errorPane);
        dialog.setMinimumSize(new Dimension(600, (int) dialog.getMinimumSize().getHeight()));

        final DialogWindow lastDialogWindow = getLastDialogWindow();
        dialog.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosed(WindowEvent e) {
                if (lastDialogWindow != null) {
                    lastDialogWindow.enableWindow();
                } else {
                    mainFrame.activate();
                }
            }
        });
        dialog.setModal(false);

        if (lastDialogWindow != null) {
            lastDialogWindow.disableWindow(null);
        } else {
            mainFrame.deactivate(null);
        }

        dialog.setVisible(true);
    }

    protected ErrorInfo createErrorInfo(String caption, String message, Throwable exception) {
        UserSessionSource userSessionSource = AppBeans.get(UserSessionSource.NAME);
        Security security = AppBeans.get(Security.NAME);
        if (userSessionSource.getUserSession() == null
                || !security.isSpecificPermitted("cuba.gui.showExceptionDetails")) {

            String dialogCaption = StringUtils.isNotEmpty(caption) ? caption : getMessage("errorPane.title");
            String dialogMessage = StringUtils.isNotEmpty(message) ? message
                    : getMessage("exceptionDialog.contactAdmin");

            return new ErrorInfo(dialogCaption, dialogMessage, null, null, null, null, null);
        }

        Throwable rootCause = ExceptionUtils.getRootCause(exception);
        if (rootCause == null)
            rootCause = exception;

        StringBuilder msg = new StringBuilder();
        if (rootCause instanceof RemoteException) {
            RemoteException re = (RemoteException) rootCause;
            if (!re.getCauses().isEmpty()) {
                RemoteException.Cause cause = re.getCauses().get(re.getCauses().size() - 1);
                if (cause.getThrowable() != null)
                    rootCause = cause.getThrowable();
                else {
                    // root cause is not supported by client
                    String className = cause.getClassName();
                    if (className != null && className.indexOf('.') > 0) {
                        className = className.substring(className.lastIndexOf('.') + 1);
                    }
                    msg.append(className).append(": ").append(cause.getMessage());
                }
            }
        }

        if (msg.length() == 0) {
            msg.append(rootCause.getClass().getSimpleName());
            if (!StringUtils.isBlank(rootCause.getMessage()))
                msg.append(": ").append(rootCause.getMessage());

            if (rootCause instanceof DevelopmentException) {
                Map<String, Object> params = new LinkedHashMap<>();
                if (rootCause instanceof GuiDevelopmentException) {
                    GuiDevelopmentException guiDevException = (GuiDevelopmentException) rootCause;
                    if (guiDevException.getFrameId() != null) {
                        params.put("Frame ID", guiDevException.getFrameId());
                        try {
                            WindowConfig windowConfig = AppBeans.get(WindowConfig.NAME);
                            params.put("XML descriptor",
                                    windowConfig.getWindowInfo(guiDevException.getFrameId()).getTemplate());
                        } catch (Exception e) {
                            params.put("XML descriptor", "not found for " + guiDevException.getFrameId());
                        }
                    }
                }
                params.putAll(((DevelopmentException) rootCause).getParams());

                if (!params.isEmpty()) {
                    msg.append("\n\n");
                    for (Map.Entry<String, Object> entry : params.entrySet()) {
                        msg.append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
                    }
                }
            }
        }

        return new ErrorInfo(getMessage("errorPane.title"), msg.toString(), null, null, rootCause, null, null);
    }

    protected String getMessage(String key) {
        Messages messages = AppBeans.get(Messages.NAME);
        return messages.getMainMessage(key, App.getInstance().getLocale());
    }

    /**
     * Handler for OptionDialog, MessageDialog, NotificationPopup buttons,
     * also used for handling dialog close event if dialog has only one action.
     */
    protected class DialogActionHandler extends WindowAdapter implements ActionListener {

        protected Action action;

        protected final DialogWindow dialog;

        protected final java.awt.Component lastFocusedComponent;

        public DialogActionHandler(DialogWindow dialog, Action action) {
            this.action = action;
            this.dialog = dialog;
            this.dialog.addWindowListener(this);

            DialogWindow lastDialogWindow = getLastDialogWindow();
            if (lastDialogWindow != null) {
                lastFocusedComponent = lastDialogWindow.getFocusOwner();
            } else {
                lastFocusedComponent = frame.getFocusOwner();
            }
        }

        public void onClose() {
            if (lastFocusedComponent != null && lastFocusedComponent.isEnabled() && lastFocusedComponent.isVisible()
                    && !(lastFocusedComponent instanceof JDialog) && !(lastFocusedComponent instanceof JFrame)) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        lastFocusedComponent.requestFocus();
                    }
                });
            }

            action.actionPerform(null);
            dialog.setVisible(false);
            cleanupAfterModalDialogClosed(null);
            dialog.dispose();
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            onClose();
        }

        @Override
        public void windowClosed(WindowEvent e) {
            action = null;
        }
    }

    protected Icon convertMessageType(MessageMode messageType) {
        switch (messageType) {
        case CONFIRMATION:
        case CONFIRMATION_HTML:
            return UIManager.getIcon("OptionPane.informationIcon");
        case WARNING:
        case WARNING_HTML:
            return UIManager.getIcon("OptionPane.warningIcon");
        default:
            return null;
        }
    }

    protected Icon convertNotificationType(NotificationType type) {
        switch (type) {
        case ERROR:
        case ERROR_HTML:
            return UIManager.getIcon("OptionPane.errorIcon");
        case HUMANIZED:
        case HUMANIZED_HTML:
            return UIManager.getIcon("OptionPane.informationIcon");
        case WARNING:
        case WARNING_HTML:
            return UIManager.getIcon("OptionPane.warningIcon");
        default:
            return null;
        }
    }

    @Override
    public void showOptionDialog(String title, String message, MessageType messageType, final Action[] actions) {
        backgroundWorker.checkUIAccess();

        showOptionDialog(title, message, messageType, true, actions, "optionDialog");
    }

    @Override
    public void showWebPage(String url, @Nullable Map<String, Object> params) {
        try {
            Desktop.getDesktop().browse(new URI(url));
        } catch (IOException | URISyntaxException e) {
            throw new RuntimeException("Unable to show web page " + url, e);
        }
    }

    protected JComponent findTab(Integer hashCode) {
        Set<Map.Entry<JComponent, WindowBreadCrumbs>> set = tabs.entrySet();
        for (Map.Entry<JComponent, WindowBreadCrumbs> entry : set) {
            Window currentWindow = entry.getValue().getCurrentWindow();
            if (hashCode.equals(getWindowHashCode(currentWindow)))
                return entry.getKey();
        }
        return null;
    }

    /**
     * Release resources right before throwing away this WindowManager instance.
     */
    public void dispose() {
        for (WindowOpenInfo openInfo : windowOpenMode.values()) {
            if (openInfo.getOpenMode() == OpenMode.DIALOG) {
                JDialog dialog = (JDialog) openInfo.getData();
                dialog.setVisible(false);
            }
        }

        if (isMainWindowManager) {
            // Stop background tasks
            WatchDog watchDog = AppBeans.get(WatchDog.NAME);
            watchDog.stopTasks();
        }

        // Dispose windows
        for (Window window : windowOpenMode.keySet()) {
            Frame frame = window.getFrame();
            if (frame instanceof Component.Disposable)
                ((Component.Disposable) frame).dispose();
        }

        tabs.clear();
        windowOpenMode.clear();
        stacks.clear();
    }

    protected static class WindowOpenInfo {

        protected Window window;
        protected OpenMode openMode;
        protected Object data;
        protected java.awt.Component focusOwner;

        public WindowOpenInfo(Window window, OpenMode openMode) {
            this.window = window;
            this.openMode = openMode;
        }

        public Object getData() {
            return data;
        }

        public void setData(Object data) {
            this.data = data;
        }

        public Window getWindow() {
            return window;
        }

        public OpenMode getOpenMode() {
            return openMode;
        }

        public java.awt.Component getFocusOwner() {
            return focusOwner;
        }

        public void setFocusOwner(java.awt.Component focusOwner) {
            this.focusOwner = focusOwner;
        }
    }

    public class TabCloseTask implements Runnable {
        protected final WindowBreadCrumbs breadCrumbs;

        public TabCloseTask(WindowBreadCrumbs breadCrumbs) {
            this.breadCrumbs = breadCrumbs;
        }

        @Override
        public void run() {
            Window windowToClose = breadCrumbs.getCurrentWindow();
            if (windowToClose != null) {
                if (!isCloseWithCloseButtonPrevented(windowToClose)) {
                    windowToClose.closeAndRun("close", new TabCloseTask(breadCrumbs));
                }
            }
        }
    }

    public void addWindowData(Window window, Object windowData, OpenType openType) {
        WindowOpenInfo openInfo = new WindowOpenInfo(window, openType.getOpenMode());
        openInfo.setData(windowData);
        if (window instanceof Window.Wrapper) {
            Window wrappedWindow = ((Window.Wrapper) window).getWrappedWindow();
            windowOpenMode.put(wrappedWindow, openInfo);
        } else {
            windowOpenMode.put(window, openInfo);
        }

        addShortcuts(window, openType);
    }

    public void checkModificationsAndCloseAll(final Runnable runIfOk, final @Nullable Runnable runIfCancel) {
        boolean modified = false;
        for (Window window : getOpenWindows()) {
            if (!disableSavingScreenHistory) {
                screenHistorySupport.saveScreenHistory(window, windowOpenMode.get(window).getOpenMode());
            }

            recursiveFramesClose = true;
            try {
                if (window instanceof WrappedWindow && ((WrappedWindow) window).getWrapper() != null) {
                    ((WrappedWindow) window).getWrapper().saveSettings();
                } else {
                    window.saveSettings();
                }
            } finally {
                recursiveFramesClose = false;
            }

            if (window.getDsContext() != null && window.getDsContext().isModified()) {
                modified = true;
            }
        }
        disableSavingScreenHistory = true;
        if (modified) {
            showOptionDialog(messages.getMainMessage("closeUnsaved.caption"),
                    messages.getMainMessage("discardChangesOnClose"), MessageType.WARNING,
                    new Action[] { new com.haulmont.cuba.gui.components.AbstractAction(
                            messages.getMainMessage("closeApplication")) {
                        @Override
                        public void actionPerform(com.haulmont.cuba.gui.components.Component component) {
                            if (runIfOk != null)
                                runIfOk.run();
                        }

                        @Override
                        public String getIcon() {
                            return "icons/ok.png";
                        }
                    }, new com.haulmont.cuba.gui.components.AbstractAction(
                            messages.getMainMessage("actions.Cancel"), Status.PRIMARY) {
                        @Override
                        public void actionPerform(com.haulmont.cuba.gui.components.Component component) {
                            if (runIfCancel != null)
                                runIfCancel.run();
                        }

                        @Override
                        public String getIcon() {
                            return "icons/cancel.png";
                        }
                    } });
        } else {
            runIfOk.run();
        }
    }

    protected String getConfigValueIfConnected(Provider<String> valueProvider, String fallbackProperty,
            String fallbackValue) {
        if (App.getInstance().getConnection().isConnected()) {
            try {
                return valueProvider.get();
            } catch (NoUserSessionException ignored) {
            }
        }

        String propertyValue = AppContext.getProperty(fallbackProperty);
        if (propertyValue != null) {
            return propertyValue;
        }

        return fallbackValue;
    }

    @Override
    protected SettingsImpl getSettingsImpl(String id) {
        return new AsyncSettingsImpl(id);
    }

    protected class AsyncSettingsImpl extends SettingsImpl {
        public AsyncSettingsImpl(String id) {
            super(id);
        }

        @Override
        public void commit() {
            if (modified && root != null) {
                final String xml = Dom4j.writeDocument(root.getDocument(), true);
                if (!recursiveFramesClose) {
                    saveSettingsAsync(xml);
                } else {
                    getSettingsClient().setSetting(name, xml);
                }

                modified = false;
            }
        }

        protected void saveSettingsAsync(final String xml) {
            BackgroundWorker worker = AppConfig.getBackgroundWorker();
            BackgroundTaskHandler<Object> handle = worker.handle(new BackgroundTask<Object, Object>(10) {
                @Override
                public Object run(TaskLifeCycle<Object> taskLifeCycle) throws Exception {
                    getSettingsClient().setSetting(AsyncSettingsImpl.this.name, xml);

                    return null;
                }

                @Override
                public boolean handleException(Exception ex) {
                    log.warn("Unable to save user settings " + AsyncSettingsImpl.this.name, ex);

                    return true;
                }

                @Override
                public boolean handleTimeoutException() {
                    log.warn("Time out while saving user settings " + AsyncSettingsImpl.this.name);

                    return true;
                }
            });
            handle.execute();
        }
    }

    @Override
    protected void initDebugIds(Frame frame) {
        if (App.getInstance().isTestMode()) {
            ComponentsHelper.walkComponents(frame, new ComponentVisitor() {
                @Override
                public void visit(com.haulmont.cuba.gui.components.Component component, String name) {
                    if (component.getDebugId() == null) {
                        Frame componentFrame = null;
                        if (component instanceof com.haulmont.cuba.gui.components.Component.BelongToFrame) {
                            componentFrame = ((com.haulmont.cuba.gui.components.Component.BelongToFrame) component)
                                    .getFrame();
                        }
                        if (componentFrame == null) {
                            log.warn("Frame for component " + component.getClass() + " is not assigned");
                        } else {
                            if (component instanceof DesktopAbstractComponent) {
                                DesktopAbstractComponent desktopComponent = (DesktopAbstractComponent) component;
                                desktopComponent.assignAutoDebugId();
                            }
                        }
                    }
                }
            });
        }
    }

    protected boolean isCloseWithShortcutPrevented(Window currentWindow) {
        DesktopWindow desktopWindow = (DesktopWindow) ComponentsHelper.getWindowImplementation(currentWindow);

        if (desktopWindow != null) {
            Window.BeforeCloseWithShortcutEvent event = new Window.BeforeCloseWithShortcutEvent(desktopWindow);
            desktopWindow.fireBeforeCloseWithShortcut(event);
            return event.isClosePrevented();
        }

        return false;
    }

    protected boolean isCloseWithCloseButtonPrevented(Window currentWindow) {
        DesktopWindow desktopWindow = (DesktopWindow) ComponentsHelper.getWindowImplementation(currentWindow);

        if (desktopWindow != null) {
            Window.BeforeCloseWithCloseButtonEvent event = new Window.BeforeCloseWithCloseButtonEvent(
                    desktopWindow);
            desktopWindow.fireBeforeCloseWithCloseButton(event);
            return event.isClosePrevented();
        }

        return false;
    }

    // just stub
    @Override
    public void openDefaultScreen() {
    }

    protected class DesktopNotificationAction implements Action {
        private Type type;

        public DesktopNotificationAction(Type type) {
            this.type = type;
        }

        public Type getType() {
            return type;
        }

        @Override
        public String getId() {
            return type.getId();
        }

        @Override
        public String getCaption() {
            return messages.getMainMessage(type.getMsgKey());
        }

        @Override
        public void setCaption(String caption) {
        }

        @Override
        public String getDescription() {
            return null;
        }

        @Override
        public void setDescription(String description) {
        }

        @Override
        public KeyCombination getShortcutCombination() {
            return null;
        }

        @Override
        public void setShortcutCombination(KeyCombination shortcut) {
        }

        @Override
        public void setShortcut(String shortcut) {
        }

        @Override
        public String getIcon() {
            Icons icons = AppBeans.get(Icons.class);
            return icons.get(type.getIconKey());
        }

        @Override
        public void setIcon(String icon) {
        }

        @Override
        public void setIconFromSet(Icons.Icon icon) {
        }

        @Override
        public boolean isEnabled() {
            return true;
        }

        @Override
        public void setEnabled(boolean enabled) {
        }

        @Override
        public boolean isVisible() {
            return true;
        }

        @Override
        public void setVisible(boolean visible) {
        }

        @Override
        public void refreshState() {
        }

        @Override
        public Component.ActionOwner getOwner() {
            return null;
        }

        @Override
        public Collection<Component.ActionOwner> getOwners() {
            return Collections.emptyList();
        }

        @Override
        public void addOwner(Component.ActionOwner actionOwner) {
        }

        @Override
        public void removeOwner(Component.ActionOwner actionOwner) {
        }

        @Override
        public void actionPerform(Component component) {
        }

        @Override
        public void addPropertyChangeListener(PropertyChangeListener listener) {
        }

        @Override
        public void removePropertyChangeListener(PropertyChangeListener listener) {
        }
    }
}