/* * Copyright 2000-2016 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.openapi.actionSystem; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; import com.intellij.util.FunctionUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.Collection; import java.util.List; /** * A default implementation of {@link ActionGroup}. Provides the ability * to add children actions and separators between them. In most of the * cases you will be using this implementation but note that there are * cases (for example "Recent files" dialog) where children are determined * on rules different than just positional constraints, that's when you need * to implement your own <code>ActionGroup</code>. * * @see Constraints * * @see com.intellij.openapi.actionSystem.ComputableActionGroup * * @see com.intellij.ide.actions.NonEmptyActionGroup * @see com.intellij.ide.actions.NonTrivialActionGroup * @see com.intellij.ide.actions.SmartPopupActionGroup * */ public class DefaultActionGroup extends ActionGroup { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.actionSystem.DefaultActionGroup"); /** * Contains instances of AnAction */ private final List<AnAction> mySortedChildren = ContainerUtil.createLockFreeCopyOnWriteList(); /** * Contains instances of Pair */ private final List<Pair<AnAction, Constraints>> myPairs = ContainerUtil.createLockFreeCopyOnWriteList(); public DefaultActionGroup() { this(null, false); } /** * Creates an action group containing the specified actions. * * @param actions the actions to add to the group * @since 9.0 */ public DefaultActionGroup(@NotNull AnAction... actions) { this(Arrays.asList(actions)); } /** * Creates an action group containing the specified actions. * * @param actions the actions to add to the group * @since 13.0 */ public DefaultActionGroup(@NotNull List<? extends AnAction> actions) { this(null, false); addActions(actions); } public DefaultActionGroup(@NotNull String name, @NotNull List<? extends AnAction> actions) { this(name, false); addActions(actions); } private void addActions(@NotNull List<? extends AnAction> actions) { for (AnAction action : actions) { add(action); } } public DefaultActionGroup(String shortName, boolean popup) { super(shortName, popup); } /** * Adds the specified action to the tail. * * @param action Action to be added * @param actionManager ActionManager instance */ public final void add(@NotNull AnAction action, @NotNull ActionManager actionManager) { add(action, Constraints.LAST, actionManager); } public final void add(@NotNull AnAction action) { addAction(action, Constraints.LAST); } public final ActionInGroup addAction(@NotNull AnAction action) { return addAction(action, Constraints.LAST); } /** * Adds a separator to the tail. */ public final void addSeparator() { add(Separator.getInstance()); } /** * Adds the specified action with the specified constraint. * * @param action Action to be added; cannot be null * @param constraint Constraint to be used for determining action's position; cannot be null * @throws IllegalArgumentException in case when: * <li>action is null * <li>constraint is null * <li>action is already in the group */ public final void add(@NotNull AnAction action, @NotNull Constraints constraint) { add(action, constraint, ActionManager.getInstance()); } public final ActionInGroup addAction(@NotNull AnAction action, @NotNull Constraints constraint) { return addAction(action, constraint, ActionManager.getInstance()); } public final void add(@NotNull AnAction action, @NotNull Constraints constraint, @NotNull ActionManager actionManager) { addAction(action, constraint, actionManager); } public final ActionInGroup addAction(@NotNull AnAction action, @NotNull Constraints constraint, @NotNull ActionManager actionManager) { if (action == this) { throw new IllegalArgumentException("Cannot add a group to itself"); } // Check that action isn't already registered if (!(action instanceof Separator)) { if (mySortedChildren.contains(action)) { throw new IllegalArgumentException("cannot add an action twice: " + action); } for (Pair<AnAction, Constraints> pair : myPairs) { if (action.equals(pair.first)) { throw new IllegalArgumentException("cannot add an action twice: " + action); } } } constraint = (Constraints)constraint.clone(); if (constraint.myAnchor == Anchor.FIRST) { mySortedChildren.add(0, action); } else if (constraint.myAnchor == Anchor.LAST) { mySortedChildren.add(action); } else { if (addToSortedList(action, constraint, actionManager)) { actionAdded(action, actionManager); } else { myPairs.add(Pair.create(action, constraint)); } } return new ActionInGroup(this, action); } private void actionAdded(AnAction addedAction, ActionManager actionManager) { String addedActionId = addedAction instanceof ActionStub ? ((ActionStub)addedAction).getId() : actionManager.getId(addedAction); if (addedActionId == null) { return; } outer: while (!myPairs.isEmpty()) { for (int i = 0; i < myPairs.size(); i++) { Pair<AnAction, Constraints> pair = myPairs.get(i); if (addToSortedList(pair.first, pair.second, actionManager)) { myPairs.remove(i); continue outer; } } break; } } private boolean addToSortedList(@NotNull AnAction action, Constraints constraint, ActionManager actionManager) { int index = findIndex(constraint.myRelativeToActionId, mySortedChildren, actionManager); if (index == -1) { return false; } if (constraint.myAnchor == Anchor.BEFORE) { mySortedChildren.add(index, action); } else { mySortedChildren.add(index + 1, action); } return true; } private static int findIndex(String actionId, List<? extends AnAction> actions, ActionManager actionManager) { for (int i = 0; i < actions.size(); i++) { AnAction action = actions.get(i); if (action instanceof ActionStub) { if (((ActionStub)action).getId().equals(actionId)) { return i; } } else { String id = actionManager.getId(action); if (id != null && id.equals(actionId)) { return i; } } } return -1; } /** * Removes specified action from group. * * @param action Action to be removed */ public final void remove(AnAction action) { if (!mySortedChildren.remove(action)) { for (int i = 0; i < myPairs.size(); i++) { Pair<AnAction, Constraints> pair = myPairs.get(i); if (pair.first.equals(action)) { myPairs.remove(i); break; } } } } /** * Removes all children actions (separators as well) from the group. */ public final void removeAll() { mySortedChildren.clear(); myPairs.clear(); } /** * Replaces specified action with the a one. */ public boolean replaceAction(@NotNull AnAction oldAction, @NotNull AnAction newAction) { int index = mySortedChildren.indexOf(oldAction); if (index >= 0) { mySortedChildren.set(index, newAction); return true; } else { for (int i = 0; i < myPairs.size(); i++) { Pair<AnAction, Constraints> pair = myPairs.get(i); if (pair.first.equals(newAction)) { myPairs.set(i, Pair.create(newAction, pair.second)); return true; } } } return false; } /** * Copies content from <code>group</code>. * @param other group to copy from */ public void copyFromGroup(@NotNull DefaultActionGroup other) { copyFrom(other); setPopup(other.isPopup()); mySortedChildren.clear(); mySortedChildren.addAll(other.mySortedChildren); myPairs.clear(); myPairs.addAll(other.myPairs); } /** * Returns group's children in the order determined by constraints. * * @param e not used * @return An array of children actions */ @Override @NotNull public final AnAction[] getChildren(@Nullable AnActionEvent e) { boolean hasNulls = false; // Mix sorted actions and pairs int sortedSize = mySortedChildren.size(); AnAction[] children = new AnAction[sortedSize + myPairs.size()]; for (int i = 0; i < sortedSize; i++) { AnAction action = mySortedChildren.get(i); if (action == null) { LOG.error("Empty sorted child: " + this + ", " + getClass() + "; index=" + i); } if (action instanceof ActionStub) { action = unStub(e, (ActionStub)action); if (action == null) { LOG.error("Can't unstub " + mySortedChildren.get(i)); } else { mySortedChildren.set(i, action); } } hasNulls |= action == null; children[i] = action; } for (int i = 0; i < myPairs.size(); i++) { final Pair<AnAction, Constraints> pair = myPairs.get(i); AnAction action = pair.first; if (action == null) { LOG.error("Empty pair child: " + this + ", " + getClass() + "; index=" + i); } else if (action instanceof ActionStub) { action = unStub(e, (ActionStub)action); if (action == null) { LOG.error("Can't unstub " + pair); } else { myPairs.set(i, Pair.create(action, pair.second)); } } hasNulls |= action == null; children[i + sortedSize] = action; } if (hasNulls) { return ContainerUtil.mapNotNull(children, FunctionUtil.id(), AnAction.EMPTY_ARRAY); } return children; } @Nullable private AnAction unStub(@Nullable AnActionEvent e, final ActionStub stub) { ActionManager actionManager = e != null ? e.getActionManager() : ActionManager.getInstance(); try { AnAction action = actionManager.getAction(stub.getId()); if (action == null) { LOG.error("Null child action in group " + this + " of class " + getClass() + ", id=" + stub.getId()); return null; } replace(stub, action); return action; } catch (Throwable e1) { LOG.error(e1); return null; } } /** * Returns the number of contained children (including separators). * * @return number of children in the group */ public final int getChildrenCount() { return mySortedChildren.size() + myPairs.size(); } @NotNull public final AnAction[] getChildActionsOrStubs() { // Mix sorted actions and pairs int sortedSize = mySortedChildren.size(); AnAction[] children = new AnAction[sortedSize + myPairs.size()]; for (int i = 0; i < sortedSize; i++) { children[i] = mySortedChildren.get(i); } for (int i = 0; i < myPairs.size(); i++) { children[i + sortedSize] = myPairs.get(i).first; } return children; } public final void addAll(ActionGroup group) { for (AnAction each : group.getChildren(null)) { add(each); } } public final void addAll(Collection<? extends AnAction> actionList) { for (AnAction each : actionList) { add(each); } } public final void addAll(AnAction... actions) { for (AnAction each : actions) { add(each); } } public void addSeparator(@Nullable String separatorText) { add(new Separator(separatorText)); } }