com.intellij.ide.util.gotoByName.GotoActionModel.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.ide.util.gotoByName.GotoActionModel.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.ide.util.gotoByName;

import com.intellij.ide.IdeBundle;
import com.intellij.ide.actions.ApplyIntentionAction;
import com.intellij.ide.ui.search.BooleanOptionDescription;
import com.intellij.ide.ui.search.OptionDescription;
import com.intellij.ide.ui.search.SearchableOptionsRegistrar;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiFile;
import com.intellij.ui.*;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.OnOffButton;
import com.intellij.ui.speedSearch.SpeedSearchUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ContainerUtilRt;
import com.intellij.util.ui.EmptyIcon;
import com.intellij.util.ui.UIUtil;
import org.apache.oro.text.regex.*;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.util.List;

import static com.intellij.ui.SimpleTextAttributes.STYLE_PLAIN;
import static com.intellij.ui.SimpleTextAttributes.STYLE_SEARCH_MATCH;

public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, Comparator<Object>, EdtSortingModel {
    @Nullable
    private final Project myProject;
    private final Component myContextComponent;

    protected final ActionManager myActionManager = ActionManager.getInstance();

    private static final Icon EMPTY_ICON = EmptyIcon.ICON_18;

    private Pattern myCompiledPattern;

    protected final SearchableOptionsRegistrar myIndex;
    protected final Map<AnAction, String> myActionGroups = ContainerUtil.newHashMap();

    protected final Map<String, ApplyIntentionAction> myIntentions = new TreeMap<String, ApplyIntentionAction>();
    private final Map<String, String> myConfigurablesNames = ContainerUtil.newTroveMap();

    public GotoActionModel(@Nullable Project project, final Component component) {
        this(project, component, null, null);
    }

    public GotoActionModel(@Nullable Project project, final Component component, @Nullable Editor editor,
            @Nullable PsiFile file) {
        myProject = project;
        myContextComponent = component;
        final ActionGroup mainMenu = (ActionGroup) myActionManager.getActionOrStub(IdeActions.GROUP_MAIN_MENU);
        collectActions(myActionGroups, mainMenu, mainMenu.getTemplatePresentation().getText());
        if (project != null && editor != null && file != null) {
            final ApplyIntentionAction[] children = ApplyIntentionAction.getAvailableIntentions(editor, file);
            if (children != null) {
                for (ApplyIntentionAction action : children) {
                    myIntentions.put(action.getName(), action);
                }
            }
        }
        myIndex = SearchableOptionsRegistrar.getInstance();
    }

    @Override
    public String getPromptText() {
        return IdeBundle.message("prompt.gotoaction.enter.action");
    }

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

    @Override
    public char getCheckBoxMnemonic() {
        return 'd';
    }

    @Override
    public String getNotInMessage() {
        return IdeBundle.message("label.no.menu.actions.found");
    }

    @Override
    public String getNotFoundMessage() {
        return IdeBundle.message("label.no.actions.found");
    }

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

    @Override
    public void saveInitialCheckBoxState(boolean state) {
    }

    public static class MatchedValue implements Comparable<MatchedValue> {
        @NotNull
        public final Comparable value;
        @NotNull
        final String pattern;

        MatchedValue(@NotNull Comparable value, @NotNull String pattern) {
            this.value = value;
            this.pattern = pattern;
        }

        @Nullable
        private String getValueText() {
            if (value instanceof OptionDescription)
                return ((OptionDescription) value).getHit();
            if (!(value instanceof ActionWrapper))
                return null;
            return ((ActionWrapper) value).getAction().getTemplatePresentation().getText();
        }

        private int getMatchingDegree() {
            String text = getValueText();
            if (text != null) {
                int degree = getRank(text);
                return value instanceof ActionWrapper && !((ActionWrapper) value).isGroupAction() ? degree + 1
                        : degree;
            }
            return 0;
        }

        private int getRank(@NotNull String text) {
            if (StringUtil.equalsIgnoreCase(StringUtil.trimEnd(text, "..."), pattern))
                return 3;
            if (StringUtil.startsWithIgnoreCase(text, pattern))
                return 2;
            if (StringUtil.containsIgnoreCase(text, pattern))
                return 1;
            return 0;
        }

        @Override
        public int compareTo(@NotNull MatchedValue o) {
            boolean edt = ApplicationManager.getApplication().isDispatchThread();

            if (value instanceof ActionWrapper && o.value instanceof ActionWrapper && edt) {
                boolean p1Enable = ((ActionWrapper) value).isAvailable();
                boolean p2enable = ((ActionWrapper) o.value).isAvailable();
                if (p1Enable && !p2enable)
                    return -1;
                if (!p1Enable && p2enable)
                    return 1;
            }

            if (value instanceof ActionWrapper && o.value instanceof BooleanOptionDescription) {
                return edt && ((ActionWrapper) value).isAvailable() ? -1 : 1;
            }

            if (o.value instanceof ActionWrapper && value instanceof BooleanOptionDescription) {
                return edt && ((ActionWrapper) o.value).isAvailable() ? 1 : -1;
            }

            if (value instanceof OptionDescription && o.value instanceof BooleanOptionDescription)
                return 1;
            if (o.value instanceof OptionDescription && value instanceof BooleanOptionDescription)
                return -1;

            if (value instanceof OptionDescription && !(o.value instanceof OptionDescription))
                return 1;
            if (o.value instanceof OptionDescription && !(value instanceof OptionDescription))
                return -1;

            int diff = o.getMatchingDegree() - getMatchingDegree();
            if (diff != 0)
                return diff;
            //noinspection unchecked
            int compare = value.compareTo(o.value);
            if (compare != 0)
                return compare;
            return o.hashCode() - hashCode();
        }
    }

    @Override
    public ListCellRenderer getListCellRenderer() {
        return new DefaultListCellRenderer() {
            @Override
            public Component getListCellRendererComponent(@NotNull final JList list, final Object matchedValue,
                    final int index, final boolean isSelected, final boolean cellHasFocus) {
                final JPanel panel = new JPanel(new BorderLayout());
                panel.setBorder(IdeBorderFactory.createEmptyBorder(2));
                panel.setOpaque(true);
                Color bg = UIUtil.getListBackground(isSelected);
                panel.setBackground(bg);

                if (matchedValue instanceof String) { //...
                    final JBLabel label = new JBLabel((String) matchedValue);
                    label.setIcon(EMPTY_ICON);
                    panel.add(label, BorderLayout.WEST);
                    return panel;
                }

                Color groupFg = isSelected ? UIUtil.getListSelectionForeground()
                        : UIUtil.getLabelDisabledForeground();

                final Object value = ((MatchedValue) matchedValue).value;
                String pattern = ((MatchedValue) matchedValue).pattern;

                SimpleColoredComponent nameComponent = new SimpleColoredComponent();
                nameComponent.setBackground(bg);
                panel.add(nameComponent, BorderLayout.CENTER);

                if (value instanceof ActionWrapper) {
                    final ActionWrapper actionWithParentGroup = (ActionWrapper) value;
                    final AnAction anAction = actionWithParentGroup.getAction();
                    final Presentation presentation = anAction.getTemplatePresentation();
                    boolean toggle = anAction instanceof ToggleAction;
                    String groupName = actionWithParentGroup.getAction() instanceof ApplyIntentionAction ? null
                            : actionWithParentGroup.getGroupName();
                    final Color fg = defaultActionForeground(isSelected, actionWithParentGroup.getPresentation());
                    panel.add(createIconLabel(presentation.getIcon()), BorderLayout.WEST);
                    appendWithColoredMatches(nameComponent, getName(presentation.getText(), groupName, toggle),
                            pattern, fg, isSelected);

                    final Shortcut shortcut = preferKeyboardShortcut(
                            KeymapManager.getInstance().getActiveKeymap().getShortcuts(getActionId(anAction)));
                    if (shortcut != null) {
                        nameComponent.append(" (" + KeymapUtil.getShortcutText(shortcut) + ")",
                                new SimpleTextAttributes(STYLE_PLAIN, groupFg));
                    }

                    if (toggle) {
                        final OnOffButton button = new OnOffButton();
                        AnActionEvent event = new AnActionEvent(null, ((ActionWrapper) value).myDataContext,
                                ActionPlaces.UNKNOWN, new Presentation(), ActionManager.getInstance(), 0);
                        button.setSelected(((ToggleAction) anAction).isSelected(event));
                        panel.add(button, BorderLayout.EAST);
                        panel.setBorder(IdeBorderFactory.createEmptyBorder());
                    } else {
                        if (groupName != null) {
                            final JLabel groupLabel = new JLabel(groupName);
                            groupLabel.setBackground(bg);
                            groupLabel.setForeground(groupFg);
                            panel.add(groupLabel, BorderLayout.EAST);
                        }
                    }
                } else if (value instanceof OptionDescription) {
                    if (!isSelected && !(value instanceof BooleanOptionDescription)) {
                        Color descriptorBg = UIUtil.isUnderDarcula()
                                ? ColorUtil.brighter(UIUtil.getListBackground(), 1)
                                : LightColors.SLIGHTLY_GRAY;
                        panel.setBackground(descriptorBg);
                        nameComponent.setBackground(descriptorBg);
                    }
                    String hit = ((OptionDescription) value).getHit();
                    if (hit == null) {
                        hit = ((OptionDescription) value).getOption();
                    }
                    hit = StringUtil.unescapeXml(hit);
                    hit = hit.replace("  ", " "); // avoid extra spaces from mnemonics and xml conversion
                    String fullHit = hit;
                    hit = StringUtil.first(hit, 45, true);

                    final Color fg = UIUtil.getListForeground(isSelected);

                    appendWithColoredMatches(nameComponent, hit.trim(), pattern, fg, isSelected);

                    panel.add(new JLabel(EMPTY_ICON), BorderLayout.WEST);
                    panel.setToolTipText(fullHit);

                    if (value instanceof BooleanOptionDescription) {
                        final OnOffButton button = new OnOffButton();
                        button.setSelected(((BooleanOptionDescription) value).isOptionEnabled());
                        panel.add(button, BorderLayout.EAST);
                        panel.setBorder(IdeBorderFactory.createEmptyBorder());
                    } else {
                        final JLabel settingsLabel = new JLabel(getGroupName((OptionDescription) value));
                        settingsLabel.setForeground(groupFg);
                        settingsLabel.setBackground(bg);
                        panel.add(settingsLabel, BorderLayout.EAST);
                    }
                }
                return panel;
            }

            public String getName(String text, String groupName, boolean toggle) {
                return toggle && StringUtil.isNotEmpty(groupName) ? groupName + ": " + text : text;
            }

            private void appendWithColoredMatches(SimpleColoredComponent nameComponent, String name, String pattern,
                    Color fg, boolean selected) {
                final SimpleTextAttributes plain = new SimpleTextAttributes(STYLE_PLAIN, fg);
                final SimpleTextAttributes highlighted = new SimpleTextAttributes(null, fg, null,
                        STYLE_SEARCH_MATCH);
                List<TextRange> fragments = ContainerUtil.newArrayList();
                if (selected) {
                    int matchStart = StringUtil.indexOfIgnoreCase(name, pattern, 0);
                    if (matchStart >= 0) {
                        fragments.add(TextRange.from(matchStart, pattern.length()));
                    }
                }
                SpeedSearchUtil.appendColoredFragments(nameComponent, name, fragments, plain, highlighted);
            }
        };
    }

    protected String getActionId(@NotNull final AnAction anAction) {
        return myActionManager.getId(anAction);
    }

    private static JLabel createIconLabel(final Icon icon) {
        final LayeredIcon layeredIcon = new LayeredIcon(2);
        layeredIcon.setIcon(EMPTY_ICON, 0);
        if (icon != null && icon.getIconWidth() <= EMPTY_ICON.getIconWidth()
                && icon.getIconHeight() <= EMPTY_ICON.getIconHeight()) {
            layeredIcon.setIcon(icon, 1, (-icon.getIconWidth() + EMPTY_ICON.getIconWidth()) / 2,
                    (EMPTY_ICON.getIconHeight() - icon.getIconHeight()) / 2);
        }

        return new JLabel(layeredIcon);
    }

    protected JLabel createActionLabel(final AnAction anAction, final String anActionName, final Color fg,
            final Color bg, final Icon icon) {
        final LayeredIcon layeredIcon = new LayeredIcon(2);
        layeredIcon.setIcon(EMPTY_ICON, 0);
        if (icon != null && icon.getIconWidth() <= EMPTY_ICON.getIconWidth()
                && icon.getIconHeight() <= EMPTY_ICON.getIconHeight()) {
            layeredIcon.setIcon(icon, 1, (-icon.getIconWidth() + EMPTY_ICON.getIconWidth()) / 2,
                    (EMPTY_ICON.getIconHeight() - icon.getIconHeight()) / 2);
        }

        final Shortcut shortcut = preferKeyboardShortcut(
                KeymapManager.getInstance().getActiveKeymap().getShortcuts(getActionId(anAction)));
        final String actionName = anActionName
                + (shortcut != null ? " (" + KeymapUtil.getShortcutText(shortcut) + ")" : "");
        final JLabel actionLabel = new JLabel(actionName, layeredIcon, SwingConstants.LEFT);
        actionLabel.setBackground(bg);
        actionLabel.setForeground(fg);
        return actionLabel;
    }

    private static Shortcut preferKeyboardShortcut(Shortcut[] shortcuts) {
        if (shortcuts != null) {
            for (Shortcut shortcut : shortcuts) {
                if (shortcut.isKeyboard())
                    return shortcut;
            }
            return shortcuts.length > 0 ? shortcuts[0] : null;
        }
        return null;
    }

    @Override
    public int compare(@NotNull Object o1, @NotNull Object o2) {
        if (ChooseByNameBase.EXTRA_ELEM.equals(o1))
            return 1;
        if (ChooseByNameBase.EXTRA_ELEM.equals(o2))
            return -1;
        return ((MatchedValue) o1).compareTo((MatchedValue) o2);
    }

    public static AnActionEvent updateActionBeforeShow(AnAction anAction, DataContext dataContext) {
        final AnActionEvent event = new AnActionEvent(null, dataContext, ActionPlaces.UNKNOWN, new Presentation(),
                ActionManager.getInstance(), 0);
        ActionUtil.performDumbAwareUpdate(anAction, event, false);
        ActionUtil.performDumbAwareUpdate(anAction, event, true);
        return event;
    }

    protected static Color defaultActionForeground(boolean isSelected, Presentation presentation) {
        if (isSelected)
            return UIUtil.getListSelectionForeground();
        if (!presentation.isEnabled() || !presentation.isVisible())
            return UIUtil.getInactiveTextColor();
        return UIUtil.getListForeground();
    }

    @Override
    @NotNull
    public String[] getNames(boolean checkBoxState) {
        return ArrayUtil.EMPTY_STRING_ARRAY;
    }

    @Override
    @NotNull
    public Object[] getElementsByName(final String id, final boolean checkBoxState, final String pattern) {
        return ArrayUtil.EMPTY_OBJECT_ARRAY;
    }

    @NotNull
    String getGroupName(@NotNull OptionDescription description) {
        String id = description.getConfigurableId();
        String name = myConfigurablesNames.get(id);
        String settings = ShowSettingsUtil.getSettingsMenuName();
        if (name == null)
            return settings;
        return settings + " > " + name;
    }

    private void collectActions(Map<AnAction, String> result, ActionGroup group, final String containingGroupName) {
        AnAction[] actions = group.getChildren(null);
        includeGroup(result, group, actions, containingGroupName);
        for (AnAction action : actions) {
            if (action == null)
                continue;
            if (action instanceof ActionGroup) {
                ActionGroup actionGroup = (ActionGroup) action;
                String groupName = actionGroup.getTemplatePresentation().getText();
                collectActions(result, actionGroup,
                        StringUtil.isEmpty(groupName) || !actionGroup.isPopup() ? containingGroupName : groupName);
            } else {
                String groupName = group.getTemplatePresentation().getText();
                if (result.containsKey(action)) {
                    result.put(action, null);
                } else {
                    result.put(action, StringUtil.isEmpty(groupName) ? containingGroupName : groupName);
                }
            }
        }
    }

    private void includeGroup(Map<AnAction, String> result, ActionGroup group, AnAction[] actions,
            String containingGroupName) {
        boolean showGroup = true;
        for (AnAction action : actions) {
            if (myActionManager.getId(action) != null) {
                showGroup = false;
                break;
            }
        }
        if (showGroup) {
            result.put(group, containingGroupName);
        }
    }

    @Override
    @Nullable
    public String getFullName(final Object element) {
        return getElementName(element);
    }

    @NonNls
    @Override
    public String getHelpId() {
        return "procedures.navigating.goto.action";
    }

    @Override
    @NotNull
    public String[] getSeparators() {
        return ArrayUtil.EMPTY_STRING_ARRAY;
    }

    @Override
    public String getElementName(final Object mv) {
        return ((MatchedValue) mv).getValueText();
    }

    @Override
    public boolean matches(@NotNull final String name, @NotNull final String pattern) {
        final AnAction anAction = myActionManager.getAction(name);
        if (anAction == null)
            return true;
        return actionMatches(pattern, anAction) != MatchMode.NONE;
    }

    protected MatchMode actionMatches(String pattern, @NotNull AnAction anAction) {
        Pattern compiledPattern = getPattern(pattern);
        Presentation presentation = anAction.getTemplatePresentation();
        String text = presentation.getText();
        String description = presentation.getDescription();
        String groupName = myActionGroups.get(anAction);
        PatternMatcher matcher = getMatcher();
        if (text != null && matcher.matches(text, compiledPattern)) {
            return MatchMode.NAME;
        } else if (description != null && !description.equals(text)
                && matcher.matches(description, compiledPattern)) {
            return MatchMode.DESCRIPTION;
        }
        if (text == null) {
            return MatchMode.NONE;
        }
        if (groupName == null) {
            return matches(pattern, compiledPattern, matcher, text) ? MatchMode.NON_MENU : MatchMode.NONE;
        }
        if (matches(pattern, compiledPattern, matcher, groupName + " " + text)) {
            return anAction instanceof ToggleAction ? MatchMode.NAME : MatchMode.GROUP;
        }
        return matches(pattern, compiledPattern, matcher, text + " " + groupName) ? MatchMode.GROUP
                : MatchMode.NONE;
    }

    private static boolean matches(String pattern, Pattern compiledPattern, PatternMatcher matcher, String str) {
        return StringUtil.containsIgnoreCase(str, pattern) || matcher.matches(str, compiledPattern);
    }

    @Nullable
    protected Project getProject() {
        return myProject;
    }

    protected Component getContextComponent() {
        return myContextComponent;
    }

    @NotNull
    Pattern getPattern(@NotNull String pattern) {
        String converted = convertPattern(pattern.trim());
        Pattern compiledPattern = myCompiledPattern;
        if (compiledPattern != null && !Comparing.strEqual(converted, compiledPattern.getPattern())) {
            compiledPattern = null;
        }
        if (compiledPattern == null) {
            try {
                myCompiledPattern = compiledPattern = new Perl5Compiler().compile(converted,
                        Perl5Compiler.READ_ONLY_MASK);
            } catch (MalformedPatternException e) {
                //do nothing
            }
        }

        return compiledPattern;
    }

    @NotNull
    @Override
    public SortedSet<Object> sort(@NotNull Set<Object> elements) {
        TreeSet<Object> objects = ContainerUtilRt.newTreeSet(this);
        objects.addAll(elements);
        return objects;
    }

    protected enum MatchMode {
        NONE, INTENTION, NAME, DESCRIPTION, GROUP, NON_MENU
    }

    static String convertPattern(String pattern) {
        final int eol = pattern.indexOf('\n');
        if (eol != -1) {
            pattern = pattern.substring(0, eol);
        }
        if (pattern.length() >= 80) {
            pattern = pattern.substring(0, 80);
        }

        @NonNls
        final StringBuilder buffer = new StringBuilder();

        boolean allowToLower = true;
        if (containsOnlyUppercaseLetters(pattern)) {
            allowToLower = false;
        }

        if (allowToLower) {
            buffer.append(".*");
        }

        boolean firstIdentifierLetter = true;
        for (int i = 0; i < pattern.length(); i++) {
            final char c = pattern.charAt(i);
            if (Character.isLetterOrDigit(c)) {
                // This logic allows to use uppercase letters only to catch the name like PDM for PsiDocumentManager
                if (Character.isUpperCase(c) || Character.isDigit(c)) {

                    if (!firstIdentifierLetter) {
                        buffer.append("[^A-Z]*");
                    }

                    buffer.append("[");
                    buffer.append(c);
                    if (allowToLower || i == 0) {
                        buffer.append('|');
                        buffer.append(Character.toLowerCase(c));
                    }
                    buffer.append("]");
                } else if (Character.isLowerCase(c)) {
                    buffer.append('[');
                    buffer.append(c);
                    buffer.append('|');
                    buffer.append(Character.toUpperCase(c));
                    buffer.append(']');
                } else {
                    buffer.append(c);
                }

                firstIdentifierLetter = false;
            } else if (c == '*') {
                buffer.append(".*");
                firstIdentifierLetter = true;
            } else if (c == '.') {
                buffer.append("\\.");
                firstIdentifierLetter = true;
            } else if (c == ' ') {
                buffer.append(".*\\ ");
                firstIdentifierLetter = true;
            } else {
                firstIdentifierLetter = true;
                // for standard RegExp engine
                // buffer.append("\\u");
                // buffer.append(Integer.toHexString(c + 0x20000).substring(1));

                // for OROMATCHER RegExp engine
                buffer.append("\\x");
                buffer.append(Integer.toHexString(c + 0x20000).substring(3));
            }
        }

        buffer.append(".*");
        return buffer.toString();
    }

    private static boolean containsOnlyUppercaseLetters(String s) {
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c != '*' && c != ' ' && !Character.isUpperCase(c))
                return false;
        }
        return true;
    }

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

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

    private final ThreadLocal<PatternMatcher> myMatcher = new ThreadLocal<PatternMatcher>() {
        @Override
        protected PatternMatcher initialValue() {
            return new Perl5Matcher();
        }
    };

    PatternMatcher getMatcher() {
        return myMatcher.get();
    }

    public static class ActionWrapper implements Comparable<ActionWrapper> {
        private final AnAction myAction;
        private final MatchMode myMode;
        private final String myGroupName;
        private final DataContext myDataContext;
        private Presentation myPresentation;

        public ActionWrapper(@NotNull AnAction action, String groupName, MatchMode mode, DataContext dataContext) {
            myAction = action;
            myMode = mode;
            myGroupName = groupName;
            myDataContext = dataContext;
        }

        public AnAction getAction() {
            return myAction;
        }

        public MatchMode getMode() {
            return myMode;
        }

        @Override
        public int compareTo(@NotNull ActionWrapper o) {
            int compared = myMode.compareTo(o.getMode());
            if (compared != 0)
                return compared;
            Presentation myPresentation = myAction.getTemplatePresentation();
            Presentation oPresentation = o.getAction().getTemplatePresentation();
            int byText = StringUtil.compare(myPresentation.getText(), oPresentation.getText(), true);
            if (byText != 0)
                return byText;
            int byGroup = Comparing.compare(myGroupName, o.getGroupName());
            if (byGroup != 0)
                return byGroup;
            int byDesc = StringUtil.compare(myPresentation.getDescription(), oPresentation.getDescription(), true);
            if (byDesc != 0)
                return byDesc;
            return 0;
        }

        private boolean isAvailable() {
            return getPresentation().isEnabledAndVisible();
        }

        public Presentation getPresentation() {
            if (myPresentation != null)
                return myPresentation;
            return myPresentation = updateActionBeforeShow(myAction, myDataContext).getPresentation();
        }

        public String getGroupName() {
            return myGroupName;
        }

        public boolean isGroupAction() {
            return myAction instanceof ActionGroup;
        }

        @Override
        public boolean equals(Object obj) {
            return obj instanceof ActionWrapper && compareTo((ActionWrapper) obj) == 0;
        }

        @Override
        public int hashCode() {
            return myAction.getTemplatePresentation().getText().hashCode();
        }
    }
}