| /* |
| * 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.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<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.<AnAction>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<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)); |
| } |
| } |