com.intellij.execution.ui.layout.impl.RunnerContentUi.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.execution.ui.layout.impl.RunnerContentUi.java

Source

/*
 * Copyright 2000-2014 JetBrains s.r.o.
 *
 * 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.intellij.execution.ui.layout.impl;

import com.intellij.execution.ui.RunnerLayoutUi;
import com.intellij.execution.ui.layout.*;
import com.intellij.execution.ui.layout.actions.CloseViewAction;
import com.intellij.execution.ui.layout.actions.MinimizeViewAction;
import com.intellij.execution.ui.layout.actions.RestoreViewAction;
import com.intellij.ide.DataManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.AbstractPainter;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.openapi.wm.IdeGlassPaneUtil;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.JBColor;
import com.intellij.ui.ScreenUtil;
import com.intellij.ui.UIBundle;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.awt.RelativeRectangle;
import com.intellij.ui.components.panels.NonOpaquePanel;
import com.intellij.ui.components.panels.Wrapper;
import com.intellij.ui.content.*;
import com.intellij.ui.docking.DockContainer;
import com.intellij.ui.docking.DockManager;
import com.intellij.ui.docking.DockableContent;
import com.intellij.ui.docking.DragSession;
import com.intellij.ui.docking.impl.DockManagerImpl;
import com.intellij.ui.switcher.QuickActionProvider;
import com.intellij.ui.switcher.SwitchProvider;
import com.intellij.ui.switcher.SwitchTarget;
import com.intellij.ui.tabs.JBTabs;
import com.intellij.ui.tabs.TabInfo;
import com.intellij.ui.tabs.TabsListener;
import com.intellij.ui.tabs.impl.JBTabsImpl;
import com.intellij.util.NotNullFunction;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.AbstractLayoutManager;
import com.intellij.util.ui.GraphicsUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;

public class RunnerContentUi implements ContentUI, Disposable, CellTransform.Facade, ViewContextEx,
        PropertyChangeListener, SwitchProvider, QuickActionProvider, DockContainer.Dialog {
    public static final DataKey<RunnerContentUi> KEY = DataKey.create("DebuggerContentUI");
    public static final Key<Boolean> LIGHTWEIGHT_CONTENT_MARKER = Key.create("LightweightContent");

    @NonNls
    private static final String LAYOUT = "Runner.Layout";
    @NonNls
    private static final String SETTINGS = "XDebugger.Settings";
    @NonNls
    private static final String VIEW_POPUP = "Runner.View.Popup";
    @NonNls
    static final String VIEW_TOOLBAR = "Runner.View.Toolbar";

    private ContentManager myManager;
    private final RunnerLayout myLayoutSettings;

    @NotNull
    private final ActionManager myActionManager;
    private final String mySessionName;
    private final MyComponent myComponent = new MyComponent();

    private final Wrapper myToolbar = new Wrapper();
    final MyDragOutDelegate myDragOutDelegate = new MyDragOutDelegate();

    JBRunnerTabs myTabs;
    private final Comparator<TabInfo> myTabsComparator = new Comparator<TabInfo>() {
        @Override
        public int compare(@NotNull final TabInfo o1, @NotNull final TabInfo o2) {
            //noinspection ConstantConditions
            TabImpl tab1 = getTabFor(o1);
            TabImpl tab2 = getTabFor(o2);
            int index1 = tab1 != null ? tab1.getIndex() : -1;
            int index2 = tab2 != null ? tab2.getIndex() : -1;
            return index1 - index2;
        }
    };
    private final Project myProject;

    private ActionGroup myTopActions = new DefaultActionGroup();

    private final DefaultActionGroup myMinimizedViewActions = new DefaultActionGroup();

    private final Map<GridImpl, Wrapper> myMinimizedButtonsPlaceholder = new HashMap<GridImpl, Wrapper>();
    private final Map<GridImpl, Wrapper> myCommonActionsPlaceholder = new HashMap<GridImpl, Wrapper>();
    private final Map<GridImpl, AnAction[]> myContextActions = new HashMap<GridImpl, AnAction[]>();

    private boolean myUiLastStateWasRestored;

    private final Set<Object> myRestoreStateRequestors = new HashSet<Object>();
    private String myActionsPlace = ActionPlaces.UNKNOWN;
    private final IdeFocusManager myFocusManager;

    private boolean myMinimizeActionEnabled = true;
    private boolean myMoveToGridActionEnabled = true;
    private final RunnerLayoutUi myRunnerUi;

    private final Map<String, LayoutAttractionPolicy> myAttractions = new HashMap<String, LayoutAttractionPolicy>();
    private final Map<String, LayoutAttractionPolicy> myConditionAttractions = new HashMap<String, LayoutAttractionPolicy>();

    private ActionGroup myTabPopupActions;
    private ActionGroup myAdditionalFocusActions;

    private final ActionCallback myInitialized = new ActionCallback();
    private boolean myToDisposeRemovedContent = true;

    private int myAttractionCount;
    private ActionGroup myLeftToolbarActions;

    private JBTabs myCurrentOver;
    private Image myCurrentOverImg;
    private TabInfo myCurrentOverInfo;
    private MyDropAreaPainter myCurrentPainter;

    private RunnerContentUi myOriginal;
    private final CopyOnWriteArraySet<Listener> myDockingListeners = new CopyOnWriteArraySet<Listener>();
    private final Set<RunnerContentUi> myChildren = new TreeSet<RunnerContentUi>(new Comparator<RunnerContentUi>() {
        @Override
        public int compare(@NotNull RunnerContentUi o1, @NotNull RunnerContentUi o2) {
            return o1.myWindow - o2.myWindow;
        }
    });
    private int myWindow;
    private boolean myDisposing;

    public RunnerContentUi(@NotNull Project project, @NotNull RunnerLayoutUi ui,
            @NotNull ActionManager actionManager, @NotNull IdeFocusManager focusManager,
            @NotNull RunnerLayout settings, @NotNull String sessionName) {
        myProject = project;
        myRunnerUi = ui;
        myLayoutSettings = settings;
        myActionManager = actionManager;
        mySessionName = sessionName;
        myFocusManager = focusManager;
    }

    public RunnerContentUi(@NotNull RunnerContentUi ui, @NotNull RunnerContentUi original, int window) {
        this(ui.myProject, ui.myRunnerUi, ui.myActionManager, ui.myFocusManager, ui.myLayoutSettings,
                ui.mySessionName);
        myOriginal = original;
        original.myChildren.add(this);
        myWindow = window == 0 ? original.findFreeWindow() : window;
    }

    public void setTopActions(@NotNull final ActionGroup topActions, @NotNull String place) {
        myTopActions = topActions;
        myActionsPlace = place;

        rebuildCommonActions();
    }

    public void setTabPopupActions(ActionGroup tabPopupActions) {
        myTabPopupActions = tabPopupActions;
        rebuildTabPopup();
    }

    public void setAdditionalFocusActions(final ActionGroup group) {
        myAdditionalFocusActions = group;
        rebuildTabPopup();
    }

    public void setLeftToolbar(ActionGroup group, String place) {
        final ActionToolbar tb = myActionManager.createActionToolbar(place, group, false);
        tb.setTargetComponent(myComponent);
        myToolbar.setContent(tb.getComponent());
        myLeftToolbarActions = group;

        myComponent.revalidate();
        myComponent.repaint();
    }

    public void initUi() {
        if (myTabs != null)
            return;

        myTabs = (JBRunnerTabs) new JBRunnerTabs(myProject, myActionManager, myFocusManager, this)
                .setDataProvider(new DataProvider() {
                    @Override
                    public Object getData(@NonNls final String dataId) {
                        if (ViewContext.CONTENT_KEY.is(dataId)) {
                            TabInfo info = myTabs.getTargetInfo();
                            if (info != null) {
                                return getGridFor(info).getData(dataId);
                            }
                        } else if (ViewContext.CONTEXT_KEY.is(dataId)) {
                            return RunnerContentUi.this;
                        }
                        return null;
                    }
                }).setTabLabelActionsAutoHide(false).setProvideSwitchTargets(false)
                .setInnerInsets(new Insets(0, 0, 0, 0)).setToDrawBorderIfTabsHidden(false)
                .setTabDraggingEnabled(isMoveToGridActionEnabled()).setUiDecorator(null).getJBTabs();
        rebuildTabPopup();

        myTabs.getPresentation().setPaintBorder(0, 0, 0, 0).setPaintFocus(false)
                .setRequestFocusOnLastFocusedComponent(true);
        myTabs.getComponent().setBackground(myToolbar.getBackground());
        myTabs.getComponent().setBorder(new EmptyBorder(0, 1, 0, 0));

        final NonOpaquePanel wrappper = new NonOpaquePanel(new BorderLayout(0, 0));
        wrappper.add(myToolbar, BorderLayout.WEST);
        wrappper.add(myTabs.getComponent(), BorderLayout.CENTER);

        myComponent.setContent(wrappper);

        myTabs.addListener(new TabsListener.Adapter() {

            @Override
            public void beforeSelectionChanged(TabInfo oldSelection, TabInfo newSelection) {
                if (oldSelection != null && !isStateBeingRestored()) {
                    final GridImpl grid = getGridFor(oldSelection);
                    if (grid != null && getTabFor(grid) != null) {
                        grid.saveUiState();
                    }
                }
            }

            @Override
            public void tabsMoved() {
                saveUiState();
            }

            @Override
            public void selectionChanged(final TabInfo oldSelection, final TabInfo newSelection) {
                if (!myTabs.getComponent().isShowing())
                    return;

                if (newSelection != null) {
                    newSelection.stopAlerting();
                    getGridFor(newSelection).processAddToUi(false);
                }

                if (oldSelection != null) {
                    getGridFor(oldSelection).processRemoveFromUi();
                }
            }
        });
        myTabs.addTabMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(@NotNull MouseEvent e) {
                if (UIUtil.isCloseClick(e)) {
                    final TabInfo tabInfo = myTabs.findInfo(e);
                    final GridImpl grid = tabInfo == null ? null : getGridFor(tabInfo);
                    final Content[] contents = grid != null ? CONTENT_KEY.getData(grid) : null;
                    if (contents == null)
                        return;
                    // see GridCellImpl.closeOrMinimize as well
                    if (CloseViewAction.isEnabled(contents)) {
                        CloseViewAction.perform(RunnerContentUi.this, contents[0]);
                    } else if (MinimizeViewAction.isEnabled(RunnerContentUi.this, contents,
                            ViewContext.TAB_TOOLBAR_PLACE)) {
                        grid.getCellFor(contents[0]).minimize(contents[0]);
                    }
                }
            }
        });

        if (myOriginal != null) {
            final ContentManager manager = ContentFactory.SERVICE.getInstance().createContentManager(this, false,
                    myProject);
            Disposer.register((Disposable) myRunnerUi, manager);
            manager.getComponent();
        } else {
            final DockManager dockManager = DockManager.getInstance(myProject);
            if (dockManager != null) { //default project
                dockManager.register(this);
            }
        }
    }

    private void rebuildTabPopup() {
        initUi();

        myTabs.setPopupGroup(getCellPopupGroup(TAB_POPUP_PLACE), TAB_POPUP_PLACE, true);

        for (GridImpl each : getGrids()) {
            each.rebuildTabPopup();
        }
    }

    @Override
    public ActionGroup getCellPopupGroup(final String place) {
        final ActionGroup original = myTabPopupActions != null ? myTabPopupActions
                : (ActionGroup) myActionManager.getAction(VIEW_POPUP);
        final ActionGroup focusPlaceholder = (ActionGroup) myActionManager.getAction("Runner.Focus");

        DefaultActionGroup group = new DefaultActionGroup(VIEW_POPUP, original.isPopup());

        final AnActionEvent event = new AnActionEvent(null, DataManager.getInstance().getDataContext(), place,
                new Presentation(), ActionManager.getInstance(), 0);
        final AnAction[] originalActions = original.getChildren(event);

        for (final AnAction each : originalActions) {
            if (each == focusPlaceholder) {
                final AnAction[] focusActions = ((ActionGroup) each).getChildren(event);
                for (AnAction eachFocus : focusActions) {
                    group.add(eachFocus);
                }
                if (myAdditionalFocusActions != null) {
                    for (AnAction action : myAdditionalFocusActions.getChildren(event)) {
                        group.add(action);
                    }
                }
            } else {
                group.add(each);
            }
        }
        return group;
    }

    @Override
    public boolean isOriginal() {
        return myOriginal == null;
    }

    @Override
    public int getWindow() {
        return myWindow;
    }

    @Override
    public void propertyChange(@NotNull final PropertyChangeEvent evt) {
        Content content = (Content) evt.getSource();
        final GridImpl grid = getGridFor(content, false);
        if (grid == null)
            return;

        final GridCellImpl cell = grid.findCell(content);
        if (cell == null)
            return;

        final String property = evt.getPropertyName();
        if (Content.PROP_ALERT.equals(property)) {
            attract(content, true);
        } else if (Content.PROP_DISPLAY_NAME.equals(property) || Content.PROP_ICON.equals(property)
                || Content.PROP_ACTIONS.equals(property) || Content.PROP_DESCRIPTION.equals(property)) {
            cell.updateTabPresentation(content);
            updateTabsUI(false);
        }
    }

    public void processBounce(Content content, final boolean activate) {
        final GridImpl grid = getGridFor(content, false);
        if (grid == null)
            return;

        final GridCellImpl cell = grid.findCell(content);
        if (cell == null)
            return;

        final TabInfo tab = myTabs.findInfo(grid);
        if (tab == null)
            return;

        if (getSelectedGrid() != grid) {
            tab.setAlertIcon(content.getAlertIcon());
            if (activate) {
                tab.fireAlert();
            } else {
                tab.stopAlerting();
            }
        } else {
            grid.processAlert(content, activate);
        }
    }

    @Override
    public ActionCallback detachTo(int window, GridCell cell) {
        if (myOriginal != null) {
            return myOriginal.detachTo(window, cell);
        }
        RunnerContentUi target = null;
        if (window > 0) {
            for (RunnerContentUi child : myChildren) {
                if (child.myWindow == window) {
                    target = child;
                    break;
                }
            }
        }
        final GridCellImpl gridCell = (GridCellImpl) cell;
        final Content[] contents = gridCell.getContents();
        storeDefaultIndices(contents);
        for (Content content : contents) {
            content.putUserData(RunnerLayout.DROP_INDEX, getStateFor(content).getTab().getIndex());
        }
        Dimension size = gridCell.getSize();
        if (size == null) {
            size = new Dimension(200, 200);
        }
        final DockableGrid content = new DockableGrid(null, null, size, Arrays.asList(contents), window);
        if (target != null) {
            target.add(content, null);
        } else {
            Point location = gridCell.getLocation();
            if (location == null) {
                location = getComponent().getLocationOnScreen();
            }
            location.translate(size.width / 2, size.height / 2);
            getDockManager().createNewDockContainerFor(content, new RelativePoint(location));
        }
        return new ActionCallback.Done();
    }

    private void storeDefaultIndices(@NotNull Content[] contents) {
        //int i = 0;
        for (Content content : contents) {
            content.putUserData(RunnerLayout.DEFAULT_INDEX, getStateFor(content).getTab().getDefaultIndex());
            //content.putUserData(CONTENT_NUMBER, i++);
        }
    }

    @Override
    public RelativeRectangle getAcceptArea() {
        return new RelativeRectangle(myTabs.getComponent());
    }

    @Override
    public RelativeRectangle getAcceptAreaFallback() {
        return getAcceptArea();
    }

    @NotNull
    @Override
    public ContentResponse getContentResponse(@NotNull DockableContent content, RelativePoint point) {
        if (!(content instanceof DockableGrid)) {
            return ContentResponse.DENY;
        }
        final RunnerContentUi ui = ((DockableGrid) content).getOriginalRunnerUi();
        return ui.getProject() == myProject && ui.mySessionName.equals(mySessionName) ? ContentResponse.ACCEPT_MOVE
                : ContentResponse.DENY;
    }

    @Override
    public JComponent getComponent() {
        initUi();
        return myComponent;
    }

    @Override
    public JComponent getContainerComponent() {
        initUi();
        return myManager.getComponent();
    }

    @Override
    public void add(@NotNull DockableContent dockable, RelativePoint dropTarget) {
        final DockableGrid dockableGrid = (DockableGrid) dockable;
        final RunnerContentUi prev = dockableGrid.getRunnerUi();

        saveUiState();

        final List<Content> contents = dockableGrid.getContents();
        final boolean wasRestoring = myOriginal != null && myOriginal.isStateBeingRestored();
        setStateIsBeingRestored(true, this);
        try {
            final Point point = dropTarget != null ? dropTarget.getPoint(myComponent) : null;
            boolean hadGrid = !myTabs.shouldAddToGlobal(point);

            for (Content content : contents) {
                final View view = getStateFor(content);
                if (view.isMinimizedInGrid())
                    continue;
                prev.myManager.removeContent(content, false);
                myManager.removeContent(content, false);
                if (hadGrid && !wasRestoring) {
                    view.assignTab(getTabFor(getSelectedGrid()));
                    view.setPlaceInGrid(calcPlaceInGrid(point, myComponent.getSize()));
                } else if (contents.size() == 1 && !wasRestoring) {
                    view.assignTab(null);
                    view.setPlaceInGrid(myLayoutSettings.getDefaultGridPlace(content));
                }
                view.setWindow(myWindow);
                myManager.addContent(content);
            }
        } finally {
            setStateIsBeingRestored(false, this);
        }

        saveUiState();

        updateTabsUI(true);
    }

    @Override
    public void closeAll() {
        final Content[] contents = myManager.getContents();
        if (myOriginal != null) {
            for (Content content : contents) {
                getStateFor(content).setWindow(0);
                myOriginal.myManager.addContent(content);
                GridCell cell = myOriginal.findCellFor(content);
                if (cell != null) {
                    myOriginal.restoreContent(content.getUserData(ViewImpl.ID));
                    cell.minimize(content);
                }
            }
        }
        myManager.removeAllContents(false);
    }

    @Override
    public void addListener(final Listener listener, Disposable parent) {
        myDockingListeners.add(listener);
        Disposer.register(parent, new Disposable() {
            @Override
            public void dispose() {
                myDockingListeners.remove(listener);
            }
        });
    }

    @Override
    public boolean isEmpty() {
        return myTabs.isEmptyVisible() || myDisposing;
    }

    @Override
    public Image startDropOver(@NotNull DockableContent content, RelativePoint point) {
        return null;
    }

    @Override
    public Image processDropOver(@NotNull DockableContent dockable, RelativePoint dropTarget) {
        JBTabs current = getTabsAt(dockable, dropTarget);

        if (myCurrentOver != null && myCurrentOver != current) {
            resetDropOver(dockable);
        }

        if (myCurrentOver == null && current != null) {
            myCurrentOver = current;
            Presentation presentation = dockable.getPresentation();
            myCurrentOverInfo = new TabInfo(new JLabel("")).setText(presentation.getText())
                    .setIcon(presentation.getIcon());
            myCurrentOverImg = myCurrentOver.startDropOver(myCurrentOverInfo, dropTarget);
        }

        if (myCurrentOver != null) {
            myCurrentOver.processDropOver(myCurrentOverInfo, dropTarget);
        }

        if (myCurrentPainter == null) {
            myCurrentPainter = new MyDropAreaPainter();
            IdeGlassPaneUtil.find(myComponent).addPainter(myComponent, myCurrentPainter, this);
        }
        myCurrentPainter.processDropOver(this, dockable, dropTarget);

        return myCurrentOverImg;
    }

    @NotNull
    private static PlaceInGrid calcPlaceInGrid(Point point, Dimension size) {
        // 1/3 (left) |   (center/bottom) | 1/3 (right)
        if (point.x < size.width / 3)
            return PlaceInGrid.left;
        if (point.x > size.width * 2 / 3)
            return PlaceInGrid.right;

        // 3/4 (center with tab titles) | 1/4 (bottom)
        if (point.y > size.height * 3 / 4)
            return PlaceInGrid.bottom;

        return PlaceInGrid.center;
    }

    @Nullable
    private JBTabs getTabsAt(DockableContent content, RelativePoint point) {
        if (content instanceof DockableGrid) {
            final Point p = point.getPoint(getComponent());
            Component c = SwingUtilities.getDeepestComponentAt(getComponent(), p.x, p.y);
            while (c != null) {
                if (c instanceof JBRunnerTabs) {
                    return (JBTabs) c;
                }
                c = c.getParent();
            }
        }
        return null;
    }

    @Override
    public void resetDropOver(@NotNull DockableContent content) {
        if (myCurrentOver != null) {
            myCurrentOver.resetDropOver(myCurrentOverInfo);
            myCurrentOver = null;
            myCurrentOverInfo = null;
            myCurrentOverImg = null;

            IdeGlassPaneUtil.find(myComponent).removePainter(myCurrentPainter);
            myCurrentPainter = null;
        }
    }

    @Override
    public boolean isDisposeWhenEmpty() {
        return myOriginal != null;
    }

    @Override
    public boolean isCycleRoot() {
        return false;
    }

    @Override
    public void setManager(@NotNull final ContentManager manager) {
        assert myManager == null;

        myManager = manager;
        myManager.addContentManagerListener(new ContentManagerListener() {
            @Override
            public void contentAdded(final ContentManagerEvent event) {
                initUi();

                if (event.getContent().getUserData(LIGHTWEIGHT_CONTENT_MARKER) == Boolean.TRUE) {
                    myLayoutSettings.setLightWeight(event.getContent());
                    Disposer.register(event.getContent(), new Disposable() {
                        @Override
                        public void dispose() {
                            myLayoutSettings.clearStateFor(event.getContent());
                        }
                    });
                }

                GridImpl grid = getGridFor(event.getContent(), true);
                if (grid == null) {
                    return;
                }

                grid.add(event.getContent());

                if (getSelectedGrid() == grid) {
                    grid.processAddToUi(false);
                }

                if (myManager.getComponent().isShowing() && !isStateBeingRestored()) {
                    grid.restoreLastUiState();
                }

                updateTabsUI(false);

                event.getContent().addPropertyChangeListener(RunnerContentUi.this);
                fireContentOpened(event.getContent());
            }

            @Override
            public void contentRemoved(final ContentManagerEvent event) {
                event.getContent().removePropertyChangeListener(RunnerContentUi.this);

                GridImpl grid = (GridImpl) findGridFor(event.getContent());
                if (grid != null) {
                    grid.remove(event.getContent());
                    grid.processRemoveFromUi();
                    removeGridIfNeeded(grid);
                }
                updateTabsUI(false);
                fireContentClosed(event.getContent());
            }

            @Override
            public void contentRemoveQuery(final ContentManagerEvent event) {
            }

            @Override
            public void selectionChanged(final ContentManagerEvent event) {
                if (isStateBeingRestored())
                    return;

                if (event.getOperation() == ContentManagerEvent.ContentOperation.add) {
                    select(event.getContent(), false);
                }
            }
        });
    }

    @Nullable
    private GridImpl getSelectedGrid() {
        TabInfo selection = myTabs.getSelectedInfo();
        return selection != null ? getGridFor(selection) : null;
    }

    private void removeGridIfNeeded(GridImpl grid) {
        if (grid.isEmpty()) {
            myTabs.removeTab(myTabs.findInfo(grid));
            myMinimizedButtonsPlaceholder.remove(grid);
            myCommonActionsPlaceholder.remove(grid);
            Disposer.dispose(grid);
        }
    }

    @Nullable
    private GridImpl getGridFor(@NotNull Content content, boolean createIfMissing) {
        GridImpl grid = (GridImpl) findGridFor(content);
        if (grid != null || !createIfMissing)
            return grid;

        grid = new GridImpl(this, mySessionName);

        if (myCurrentOver != null || myOriginal != null) {
            Integer forcedDropIndex = content.getUserData(RunnerLayout.DROP_INDEX);
            final int index = myTabs.getDropInfoIndex()
                    + (myOriginal != null ? myOriginal.getTabOffsetFor(this) : 0);
            final int dropIndex = forcedDropIndex != null ? forcedDropIndex : index;
            if (forcedDropIndex == null) {
                moveFollowingTabs(dropIndex);
            }
            final int defaultIndex = content.getUserData(RunnerLayout.DEFAULT_INDEX);
            final TabImpl tab = myLayoutSettings.getOrCreateTab(forcedDropIndex != null ? forcedDropIndex : -1);
            tab.setDefaultIndex(defaultIndex);
            tab.setIndex(dropIndex);
            getStateFor(content).assignTab(tab);
            content.putUserData(RunnerLayout.DROP_INDEX, null);
            content.putUserData(RunnerLayout.DEFAULT_INDEX, null);
        }

        TabInfo tab = new TabInfo(grid).setObject(getStateFor(content).getTab()).setText("Tab");

        Wrapper left = new Wrapper();
        myCommonActionsPlaceholder.put(grid, left);

        Wrapper minimizedToolbar = new Wrapper();
        myMinimizedButtonsPlaceholder.put(grid, minimizedToolbar);

        final Wrapper searchComponent = new Wrapper();
        if (content.getSearchComponent() != null) {
            searchComponent.setContent(content.getSearchComponent());
        }

        TwoSideComponent right = new TwoSideComponent(searchComponent, minimizedToolbar);

        NonOpaquePanel sideComponent = new TwoSideComponent(left, right);

        tab.setSideComponent(sideComponent);

        tab.setTabLabelActions((ActionGroup) myActionManager.getAction(VIEW_TOOLBAR), TAB_TOOLBAR_PLACE);

        myTabs.addTab(tab);
        myTabs.sortTabs(myTabsComparator);

        return grid;
    }

    private void moveFollowingTabs(int index) {
        if (myOriginal != null) {
            myOriginal.moveFollowingTabs(index);
            return;
        }
        moveFollowingTabs(index, myTabs);
        for (RunnerContentUi child : myChildren) {
            moveFollowingTabs(index, child.myTabs);
        }
    }

    public ActionGroup getSettingsActions() {
        return (ActionGroup) myActionManager.getAction(SETTINGS);
    }

    public ContentManager getContentManager(Content content) {
        if (hasContent(myManager, content)) {
            return myManager;
        }
        for (RunnerContentUi child : myChildren) {
            if (hasContent(child.myManager, content)) {
                return child.myManager;
            }
        }
        return myManager;
    }

    private static boolean hasContent(ContentManager manager, Content content) {
        for (Content c : manager.getContents()) {
            if (c == content) {
                return true;
            }
        }
        return false;
    }

    private static void moveFollowingTabs(int index, final JBRunnerTabs tabs) {
        for (TabInfo info : tabs.getTabs()) {
            TabImpl tab = getTabFor(info);
            if (tab != null) {
                int tabIndex = tab.getIndex();
                if (tabIndex >= index) {
                    tab.setIndex(tabIndex + 1);
                }
            }
        }
    }

    private int getTabOffsetFor(RunnerContentUi ui) {
        int offset = myTabs.getTabCount();
        for (RunnerContentUi child : myChildren) {
            if (child == ui)
                break;
            offset += child.myTabs.getTabCount();
        }
        return offset;
    }

    @Override
    @Nullable
    public GridCell findCellFor(@NotNull final Content content) {
        GridImpl cell = getGridFor(content, false);
        return cell != null ? cell.getCellFor(content) : null;
    }

    private boolean rebuildToolbar() {
        boolean hasToolbarContent = rebuildCommonActions();
        hasToolbarContent |= rebuildMinimizedActions();
        return hasToolbarContent;
    }

    private boolean rebuildCommonActions() {
        boolean hasToolbarContent = false;
        for (Map.Entry<GridImpl, Wrapper> entry : myCommonActionsPlaceholder.entrySet()) {
            Wrapper eachPlaceholder = entry.getValue();
            List<Content> contentList = entry.getKey().getContents();

            Set<Content> contents = new HashSet<Content>();
            contents.addAll(contentList);

            DefaultActionGroup groupToBuild;
            JComponent contextComponent = null;
            if (isHorizontalToolbar() && contents.size() == 1) {
                Content content = contentList.get(0);
                groupToBuild = new DefaultActionGroup();
                if (content.getActions() != null) {
                    groupToBuild.addAll(content.getActions());
                    groupToBuild.addSeparator();
                    contextComponent = content.getActionsContextComponent();
                }
                groupToBuild.addAll(myTopActions);
            } else {
                final DefaultActionGroup group = new DefaultActionGroup();
                group.addAll(myTopActions);
                groupToBuild = group;
            }

            final AnAction[] actions = groupToBuild.getChildren(null);
            if (!Arrays.equals(actions, myContextActions.get(entry.getKey()))) {
                ActionToolbar tb = myActionManager.createActionToolbar(myActionsPlace, groupToBuild, true);
                tb.getComponent().setBorder(null);
                tb.setTargetComponent(contextComponent);
                eachPlaceholder.setContent(tb.getComponent());
            }

            if (groupToBuild.getChildrenCount() > 0) {
                hasToolbarContent = true;
            }

            myContextActions.put(entry.getKey(), actions);
        }

        return hasToolbarContent;
    }

    private boolean rebuildMinimizedActions() {
        for (Map.Entry<GridImpl, Wrapper> entry : myMinimizedButtonsPlaceholder.entrySet()) {
            Wrapper eachPlaceholder = entry.getValue();
            ActionToolbar tb = myActionManager.createActionToolbar(ActionPlaces.DEBUGGER_TOOLBAR,
                    myMinimizedViewActions, true);
            tb.getComponent().setBorder(null);
            tb.setReservePlaceAutoPopupIcon(false);
            JComponent minimized = tb.getComponent();
            eachPlaceholder.setContent(minimized);
        }

        myTabs.getComponent().revalidate();
        myTabs.getComponent().repaint();

        return myMinimizedViewActions.getChildrenCount() > 0;
    }

    private void updateTabsUI(final boolean validateNow) {
        boolean hasToolbarContent = rebuildToolbar();

        Set<String> usedNames = new HashSet<String>();
        List<TabInfo> tabs = myTabs.getTabs();
        for (TabInfo each : tabs) {
            hasToolbarContent |= updateTabUI(each, usedNames);
        }
        int tabsCount = tabs.size();
        for (RunnerContentUi child : myChildren) {
            tabsCount += child.myTabs.getTabCount();
        }
        myTabs.getPresentation().setHideTabs(!hasToolbarContent && tabsCount <= 1 && myOriginal == null);
        myTabs.updateTabActions(validateNow);

        if (validateNow) {
            myTabs.sortTabs(myTabsComparator);
        }
    }

    private boolean updateTabUI(TabInfo tab, Set<String> usedNames) {
        TabImpl t = getTabFor(tab);
        if (t == null) {
            return false;
        }

        Icon icon = t.getIcon();

        GridImpl grid = getGridFor(tab);
        boolean hasToolbarContent = grid.updateGridUI();

        List<Content> contents = grid.getContents();
        String title = contents.size() > 1 ? t.getDisplayName() : null;
        if (title == null) {
            final String name = myLayoutSettings.getDefaultDisplayName(t.getDefaultIndex());
            if (name != null && contents.size() > 1 && !usedNames.contains(name)) {
                title = name;
            } else {
                title = StringUtil.join(contents, new NotNullFunction<Content, String>() {
                    @NotNull
                    @Override
                    public String fun(Content dom) {
                        return dom.getTabName();
                    }
                }, " | ");
            }
        }
        usedNames.add(title);

        boolean hidden = true;
        for (Content content : contents) {
            if (!grid.isMinimized(content)) {
                hidden = false;
                break;
            }
        }
        tab.setHidden(hidden);
        if (icon == null && contents.size() == 1) {
            icon = contents.get(0).getIcon();
        }

        tab.setDragOutDelegate(myTabs.getTabs().size() > 1 || !isOriginal() ? myDragOutDelegate : null);

        Tab gridTab = grid.getTab();
        tab.setText(title).setIcon(gridTab != null && gridTab.isDefault() && contents.size() > 1 ? null : icon);

        return hasToolbarContent;
    }

    private ActionCallback restoreLastUiState() {
        if (isStateBeingRestored())
            return new ActionCallback.Rejected();

        try {
            setStateIsBeingRestored(true, this);

            List<TabInfo> tabs = new ArrayList<TabInfo>();
            tabs.addAll(myTabs.getTabs());

            final ActionCallback result = new ActionCallback(tabs.size());

            for (TabInfo each : tabs) {
                getGridFor(each).restoreLastUiState().notifyWhenDone(result);
            }

            return result;
        } finally {
            setStateIsBeingRestored(false, this);
        }
    }

    @Override
    public void saveUiState() {
        if (isStateBeingRestored())
            return;

        if (myOriginal != null) {
            myOriginal.saveUiState();
            return;
        }
        int offset = updateTabsIndices(myTabs, 0);
        for (RunnerContentUi child : myChildren) {
            offset = updateTabsIndices(child.myTabs, offset);
        }

        doSaveUiState();
    }

    private static int updateTabsIndices(final JBRunnerTabs tabs, int offset) {
        for (TabInfo each : tabs.getTabs()) {
            final int index = tabs.getIndexOf(each);
            final TabImpl tab = getTabFor(each);
            if (tab != null)
                tab.setIndex(index >= 0 ? index + offset : index);
        }
        return offset + tabs.getTabCount();
    }

    private void doSaveUiState() {
        if (isStateBeingRestored())
            return;

        for (TabInfo each : myTabs.getTabs()) {
            GridImpl eachGrid = getGridFor(each);
            eachGrid.saveUiState();
        }

        for (RunnerContentUi child : myChildren) {
            child.doSaveUiState();
        }
    }

    @Override
    @Nullable
    public Tab getTabFor(final Grid grid) {
        TabInfo info = myTabs.findInfo((Component) grid);
        return getTabFor(info);
    }

    @Override
    public void showNotify() {
        final Window window = SwingUtilities.getWindowAncestor(myComponent);
        if (window instanceof IdeFrame.Child) {
            ((IdeFrame.Child) window).setFrameTitle(mySessionName);
        }
    }

    @Override
    public void hideNotify() {
    }

    @Nullable
    private static TabImpl getTabFor(@Nullable final TabInfo tab) {
        if (tab == null) {
            return null;
        }
        return (TabImpl) tab.getObject();
    }

    private static GridImpl getGridFor(TabInfo tab) {
        return (GridImpl) tab.getComponent();
    }

    @Override
    @Nullable
    public Grid findGridFor(@NotNull Content content) {
        TabImpl tab = (TabImpl) getStateFor(content).getTab();
        for (TabInfo each : myTabs.getTabs()) {
            TabImpl t = getTabFor(each);
            if (t != null && t.equals(tab))
                return getGridFor(each);
        }

        return null;
    }

    private ArrayList<GridImpl> getGrids() {
        ArrayList<GridImpl> result = new ArrayList<GridImpl>();
        for (TabInfo each : myTabs.getTabs()) {
            result.add(getGridFor(each));
        }
        return result;
    }

    public void setHorizontalToolbar(final boolean state) {
        myLayoutSettings.setToolbarHorizontal(state);
        for (GridImpl each : getGrids()) {
            each.setToolbarHorizontal(state);
        }

        myContextActions.clear();
        updateTabsUI(false);
    }

    @Override
    public boolean isSingleSelection() {
        return false;
    }

    @Override
    public boolean isToSelectAddedContent() {
        return false;
    }

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

    @Override
    public void beforeDispose() {
        if (myOriginal != null) {
            myDisposing = true;
            fireContentClosed(null);
        }
    }

    @Override
    public boolean canChangeSelectionTo(@NotNull Content content, boolean implicit) {
        if (implicit) {
            GridImpl grid = getGridFor(content, false);
            if (grid != null) {
                return !grid.isMinimized(content);
            }
        }

        return true;
    }

    @NotNull
    @Override
    public String getCloseActionName() {
        return UIBundle.message("tabbed.pane.close.tab.action.name");
    }

    @NotNull
    @Override
    public String getCloseAllButThisActionName() {
        return UIBundle.message("tabbed.pane.close.all.tabs.but.this.action.name");
    }

    @NotNull
    @Override
    public String getPreviousContentActionName() {
        return "Select Previous Tab";
    }

    @NotNull
    @Override
    public String getNextContentActionName() {
        return "Select Next Tab";
    }

    @Override
    public void dispose() {
        if (myOriginal != null) {
            myOriginal.myChildren.remove(this);
        }
        myMinimizedButtonsPlaceholder.clear();
        myCommonActionsPlaceholder.clear();
        myContextActions.clear();

        myOriginal = null;
        myTopActions = null;
        myAdditionalFocusActions = null;
        myLeftToolbarActions = null;
    }

    @Override
    public void restoreLayout() {
        final RunnerContentUi[] children = myChildren.toArray(new RunnerContentUi[myChildren.size()]);
        final List<Content> contents = new ArrayList<Content>();
        Collections.addAll(contents, myManager.getContents());
        for (RunnerContentUi child : children) {
            Collections.addAll(contents, child.myManager.getContents());
        }
        for (AnAction action : myMinimizedViewActions.getChildren(null)) {
            final Content content = ((RestoreViewAction) action).getContent();
            contents.add(content);
        }
        Content[] all = contents.toArray(new Content[contents.size()]);
        Arrays.sort(all, new Comparator<Content>() {
            @Override
            public int compare(@NotNull Content content, @NotNull Content content1) {
                final int i = getStateFor(content).getTab().getDefaultIndex();
                final int i1 = getStateFor(content1).getTab().getDefaultIndex();
                return i - i1;
            }
        });

        setStateIsBeingRestored(true, this);
        try {
            for (RunnerContentUi child : children) {
                child.myManager.removeAllContents(false);
            }
            myManager.removeAllContents(false);
            myMinimizedViewActions.removeAll();
        } finally {
            setStateIsBeingRestored(false, this);
        }

        myLayoutSettings.resetToDefault();
        for (Content each : all) {
            myManager.addContent(each);
        }

        updateTabsUI(true);
    }

    @Override
    public boolean isStateBeingRestored() {
        return !myRestoreStateRequestors.isEmpty();
    }

    @Override
    public void setStateIsBeingRestored(final boolean restoredNow, final Object requestor) {
        if (restoredNow) {
            myRestoreStateRequestors.add(requestor);
        } else {
            myRestoreStateRequestors.remove(requestor);
        }
    }

    public ActionGroup getLayoutActions() {
        return (ActionGroup) myActionManager.getAction(LAYOUT);
    }

    public void updateActionsImmediately() {
        if (myToolbar.getTargetComponent() instanceof ActionToolbar) {
            ((ActionToolbar) myToolbar.getTargetComponent()).updateActionsImmediately();
        }
    }

    public void setMinimizeActionEnabled(final boolean enabled) {
        myMinimizeActionEnabled = enabled;
    }

    @SuppressWarnings("SpellCheckingInspection")
    public void setMovetoGridActionEnabled(final boolean enabled) {
        myMoveToGridActionEnabled = enabled;
    }

    @Override
    public boolean isMinimizeActionEnabled() {
        return myMinimizeActionEnabled && myOriginal == null;
    }

    @Override
    public boolean isMoveToGridActionEnabled() {
        return myMoveToGridActionEnabled;
    }

    public void setPolicy(String contentId, final LayoutAttractionPolicy policy) {
        myAttractions.put(contentId, policy);
    }

    public void setConditionPolicy(final String condition, final LayoutAttractionPolicy policy) {
        myConditionAttractions.put(condition, policy);
    }

    private static LayoutAttractionPolicy getOrCreatePolicyFor(String key, Map<String, LayoutAttractionPolicy> map,
            LayoutAttractionPolicy defaultPolicy) {
        LayoutAttractionPolicy policy = map.get(key);
        if (policy == null) {
            policy = defaultPolicy;
            map.put(key, policy);
        }
        return policy;
    }

    @Nullable
    public Content findContent(final String key) {
        final ContentManager manager = getContentManager();
        if (manager == null || key == null)
            return null;

        Content[] contents = manager.getContents();
        for (Content content : contents) {
            String kind = content.getUserData(ViewImpl.ID);
            if (key.equals(kind)) {
                return content;
            }
        }

        return null;
    }

    public void restoreContent(final String key) {
        for (AnAction action : myMinimizedViewActions.getChildren(null)) {
            Content content = ((RestoreViewAction) action).getContent();
            if (key.equals(content.getUserData(ViewImpl.ID))) {
                action.actionPerformed(null);
                return;
            }
        }
    }

    public void setToDisposeRemovedContent(final boolean toDispose) {
        myToDisposeRemovedContent = toDispose;
    }

    @Override
    public boolean isToDisposeRemovedContent() {
        return myToDisposeRemovedContent;
    }

    private static class MyDropAreaPainter extends AbstractPainter {
        private Shape myBoundingBox;
        private final Color myColor = ColorUtil.mix(JBColor.BLUE, JBColor.WHITE, .3);

        @Override
        public boolean needsRepaint() {
            return myBoundingBox != null;
        }

        @Override
        public void executePaint(Component component, Graphics2D g) {
            if (myBoundingBox == null)
                return;
            GraphicsUtil.setupAAPainting(g);
            g.setColor(ColorUtil.toAlpha(myColor, 200));
            g.setStroke(new BasicStroke(2));
            g.draw(myBoundingBox);
            g.setColor(ColorUtil.toAlpha(myColor, 40));
            g.fill(myBoundingBox);
        }

        private void processDropOver(RunnerContentUi ui, DockableContent dockable, RelativePoint dropTarget) {
            myBoundingBox = null;
            setNeedsRepaint(true);

            if (!(dockable instanceof DockableGrid))
                return;

            JComponent component = ui.myComponent;
            Point point = dropTarget != null ? dropTarget.getPoint(component) : null;

            // do not paint anything if adding to the top
            if (ui.myTabs.shouldAddToGlobal(point))
                return;

            // calc target place-in-grid
            PlaceInGrid targetPlaceInGrid = null;
            for (Content c : ((DockableGrid) dockable).getContents()) {
                View view = ui.getStateFor(c);
                if (view.isMinimizedInGrid())
                    continue;
                PlaceInGrid defaultGridPlace = ui.getLayoutSettings().getDefaultGridPlace(c);
                targetPlaceInGrid = point == null ? defaultGridPlace : calcPlaceInGrid(point, component.getSize());
                break;
            }
            if (targetPlaceInGrid == null)
                return;

            // calc the default rectangle for the targetPlaceInGrid "area"
            Dimension size = component.getSize();
            Rectangle r = new Rectangle(size);
            switch (targetPlaceInGrid) {
            case left:
                r.width /= 3;
                break;
            case center:
                r.width /= 3;
                r.x += r.width;
                break;
            case right:
                r.width /= 3;
                r.x += 2 * r.width;
                break;
            case bottom:
                r.height /= 4;
                r.y += 3 * r.height;
                break;
            }
            // adjust the rectangle if the target grid cell is already present and showing
            for (Content c : ui.getContentManager().getContents()) {
                GridCell cellFor = ui.findCellFor(c);
                PlaceInGrid placeInGrid = cellFor == null ? null : ((GridCellImpl) cellFor).getPlaceInGrid();
                if (placeInGrid != targetPlaceInGrid)
                    continue;
                Wrapper wrapper = UIUtil.getParentOfType(Wrapper.class, c.getComponent());
                JComponent cellWrapper = wrapper == null ? null : (JComponent) wrapper.getParent();
                if (cellWrapper == null || !cellWrapper.isShowing())
                    continue;
                r = new RelativeRectangle(cellWrapper).getRectangleOn(component);
                break;
            }
            myBoundingBox = new RoundRectangle2D.Double(r.x, r.y, r.width, r.height, 16, 16);
        }
    }

    private class MyComponent extends Wrapper.FocusHolder implements DataProvider, QuickActionProvider {
        private boolean myWasEverAdded;

        public MyComponent() {
            setOpaque(true);
            setFocusCycleRoot(true);
            setBorder(new ToolWindow.Border(false, false, false, false));
        }

        @Override
        @Nullable
        public Object getData(@NonNls final String dataId) {
            if (KEY.is(dataId)) {
                return RunnerContentUi.this;
            }

            ContentManager originalContentManager = myOriginal == null ? null : myOriginal.getContentManager();
            JComponent originalContentComponent = originalContentManager == null ? null
                    : originalContentManager.getComponent();
            if (originalContentComponent instanceof DataProvider) {
                return ((DataProvider) originalContentComponent).getData(dataId);
            }
            return null;
        }

        @SuppressWarnings("NullableProblems")
        @Override
        public String getName() {
            return RunnerContentUi.this.getName();
        }

        @Override
        public List<AnAction> getActions(boolean originalProvider) {
            return RunnerContentUi.this.getActions(originalProvider);
        }

        @Override
        public JComponent getComponent() {
            return RunnerContentUi.this.getComponent();
        }

        @Override
        public boolean isCycleRoot() {
            return RunnerContentUi.this.isCycleRoot();
        }

        @Override
        public void addNotify() {
            super.addNotify();

            if (!myUiLastStateWasRestored && myOriginal == null) {
                myUiLastStateWasRestored = true;

                // [kirillk] this is done later since restoreUiState doesn't work properly in the addNotify call chain
                //todo to investigate and to fix (may cause extra flickering)
                //noinspection SSBasedInspection
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        restoreLastUiState().doWhenDone(new Runnable() {
                            @Override
                            public void run() {
                                if (!myWasEverAdded) {
                                    myWasEverAdded = true;
                                    attractOnStartup();
                                    myInitialized.setDone();
                                }
                            }
                        });
                    }
                });
            }
        }

        @Override
        public void removeNotify() {
            super.removeNotify();
            if (!ScreenUtil.isStandardAddRemoveNotify(this))
                return;

            if (Disposer.isDisposed(RunnerContentUi.this))
                return;

            saveUiState();
        }
    }

    @SuppressWarnings({ "SSBasedInspection" })
    // [kirillk] this is done later since "startup" attractions should be done gently, only if no explicit calls are done
    private void attractOnStartup() {
        final int currentCount = myAttractionCount;
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                if (currentCount < myAttractionCount)
                    return;
                attractByCondition(LayoutViewOptions.STARTUP, false);
            }
        });
    }

    public void attract(final Content content, boolean afterInitialized) {
        processAttraction(content.getUserData(ViewImpl.ID), myAttractions, new LayoutAttractionPolicy.Bounce(),
                afterInitialized, true);
    }

    public void attractByCondition(@NotNull String condition, boolean afterInitialized) {
        processAttraction(myLayoutSettings.getToFocus(condition), myConditionAttractions,
                myLayoutSettings.getAttractionPolicy(condition), afterInitialized, true);
    }

    public void clearAttractionByCondition(String condition, boolean afterInitialized) {
        processAttraction(myLayoutSettings.getToFocus(condition), myConditionAttractions,
                new LayoutAttractionPolicy.FocusOnce(), afterInitialized, false);
    }

    private void processAttraction(final String contentId, final Map<String, LayoutAttractionPolicy> policyMap,
            final LayoutAttractionPolicy defaultPolicy, final boolean afterInitialized, final boolean activate) {
        IdeFocusManager.getInstance(getProject()).doWhenFocusSettlesDown(new Runnable() {
            @Override
            public void run() {
                myInitialized.processOnDone(new Runnable() {
                    @Override
                    public void run() {
                        Content content = findContent(contentId);
                        if (content == null)
                            return;

                        final LayoutAttractionPolicy policy = getOrCreatePolicyFor(contentId, policyMap,
                                defaultPolicy);
                        if (activate) {
                            // See IDEA-93683, bounce attraction should not disable further focus attraction
                            if (!(policy instanceof LayoutAttractionPolicy.Bounce)) {
                                myAttractionCount++;
                            }
                            policy.attract(content, myRunnerUi);
                        } else {
                            policy.clearAttraction(content, myRunnerUi);
                        }
                    }
                }, afterInitialized);
            }
        });
    }

    public static boolean ensureValid(JComponent c) {
        if (c.getRootPane() == null)
            return false;

        Container eachParent = c.getParent();
        while (eachParent != null && eachParent.isValid()) {
            eachParent = eachParent.getParent();
        }

        if (eachParent == null) {
            eachParent = c.getRootPane();
        }

        eachParent.validate();

        return true;
    }

    public ContentUI getContentUI() {
        return this;
    }

    @Override
    public void minimize(final Content content, final CellTransform.Restore restore) {
        final Ref<AnAction> restoreAction = new Ref<AnAction>();
        myManager.removeContent(content, false);
        restoreAction.set(new RestoreViewAction(content, new CellTransform.Restore() {
            @Override
            public ActionCallback restoreInGrid() {
                myMinimizedViewActions.remove(restoreAction.get());
                final GridImpl grid = getGridFor(content, false);
                if (grid == null) {
                    getStateFor(content).assignTab(myLayoutSettings.getOrCreateTab(-1));
                } else {
                    //noinspection ConstantConditions
                    ((GridCellImpl) findCellFor(content)).restore(content);
                }
                getStateFor(content).setMinimizedInGrid(false);
                myManager.addContent(content);
                saveUiState();
                select(content, true);
                updateTabsUI(false);
                return new ActionCallback.Done();
            }
        }));

        myMinimizedViewActions.add(restoreAction.get());

        saveUiState();
        updateTabsUI(false);
    }

    @Override
    public Project getProject() {
        return myProject;
    }

    @Override
    public CellTransform.Facade getCellTransform() {
        return this;
    }

    @Override
    public ContentManager getContentManager() {
        return myManager;
    }

    @NotNull
    @Override
    public ActionManager getActionManager() {
        return myActionManager;
    }

    @Override
    public RunnerLayout getLayoutSettings() {
        return myLayoutSettings;
    }

    @Override
    public View getStateFor(@NotNull final Content content) {
        return myLayoutSettings.getStateFor(content);
    }

    public boolean isHorizontalToolbar() {
        return myLayoutSettings.isToolbarHorizontal();
    }

    @Override
    public ActionCallback select(final Content content, final boolean requestFocus) {
        final GridImpl grid = (GridImpl) findGridFor(content);
        if (grid == null)
            return new ActionCallback.Done();

        final TabInfo info = myTabs.findInfo(grid);
        if (info == null)
            return new ActionCallback.Done();

        final ActionCallback result = new ActionCallback();
        myTabs.select(info, false).doWhenDone(new Runnable() {
            @Override
            public void run() {
                grid.select(content, requestFocus).notifyWhenDone(result);
            }
        });

        return result;
    }

    @Override
    public void validate(Content content, final ActiveRunnable toRestore) {
        final TabInfo current = myTabs.getSelectedInfo();
        myTabs.getPresentation().setPaintBlocked(true, true);

        select(content, false).doWhenDone(new Runnable() {
            @Override
            public void run() {
                myTabs.getComponent().validate();
                toRestore.run().doWhenDone(new Runnable() {
                    @Override
                    public void run() {
                        assert current != null;
                        myTabs.select(current, true);
                        myTabs.getPresentation().setPaintBlocked(false, true);
                    }
                });
            }
        });
    }

    private static class TwoSideComponent extends NonOpaquePanel {
        private TwoSideComponent(JComponent left, JComponent right) {
            setLayout(new CommonToolbarLayout(left, right));
            add(left);
            add(right);
        }
    }

    private static class CommonToolbarLayout extends AbstractLayoutManager {
        private final JComponent myLeft;
        private final JComponent myRight;

        public CommonToolbarLayout(final JComponent left, final JComponent right) {
            myLeft = left;
            myRight = right;
        }

        @Override
        public Dimension preferredLayoutSize(@NotNull final Container parent) {

            Dimension size = new Dimension();
            Dimension leftSize = myLeft.getPreferredSize();
            Dimension rightSize = myRight.getPreferredSize();

            size.width = leftSize.width + rightSize.width;
            size.height = Math.max(leftSize.height, rightSize.height);

            return size;
        }

        @Override
        public void layoutContainer(@NotNull final Container parent) {
            Dimension size = parent.getSize();
            Dimension prefSize = parent.getPreferredSize();
            if (prefSize.width <= size.width) {
                myLeft.setBounds(0, 0, myLeft.getPreferredSize().width, parent.getHeight());
                Dimension rightSize = myRight.getPreferredSize();
                myRight.setBounds(parent.getWidth() - rightSize.width, 0, rightSize.width, parent.getHeight());
            } else {
                Dimension leftMinSize = myLeft.getMinimumSize();
                Dimension rightMinSize = myRight.getMinimumSize();

                int delta = (prefSize.width - size.width) / 2;

                myLeft.setBounds(0, 0, myLeft.getPreferredSize().width - delta, parent.getHeight());
                int rightX = (int) myLeft.getBounds().getMaxX();
                int rightWidth = size.width - rightX;
                if (rightWidth < rightMinSize.width) {
                    Dimension leftSize = myLeft.getSize();
                    int diffToRightMin = rightMinSize.width - rightWidth;
                    if (leftSize.width - diffToRightMin >= leftMinSize.width) {
                        leftSize.width -= diffToRightMin;
                        myLeft.setSize(leftSize);
                    }
                }

                myRight.setBounds((int) myLeft.getBounds().getMaxX(), 0, parent.getWidth() - myLeft.getWidth(),
                        parent.getHeight());
            }

            toMakeVerticallyInCenter(myLeft, parent);
            toMakeVerticallyInCenter(myRight, parent);
        }

        private static void toMakeVerticallyInCenter(JComponent comp, Container parent) {
            final Rectangle compBounds = comp.getBounds();
            int compHeight = comp.getPreferredSize().height;
            final int parentHeight = parent.getHeight();
            if (compHeight > parentHeight) {
                compHeight = parentHeight;
            }

            int y = (int) Math.floor(parentHeight / 2.0 - compHeight / 2.0);
            comp.setBounds(compBounds.x, y, compBounds.width, compHeight);
        }
    }

    @Override
    public IdeFocusManager getFocusManager() {
        return myFocusManager;
    }

    @Override
    public RunnerLayoutUi getRunnerLayoutUi() {
        return myRunnerUi;
    }

    @Override
    public String getName() {
        return mySessionName;
    }

    @Override
    public List<AnAction> getActions(boolean originalProvider) {
        ArrayList<AnAction> result = new ArrayList<AnAction>();
        if (myLeftToolbarActions != null) {
            AnAction[] kids = myLeftToolbarActions.getChildren(null);
            ContainerUtil.addAll(result, kids);
        }
        return result;
    }

    @Override
    public SwitchTarget getCurrentTarget() {
        Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
        if (owner == null)
            return myTabs.getCurrentTarget();

        GridImpl grid = getSelectedGrid();
        if (grid != null && grid.getContents().size() <= 1)
            return myTabs.getCurrentTarget();

        if (grid != null) {
            SwitchTarget cell = grid.getCellFor(owner);
            return cell != null ? cell : myTabs.getCurrentTarget();
        } else {
            return myTabs.getCurrentTarget();
        }
    }

    @Override
    public List<SwitchTarget> getTargets(boolean onlyVisible, boolean originalProvider) {
        List<SwitchTarget> result = new ArrayList<SwitchTarget>();

        result.addAll(myTabs.getTargets(true, false));
        GridImpl grid = getSelectedGrid();
        if (grid != null) {
            result.addAll(grid.getTargets(onlyVisible));
        }

        for (Wrapper wrapper : myMinimizedButtonsPlaceholder.values()) {
            if (!wrapper.isShowing())
                continue;
            JComponent target = wrapper.getTargetComponent();
            if (target instanceof ActionToolbar) {
                ActionToolbar tb = (ActionToolbar) target;
                result.addAll(tb.getTargets(onlyVisible, false));
            }
        }

        return result;
    }

    private int findFreeWindow() {
        int i;
        for (i = 1; i < Integer.MAX_VALUE; i++) {
            if (!isUsed(i)) {
                return i;
            }
        }
        return i;
    }

    private boolean isUsed(int i) {
        for (RunnerContentUi child : myChildren) {
            if (child.getWindow() == i) {
                return true;
            }
        }
        return false;
    }

    private DockManagerImpl getDockManager() {
        return (DockManagerImpl) DockManager.getInstance(myProject);
    }

    class MyDragOutDelegate implements TabInfo.DragOutDelegate {
        private DragSession mySession;

        @Override
        public void dragOutStarted(MouseEvent mouseEvent, TabInfo info) {
            JComponent component = info.getComponent();
            Content[] data = CONTENT_KEY.getData((DataProvider) component);
            assert data != null;
            storeDefaultIndices(data);

            final Dimension size = info.getComponent().getSize();
            final Image image = JBTabsImpl.getComponentImage(info);
            if (component instanceof Grid) {
                info.setHidden(true);
            }

            Presentation presentation = new Presentation(info.getText());
            presentation.setIcon(info.getIcon());
            mySession = getDockManager().createDragSession(mouseEvent,
                    new DockableGrid(image, presentation, size, Arrays.asList(data), 0));
        }

        @Override
        public void processDragOut(MouseEvent event, TabInfo source) {
            mySession.process(event);
        }

        @Override
        public void dragOutFinished(MouseEvent event, TabInfo source) {
            final Component component = event.getComponent();
            final IdeFrame window = UIUtil.getParentOfType(IdeFrame.class, component);
            mySession.process(event);
            mySession = null;
        }

        @Override
        public void dragOutCancelled(TabInfo source) {
            source.setHidden(false);
            mySession.cancel();
            mySession = null;
        }
    }

    class DockableGrid implements DockableContent<List<Content>> {
        private final Image myImg;
        private final Presentation myPresentation;
        private final Dimension myPreferredSize;
        private final List<Content> myContents;
        private final int myWindow;

        public DockableGrid(Image img, Presentation presentation, final Dimension size, List<Content> contents,
                int window) {
            myImg = img;
            myPresentation = presentation;
            myPreferredSize = size;
            myContents = contents;
            myWindow = window;
        }

        @NotNull
        @Override
        public List<Content> getKey() {
            return myContents;
        }

        @Override
        public Image getPreviewImage() {
            return myImg;
        }

        @Override
        public Dimension getPreferredSize() {
            return myPreferredSize;
        }

        @Override
        public String getDockContainerType() {
            return DockableGridContainerFactory.TYPE;
        }

        @Override
        public Presentation getPresentation() {
            return myPresentation;
        }

        public RunnerContentUi getRunnerUi() {
            return RunnerContentUi.this;
        }

        public RunnerContentUi getOriginalRunnerUi() {
            return myOriginal != null ? myOriginal : RunnerContentUi.this;
        }

        @NotNull
        public List<Content> getContents() {
            return myContents;
        }

        @Override
        public void close() {
        }

        public int getWindow() {
            return myWindow;
        }
    }

    void fireContentOpened(Content content) {
        for (Listener each : myDockingListeners) {
            each.contentAdded(content);
        }
    }

    void fireContentClosed(Content content) {
        for (Listener each : myDockingListeners) {
            each.contentRemoved(content);
        }
    }
}