com.android.tools.idea.uibuilder.editor.NlActionManager.java Source code

Java tutorial

Introduction

Here is the source code for com.android.tools.idea.uibuilder.editor.NlActionManager.java

Source

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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.android.tools.idea.uibuilder.editor;

import com.android.tools.idea.configurations.FlatComboAction;
import com.android.tools.idea.actions.MockupDeleteAction;
import com.android.tools.idea.actions.MockupEditAction;
import com.android.tools.idea.actions.SaveScreenshotAction;
import com.android.tools.idea.uibuilder.actions.*;
import com.android.tools.idea.uibuilder.api.ViewEditor;
import com.android.tools.idea.uibuilder.api.ViewHandler;
import com.android.tools.idea.uibuilder.api.actions.*;
import com.android.tools.idea.uibuilder.handlers.ViewEditorImpl;
import com.android.tools.idea.uibuilder.handlers.ViewHandlerManager;
import com.android.tools.idea.uibuilder.mockup.Mockup;
import com.android.tools.idea.uibuilder.model.Coordinates;
import com.android.tools.idea.uibuilder.model.NlComponent;
import com.android.tools.idea.uibuilder.model.NlModel;
import com.android.tools.idea.uibuilder.model.SelectionModel;
import com.android.tools.idea.uibuilder.surface.DesignSurface;
import com.android.tools.idea.uibuilder.surface.InteractionManager;
import com.android.tools.idea.uibuilder.surface.ScreenView;
import com.google.common.collect.Lists;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.psi.PsiFile;
import com.intellij.ui.components.panels.VerticalLayout;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.android.refactoring.AndroidExtractAsIncludeAction;
import org.jetbrains.android.refactoring.AndroidExtractStyleAction;
import org.jetbrains.android.refactoring.AndroidInlineIncludeAction;
import org.jetbrains.android.refactoring.AndroidInlineStyleReferenceAction;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Provides and handles actions in the layout editor
 */
public class NlActionManager {
    private final DesignSurface mySurface;
    private AnAction mySelectAllAction;
    private AnAction mySelectParent;
    private GotoComponentAction myGotoComponentAction;

    public NlActionManager(@NotNull DesignSurface surface) {
        mySurface = surface;
    }

    public void registerActions(@NotNull JComponent component) {
        assert mySelectAllAction == null; // should only be called once!
        mySelectAllAction = new SelectAllAction(mySurface);
        registerAction(mySelectAllAction, "$SelectAll", component);

        myGotoComponentAction = new GotoComponentAction(mySurface);
        registerAction(myGotoComponentAction, IdeActions.ACTION_GOTO_DECLARATION, component);

        mySelectParent = new SelectParentAction(mySurface);
        mySelectParent.registerCustomShortcutSet(KeyEvent.VK_ESCAPE, 0, component);
    }

    private static void registerAction(@NotNull AnAction action, @NonNls String actionId,
            @NotNull JComponent component) {
        action.registerCustomShortcutSet(ActionManager.getInstance().getAction(actionId).getShortcutSet(),
                component);
    }

    @NotNull
    public JComponent createToolbar(@NotNull NlModel model) {
        NlActionsToolbar actionsToolbar = new NlActionsToolbar(mySurface);
        actionsToolbar.setModel(model);
        return actionsToolbar.getToolbarComponent();
    }

    @NotNull
    private static ActionGroup createRefactoringMenu() {
        DefaultActionGroup group = new DefaultActionGroup("_Refactor", true);
        ActionManager manager = ActionManager.getInstance();

        AnAction action = manager.getAction(AndroidExtractStyleAction.ACTION_ID);
        group.add(new AndroidRefactoringActionWrapper("_Extract Style...", action));

        action = manager.getAction(AndroidInlineStyleReferenceAction.ACTION_ID);
        group.add(new AndroidRefactoringActionWrapper("_Inline Style...", action));

        action = manager.getAction(AndroidExtractAsIncludeAction.ACTION_ID);
        group.add(new AndroidRefactoringActionWrapper("E_xtract Layout...", action));

        action = manager.getAction(AndroidInlineIncludeAction.ACTION_ID);
        group.add(new AndroidRefactoringActionWrapper("I_nline Layout...", action));

        return group;
    }

    /**
     * Exposes android refactoring actions in layout editor context menu: customizes
     * label for menu usage (e.g. with mnemonics) and makes hidden action visible but
     * disabled instead
     */
    private static class AndroidRefactoringActionWrapper extends AnAction {
        private final AnAction myRefactoringAction;

        public AndroidRefactoringActionWrapper(@NotNull String text, @NotNull AnAction refactoringAction) {
            super(text, null, null);
            myRefactoringAction = refactoringAction;
            getTemplatePresentation().setDescription(refactoringAction.getTemplatePresentation().getDescription());
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            myRefactoringAction.actionPerformed(e);
        }

        @Override
        public void update(AnActionEvent e) {
            myRefactoringAction.update(e);
            Presentation p = e.getPresentation();
            if (!p.isVisible()) {
                p.setEnabled(false);
                p.setVisible(true);
            }
        }
    }

    public void showPopup(@NotNull MouseEvent event) {
        NlComponent component = null;
        int x = event.getX();
        int y = event.getY();
        ScreenView screenView = mySurface.getScreenView(x, y);
        if (screenView == null) {
            screenView = mySurface.getCurrentScreenView();
        }
        if (screenView != null) {
            component = Coordinates.findComponent(screenView, x, y);
        }
        showPopup(event, screenView, component);
    }

    public void showPopup(@NotNull MouseEvent event, @Nullable ScreenView screenView,
            @Nullable NlComponent leafComponent) {
        ActionManager actionManager = ActionManager.getInstance();

        DefaultActionGroup group = createPopupMenu(actionManager, screenView, leafComponent);
        ActionPopupMenu popupMenu = actionManager.createActionPopupMenu("LayoutEditor", group);
        Component invoker = event.getSource() instanceof Component ? (Component) event.getSource() : mySurface;
        popupMenu.getComponent().show(invoker, event.getX(), event.getY());
    }

    @NotNull
    private DefaultActionGroup createPopupMenu(@NotNull ActionManager actionManager,
            @Nullable ScreenView screenView, @Nullable NlComponent leafComponent) {
        DefaultActionGroup group = new DefaultActionGroup();

        if (screenView != null) {
            if (leafComponent != null) {
                addViewHandlerActions(group, leafComponent, screenView.getSelectionModel().getSelection());
            }

            group.add(createSelectActionGroup(screenView.getSelectionModel()));
            group.addSeparator();
        }

        group.add(new MockupEditAction(mySurface));
        if (leafComponent != null && Mockup.hasMockupAttribute(leafComponent)) {
            group.add(new MockupDeleteAction(leafComponent));
        }
        group.addSeparator();

        group.add(actionManager.getAction(IdeActions.ACTION_CUT));
        group.add(actionManager.getAction(IdeActions.ACTION_COPY));
        group.add(actionManager.getAction(IdeActions.ACTION_PASTE));
        group.addSeparator();
        group.add(actionManager.getAction(IdeActions.ACTION_DELETE));
        group.addSeparator();
        group.add(myGotoComponentAction);
        group.add(createRefactoringMenu());
        group.add(new SaveScreenshotAction(mySurface));

        if (ConvertToConstraintLayoutAction.ENABLED) {
            group.addSeparator();
            group.add(new ConvertToConstraintLayoutAction(mySurface));
        }

        return group;
    }

    private void addViewHandlerActions(@NotNull DefaultActionGroup group, @NotNull NlComponent component,
            @NotNull List<NlComponent> selection) {
        // Look up view handlers
        int prevCount = group.getChildrenCount();
        NlComponent parent = !component.isRoot() ? component.getParent() : null;
        addViewActions(group, component, parent, selection, false);
        if (group.getChildrenCount() > prevCount) {
            group.addSeparator();
        }
    }

    @NotNull
    private ActionGroup createSelectActionGroup(@NotNull SelectionModel model) {
        DefaultActionGroup group = new DefaultActionGroup("_Select", true);

        AnAction selectSiblings = new SelectSiblingsAction(model);
        AnAction selectSameType = new SelectSameTypeAction(model);
        AnAction deselectAllAction = new DeselectAllAction(model);

        group.add(mySelectParent);
        group.add(selectSiblings);
        group.add(selectSameType);
        group.addSeparator();
        group.add(mySelectAllAction);
        group.add(deselectAllAction);

        return group;
    }

    public void addViewActions(@NotNull DefaultActionGroup group, @Nullable NlComponent component,
            @Nullable NlComponent parent, @NotNull List<NlComponent> newSelection, boolean toolbar) {
        ScreenView screenView = mySurface.getCurrentScreenView();
        if (screenView == null || (parent == null && component == null)) {
            return;
        }

        ViewEditor editor = new ViewEditorImpl(screenView);

        // TODO: Perform caching
        if (component != null) {
            ViewHandler handler = ViewHandlerManager.get(mySurface.getProject()).getHandler(component);
            addViewActionsForHandler(group, component, newSelection, editor, handler, toolbar);
        }
        if (parent != null) {
            ViewHandler handler = ViewHandlerManager.get(mySurface.getProject()).getHandler(parent);
            List<NlComponent> selectedChildren = Lists.newArrayListWithCapacity(newSelection.size());
            for (NlComponent selected : newSelection) {
                if (selected.getParent() == parent) {
                    selectedChildren.add(selected);
                }
            }
            addViewActionsForHandler(group, parent, selectedChildren, editor, handler, toolbar);
        }
    }

    private void addViewActionsForHandler(@NotNull DefaultActionGroup group, @NotNull NlComponent component,
            @NotNull List<NlComponent> newSelection, @NotNull ViewEditor editor, @Nullable ViewHandler handler,
            boolean toolbar) {
        if (handler == null) {
            return;
        }

        List<ViewAction> viewActions = createViewActionList();
        if (toolbar) {
            viewActions.addAll(ViewHandlerManager.get(mySurface.getProject()).getToolbarActions(handler));
        } else {
            viewActions.addAll(ViewHandlerManager.get(mySurface.getProject()).getPopupMenuActions(handler));
        }
        Collections.sort(viewActions);

        group.removeAll();
        List<AnAction> target = Lists.newArrayList();
        NlActionManager actionManager = mySurface.getActionManager();
        for (ViewAction viewAction : viewActions) {
            actionManager.addActions(target, toolbar, viewAction, mySurface.getProject(), editor, handler,
                    component, newSelection);
        }
        boolean lastWasSeparator = false;
        for (AnAction action : target) {
            // Merge repeated separators
            boolean isSeparator = action instanceof Separator;
            if (isSeparator && lastWasSeparator) {
                continue;
            }

            group.add(action);
            lastWasSeparator = isSeparator;
        }
    }

    @NotNull
    private static List<ViewAction> createViewActionList() {
        return new ArrayList<ViewAction>() {
            @Override
            public boolean add(ViewAction viewAction) {
                // Ensure that if no rank is specified, we just sort in the insert order
                if (!isEmpty()) {
                    ViewAction prev = get(size() - 1);
                    if (viewAction.getRank() == prev.getRank() || viewAction.getRank() == -1) {
                        viewAction.setRank(prev.getRank() + 5);
                    }
                } else if (viewAction.getRank() == -1) {
                    viewAction.setRank(0);
                }

                return super.add(viewAction);
            }
        };
    }

    /**
     * Adds one or more {@link AnAction} to the target list from a given {@link ViewAction}. This
     * is typically just one action, but in the case of a {@link ToggleViewActionGroup} it can add
     * a series of related actions.
     */
    void addActions(@NotNull List<AnAction> target, boolean toolbar, @NotNull ViewAction viewAction,
            @NotNull Project project, @NotNull ViewEditor editor, @NotNull ViewHandler handler,
            @NotNull NlComponent parent, @NotNull List<NlComponent> newSelection) {
        if (viewAction instanceof DirectViewAction) {
            target.add(new DirectViewActionWrapper(project, (DirectViewAction) viewAction, editor, handler, parent,
                    newSelection));
        } else if (viewAction instanceof ViewActionSeparator) {
            target.add(Separator.getInstance());
        } else if (viewAction instanceof ToggleViewAction) {
            target.add(new ToggleViewActionWrapper(project, (ToggleViewAction) viewAction, editor, handler, parent,
                    newSelection));
        } else if (viewAction instanceof ToggleViewActionGroup) {
            List<ToggleViewActionWrapper> actions = Lists.newArrayList();
            for (ToggleViewAction action : ((ToggleViewActionGroup) viewAction).getActions()) {
                actions.add(new ToggleViewActionWrapper(project, action, editor, handler, parent, newSelection));
            }
            if (!actions.isEmpty()) {
                ToggleViewActionWrapper prev = null;
                for (ToggleViewActionWrapper action : actions) {
                    target.add(action);

                    if (prev != null) {
                        prev.myGroupSibling = action;
                    }
                    prev = action;
                }
                if (prev != null) { // last link back to first
                    prev.myGroupSibling = actions.get(0);
                }
            }
        } else if (viewAction instanceof ViewActionMenu) {
            target.add(
                    new ViewActionMenuWrapper((ViewActionMenu) viewAction, editor, handler, parent, newSelection));
        } else if (viewAction instanceof NestedViewActionMenu) {
            // Can't place toolbar popups in menus
            if (toolbar) {
                target.add(new ViewActionToolbarMenuWrapper((NestedViewActionMenu) viewAction, editor, handler,
                        parent, newSelection));
            }
        } else {
            throw new UnsupportedOperationException(viewAction.getClass().getName());
        }
    }

    /**
     * Wrapper around a {@link DirectViewAction} which uses an IDE {@link AnAction} in the toolbar
     */
    private class DirectViewActionWrapper extends AnAction implements ViewActionPresentation {
        private final Project myProject;
        private final DirectViewAction myAction;
        private final ViewHandler myHandler;
        private final ViewEditor myEditor;
        private final NlComponent myComponent;
        private final List<NlComponent> mySelectedChildren;
        private Presentation myCurrentPresentation;

        public DirectViewActionWrapper(@NotNull Project project, @NotNull DirectViewAction action,
                @NotNull ViewEditor editor, @NotNull ViewHandler handler, @NotNull NlComponent component,
                @NotNull List<NlComponent> selectedChildren) {
            myProject = project;
            myAction = action;
            myEditor = editor;
            myHandler = handler;
            myComponent = component;
            mySelectedChildren = selectedChildren;
            Presentation presentation = getTemplatePresentation();
            presentation.setIcon(action.getDefaultIcon());
            presentation.setText(action.getLabel());
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            String description = e.getPresentation().getText();
            PsiFile file = myComponent.getTag().getContainingFile();
            if (myAction.affectsUndo()) {
                new WriteCommandAction<Void>(myProject, description, null, new PsiFile[] { file }) {
                    @Override
                    protected void run(@NotNull Result<Void> result) throws Throwable {
                        myAction.perform(myEditor, myHandler, myComponent, mySelectedChildren, e.getModifiers());
                    }
                }.execute();
            } else {
                // Catch missing write lock and diagnose as missing affectsRedo
                try {
                    myAction.perform(myEditor, myHandler, myComponent, mySelectedChildren, e.getModifiers());
                } catch (Throwable t) {
                    throw new IncorrectOperationException(
                            "View Action required write lock: should not specify affectsUndo=false");
                }
            }

            mySurface.repaint();
        }

        @Override
        public void update(AnActionEvent e) {
            // Unfortunately, the action event we're fed here does *not* have the correct
            // current modifier state; there are code paths which just feed in a value of 0
            // when manufacturing their own ActionEvents; for example, Utils#expandActionGroup
            // which is usually how the toolbar code is updated.
            //
            // So, instead we'll need to feed it the most recently known mask from the
            // InteractionManager which observes mouse and keyboard events in the design surface.
            // This misses pure keyboard events when the design surface does not have focus
            // (but moving the mouse over the design surface updates it immediately.)
            //
            // (Longer term we consider having a singleton Toolkit listener which listens
            // for AWT events globally and tracks the most recent global modifier key state.)
            int modifiers = InteractionManager.getLastModifiers();

            myCurrentPresentation = e.getPresentation();
            try {
                myAction.updatePresentation(this, myEditor, myHandler, myComponent, mySelectedChildren, modifiers);
            } finally {
                myCurrentPresentation = null;
            }
        }

        // ---- Implements ViewActionPresentation ----

        @Override
        public void setLabel(@NotNull String label) {
            myCurrentPresentation.setText(label);
        }

        @Override
        public void setEnabled(boolean enabled) {
            myCurrentPresentation.setEnabled(enabled);
        }

        @Override
        public void setVisible(boolean visible) {
            myCurrentPresentation.setVisible(visible);
        }

        @Override
        public void setIcon(@Nullable Icon icon) {
            myCurrentPresentation.setIcon(icon);
        }
    }

    /**
     * Wrapper around a {@link ToggleViewAction} which uses an IDE {@link AnAction} in the toolbar
     */
    private class ToggleViewActionWrapper extends ToggleAction implements ViewActionPresentation {
        private final Project myProject;
        private final ToggleViewAction myAction;
        private final ViewEditor myEditor;
        private final ViewHandler myHandler;
        private final NlComponent myComponent;
        private final List<NlComponent> mySelectedChildren;
        private Presentation myCurrentPresentation;
        private ToggleViewActionWrapper myGroupSibling;

        public ToggleViewActionWrapper(@NotNull Project project, @NotNull ToggleViewAction action,
                @NotNull ViewEditor editor, @NotNull ViewHandler handler, @NotNull NlComponent component,
                @NotNull List<NlComponent> selectedChildren) {
            myProject = project;
            myAction = action;
            myEditor = editor;
            myHandler = handler;
            myComponent = component;
            mySelectedChildren = selectedChildren;
            Presentation presentation = getTemplatePresentation();
            presentation.setText(action.getUnselectedLabel());
            presentation.setIcon(action.getUnselectedIcon());
            presentation.setSelectedIcon(action.getSelectedIcon());
        }

        @Override
        public boolean isSelected(AnActionEvent e) {
            return myAction.isSelected(myEditor, myHandler, myComponent, mySelectedChildren);
        }

        @Override
        public void setSelected(AnActionEvent e, boolean state) {
            String description = e.getPresentation().getText();
            PsiFile file = myComponent.getTag().getContainingFile();

            if (myAction.affectsUndo()) {
                new WriteCommandAction<Void>(myProject, description, null, new PsiFile[] { file }) {
                    @Override
                    protected void run(@NotNull Result<Void> result) throws Throwable {
                        applySelection(state);
                    }
                }.execute();
            } else {
                try {
                    applySelection(state);
                } catch (Throwable t) {
                    throw new IncorrectOperationException(
                            "View Action required write lock: should not specify affectsUndo=false");
                }
            }
        }

        private void applySelection(boolean state) {
            myAction.setSelected(myEditor, myHandler, myComponent, mySelectedChildren, state);

            // Also clear any in the group (if any)
            if (state) {
                ToggleViewActionWrapper groupSibling = myGroupSibling;
                while (groupSibling != null && groupSibling != this) { // This is a circular list.
                    groupSibling.myAction.setSelected(myEditor, myHandler, myComponent, mySelectedChildren, false);
                    groupSibling = groupSibling.myGroupSibling;
                }
            }

            mySurface.repaint();
        }

        @Override
        public void update(@NotNull AnActionEvent e) {
            myCurrentPresentation = e.getPresentation();
            try {
                boolean selected = myAction.isSelected(myEditor, myHandler, myComponent, mySelectedChildren);
                if (myAction.getSelectedLabel() != null) {
                    myCurrentPresentation
                            .setText(selected ? myAction.getSelectedLabel() : myAction.getUnselectedLabel());
                }
                myAction.updatePresentation(this, myEditor, myHandler, myComponent, mySelectedChildren,
                        e.getModifiers(), selected);
            } finally {
                myCurrentPresentation = null;
            }
        }

        // ---- Implements ViewActionPresentation ----

        @Override
        public void setLabel(@NotNull String label) {
            myCurrentPresentation.setText(label);
        }

        @Override
        public void setEnabled(boolean enabled) {
            myCurrentPresentation.setEnabled(enabled);
        }

        @Override
        public void setVisible(boolean visible) {
            myCurrentPresentation.setVisible(visible);
        }

        @Override
        public void setIcon(@Nullable Icon icon) {
            myCurrentPresentation.setIcon(icon);
        }
    }

    /**
     * Wrapper around a {@link ViewActionMenu} which uses an IDE {@link AnAction} in the toolbar
     */
    private class ViewActionMenuWrapper extends ActionGroup implements ViewActionPresentation {
        private final ViewActionMenu myAction;
        private final ViewEditor myEditor;
        private final ViewHandler myHandler;
        private final NlComponent myComponent;
        private final List<NlComponent> mySelectedChildren;
        private Presentation myCurrentPresentation;

        public ViewActionMenuWrapper(@NotNull ViewActionMenu action, @NotNull ViewEditor editor,
                @NotNull ViewHandler handler, @NotNull NlComponent component,
                @NotNull List<NlComponent> selectedChildren) {
            super(action.getLabel(), true);
            myAction = action;
            myEditor = editor;
            myHandler = handler;
            myComponent = component;
            mySelectedChildren = selectedChildren;
            Presentation presentation = getTemplatePresentation();
            presentation.setIcon(action.getDefaultIcon());
            presentation.setText(action.getLabel());
        }

        @Override
        public void update(AnActionEvent e) {
            myCurrentPresentation = e.getPresentation();
            try {
                myAction.updatePresentation(this, myEditor, myHandler, myComponent, mySelectedChildren,
                        e.getModifiers());
            } finally {
                myCurrentPresentation = null;
            }
        }

        // ---- Implements ViewActionPresentation ----

        @Override
        public void setLabel(@NotNull String label) {
            myCurrentPresentation.setText(label);
        }

        @Override
        public void setEnabled(boolean enabled) {
            myCurrentPresentation.setEnabled(enabled);
        }

        @Override
        public void setVisible(boolean visible) {
            myCurrentPresentation.setVisible(visible);
        }

        @Override
        public void setIcon(@Nullable Icon icon) {
            myCurrentPresentation.setIcon(icon);
        }

        @NotNull
        @Override
        public AnAction[] getChildren(@Nullable AnActionEvent e) {
            List<AnAction> actions = Lists.newArrayList();
            for (ViewAction viewAction : myAction.getActions()) {
                addActions(actions, false, viewAction, mySurface.getProject(), myEditor, myHandler, myComponent,
                        mySelectedChildren);
            }
            return actions.toArray(AnAction.EMPTY_ARRAY);
        }
    }

    private class ViewActionToolbarMenuWrapper extends FlatComboAction implements ViewActionPresentation {
        private final NestedViewActionMenu myAction;
        private final ViewEditor myEditor;
        private final ViewHandler myHandler;
        private final NlComponent myComponent;
        private final List<NlComponent> mySelectedChildren;
        private Presentation myCurrentPresentation;

        public ViewActionToolbarMenuWrapper(@NotNull NestedViewActionMenu action, @NotNull ViewEditor editor,
                @NotNull ViewHandler handler, @NotNull NlComponent component,
                @NotNull List<NlComponent> selectedChildren) {
            myAction = action;
            myEditor = editor;
            myHandler = handler;
            myComponent = component;
            mySelectedChildren = selectedChildren;
            Presentation presentation = getTemplatePresentation();
            presentation.setIcon(action.getDefaultIcon());
            presentation.setDescription(action.getLabel());
        }

        @Override
        public void update(AnActionEvent e) {
            myCurrentPresentation = e.getPresentation();
            try {
                myAction.updatePresentation(this, myEditor, myHandler, myComponent, mySelectedChildren,
                        e.getModifiers());
            } finally {
                myCurrentPresentation = null;
            }
        }

        @NotNull
        @Override
        protected DefaultActionGroup createPopupActionGroup() {
            List<List<ViewAction>> rows = myAction.getActions();
            if (rows.size() == 1) {
                List<AnAction> actions = Lists.newArrayList();
                for (ViewAction viewAction : rows.get(0)) {
                    addActions(actions, false, viewAction, mySurface.getProject(), myEditor, myHandler, myComponent,
                            mySelectedChildren);
                }
                return new DefaultActionGroup(actions);
            } else {
                return new DefaultActionGroup();
            }
        }

        @Override
        protected JBPopup createPopup(@Nullable Runnable onDispose, @Nullable DataContext context) {
            List<List<ViewAction>> rows = myAction.getActions();
            if (rows.size() == 1) {
                return super.createPopup(onDispose, context);
            }

            ActionManager actionManager = ActionManager.getInstance();
            JPanel panel = new JPanel(new VerticalLayout(0));
            for (List<ViewAction> row : rows) {
                if (row.size() == 1 && row.get(0) instanceof ViewActionSeparator) {
                    panel.add(new JSeparator());
                    continue;
                }
                List<AnAction> actions = Lists.newArrayList();
                for (ViewAction viewAction : row) {
                    addActions(actions, false, viewAction, mySurface.getProject(), myEditor, myHandler, myComponent,
                            mySelectedChildren);
                }
                ActionGroup group = new DefaultActionGroup(actions);
                ActionToolbar toolbar = actionManager.createActionToolbar("DynamicToolbar", group, true);
                panel.add(toolbar.getComponent());
            }

            return JBPopupFactory.getInstance().createComponentPopupBuilder(panel, panel).setRequestFocus(true)
                    .setCancelOnClickOutside(true).setLocateWithinScreenBounds(true).setShowShadow(true)
                    .setCancelOnWindowDeactivation(true).setCancelCallback(() -> {
                        if (onDispose != null) {
                            onDispose.run();
                        }
                        return Boolean.TRUE;
                    }).createPopup();
        }

        // ---- Implements ViewActionPresentation ----

        @Override
        public void setLabel(@NotNull String label) {
            myCurrentPresentation.setText(label);
        }

        @Override
        public void setEnabled(boolean enabled) {
            myCurrentPresentation.setEnabled(enabled);
        }

        @Override
        public void setVisible(boolean visible) {
            myCurrentPresentation.setVisible(visible);
        }

        @Override
        public void setIcon(@Nullable Icon icon) {
            myCurrentPresentation.setIcon(icon);
        }
    }
}