blob: cfc5270ba4c031ef64823f3113c713885022cf84 [file] [log] [blame]
/*
* 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));
}
}