blob: 44f910eb04dd23886b962ac56667761688436da5 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.ide.common.api;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.utils.Pair;
import com.google.common.annotations.Beta;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
/**
* A {@link RuleAction} represents an action provided by an {@link IViewRule}, typically
* shown in a context menu or in the layout actions bar.
* <p/>
* Each action should have a reasonably unique ID. This is used when multiple nodes
* are selected to filter the actions down to just those actions that are supported
* across all selected nodes. If an action does not support multiple nodes, it can
* return false from {@link #supportsMultipleNodes()}.
* <p/>
* Actions can be grouped into a hierarchy of sub-menus using the {@link NestedAction} class,
* or into a flat submenu using the {@link Choices} class.
* <p/>
* Actions (including separators) all have a "sort priority", and this is used to
* sort the menu items or toolbar buttons into a specific order.
* <p>
* <b>NOTE: This is not a public or final API; if you rely on this be prepared
* to adjust your code for the next tools release.</b>
* </p>
*/
@Beta
public class RuleAction implements Comparable<RuleAction> {
/**
* Character used to split multiple checked choices.
* The pipe character "|" is used, to natively match Android resource flag separators.
*/
public static final String CHOICE_SEP = "|"; //$NON-NLS-1$
/**
* Same as {@link #CHOICE_SEP} but safe for use in regular expressions.
*/
public static final String CHOICE_SEP_PATTERN = Pattern.quote(CHOICE_SEP);
/**
* The unique id of the action.
* @see #getId()
*/
private final String mId;
/**
* The UI-visible title of the action.
*/
private final String mTitle;
/** A URL pointing to an icon, or null */
private URL mIconUrl;
/**
* A callback executed when the action is selected in the context menu.
*/
private final IMenuCallback mCallback;
/**
* The sorting priority of this item; actions can be sorted according to these
*/
protected final int mSortPriority;
/**
* Whether this action supports multiple nodes, see
* {@link #supportsMultipleNodes()} for details.
*/
private final boolean mSupportsMultipleNodes;
/**
* Special value which will insert a separator in the choices' submenu.
*/
public static final String SEPARATOR = "----";
// Factories
/**
* Constructs a new separator which will be shown in places where separators
* are supported such as context menus
*
* @param sortPriority a priority used for sorting this action
* @return a new separator
*/
@NonNull
public static Separator createSeparator(int sortPriority) {
return new Separator(sortPriority, true /* supportsMultipleNodes*/);
}
/**
* Constructs a new base {@link RuleAction} with its ID, title and action callback.
*
* @param id The unique ID of the action. Must not be null.
* @param title The title of the action. Must not be null.
* @param callback The callback executed when the action is selected.
* Must not be null.
* @param iconUrl a URL pointing to an icon to use for this action, or null
* @param sortPriority a priority used for sorting this action
* @param supportsMultipleNodes whether this action supports multiple nodes,
* see {@link #supportsMultipleNodes()} for details
* @return the new {@link RuleAction}
*/
@NonNull
public static RuleAction createAction(
@NonNull String id,
@NonNull String title,
@NonNull IMenuCallback callback,
@Nullable URL iconUrl,
int sortPriority,
boolean supportsMultipleNodes) {
RuleAction action = new RuleAction(id, title, callback, sortPriority,
supportsMultipleNodes);
action.setIconUrl(iconUrl);
return action;
}
/**
* Creates a new immutable toggle action.
*
* @param id The unique id of the action. Cannot be null.
* @param title The UI-visible title of the context menu item. Cannot be null.
* @param isChecked Whether the context menu item has a check mark.
* @param callback A callback to execute when the context menu item is
* selected.
* @param iconUrl a URL pointing to an icon to use for this action, or null
* @param sortPriority a priority used for sorting this action
* @param supportsMultipleNodes whether this action supports multiple nodes,
* see {@link #supportsMultipleNodes()} for details
* @return the new {@link Toggle}
*/
@NonNull
public static Toggle createToggle(
@NonNull String id,
@NonNull String title,
boolean isChecked,
@NonNull IMenuCallback callback,
@Nullable URL iconUrl,
int sortPriority,
boolean supportsMultipleNodes) {
Toggle toggle = new Toggle(id, title, isChecked, callback, sortPriority,
supportsMultipleNodes);
toggle.setIconUrl(iconUrl);
return toggle;
}
/**
* Creates a new immutable multiple-choice action with a defined ordered set
* of action children.
*
* @param id The unique id of the action. Cannot be null.
* @param title The title of the action to be displayed to the user
* @param provider Provides the actions to be shown as children of this
* action
* @param callback A callback to execute when the context menu item is
* selected.
* @param iconUrl the icon to use for the multiple choice action itself
* @param sortPriority the sorting priority to use for the multiple choice
* action itself
* @param supportsMultipleNodes whether this action supports multiple nodes,
* see {@link #supportsMultipleNodes()} for details
* @return the new {@link NestedAction}
*/
@NonNull
public static NestedAction createChoices(
@NonNull String id,
@NonNull String title,
@NonNull IMenuCallback callback,
@Nullable URL iconUrl,
int sortPriority,
boolean supportsMultipleNodes,
@NonNull ActionProvider provider) {
NestedAction choices = new NestedAction(id, title, provider, callback,
sortPriority, supportsMultipleNodes);
choices.setIconUrl(iconUrl);
return choices;
}
/**
* Creates a new immutable multiple-choice action with a defined ordered set
* of children.
*
* @param id The unique id of the action. Cannot be null.
* @param title The title of the action to be displayed to the user
* @param iconUrls The icon urls for the children items (may be null)
* @param ids The internal ids for the children
* @param current The id(s) of the current choice(s) that will be check
* marked. Can be null. Can be an id not present in the choices
* map. There can be more than one id separated by
* {@link #CHOICE_SEP}.
* @param callback A callback to execute when the context menu item is
* selected.
* @param titles The UI-visible titles of the children
* @param iconUrl the icon to use for the multiple choice action itself
* @param sortPriority the sorting priority to use for the multiple choice
* action itself
* @param supportsMultipleNodes whether this action supports multiple nodes,
* see {@link #supportsMultipleNodes()} for details
* @return the new {@link Choices}
*/
@NonNull
public static Choices createChoices(
@NonNull String id,
@NonNull String title,
@NonNull IMenuCallback callback,
@NonNull List<String> titles,
@Nullable List<URL> iconUrls,
@NonNull List<String> ids,
@Nullable String current,
@Nullable URL iconUrl,
int sortPriority,
boolean supportsMultipleNodes) {
Choices choices = new Choices(id, title, callback, titles, iconUrls,
ids, current, sortPriority, supportsMultipleNodes);
choices.setIconUrl(iconUrl);
return choices;
}
/**
* Creates a new immutable multiple-choice action with a defined ordered set
* of children.
*
* @param id The unique id of the action. Cannot be null.
* @param title The title of the action to be displayed to the user
* @param iconUrls The icon urls for the children items (may be null)
* @param current The id(s) of the current choice(s) that will be check
* marked. Can be null. Can be an id not present in the choices
* map. There can be more than one id separated by
* {@link #CHOICE_SEP}.
* @param callback A callback to execute when the context menu item is
* selected.
* @param iconUrl the icon to use for the multiple choice action itself
* @param sortPriority the sorting priority to use for the multiple choice
* action itself
* @param supportsMultipleNodes whether this action supports multiple nodes,
* see {@link #supportsMultipleNodes()} for details
* @param idsAndTitles a list of pairs (of ids and titles) to use for the
* menu items
* @return the new {@link Choices}
*/
@NonNull
public static Choices createChoices(
@NonNull String id,
@NonNull String title,
@NonNull IMenuCallback callback,
@Nullable List<URL> iconUrls,
@Nullable String current,
@Nullable URL iconUrl,
int sortPriority,
boolean supportsMultipleNodes,
@NonNull List<Pair<String, String>> idsAndTitles) {
int itemCount = idsAndTitles.size();
List<String> titles = new ArrayList<String>(itemCount);
List<String> ids = new ArrayList<String>(itemCount);
for (Pair<String, String> pair : idsAndTitles) {
ids.add(pair.getFirst());
titles.add(pair.getSecond());
}
Choices choices = new Choices(id, title, callback, titles, iconUrls,
ids, current, sortPriority, supportsMultipleNodes);
choices.setIconUrl(iconUrl);
return choices;
}
/**
* Creates a new immutable multiple-choice action with lazily computed children.
*
* @param id The unique id of the action. Cannot be null.
* @param title The title of the multiple-choice itself
* @param callback A callback to execute when the context menu item is
* selected.
* @param provider the provider which provides choices lazily
* @param current The id(s) of the current choice(s) that will be check
* marked. Can be null. Can be an id not present in the choice
* alternatives. There can be more than one id separated by
* {@link #CHOICE_SEP}.
* @param iconUrl the icon to use for the multiple choice action itself
* @param sortPriority the sorting priority to use for the multiple choice
* action itself
* @param supportsMultipleNodes whether this action supports multiple nodes,
* see {@link #supportsMultipleNodes()} for details
* @return the new {@link Choices}
*/
@NonNull
public static Choices createChoices(
@NonNull String id,
@NonNull String title,
IMenuCallback callback,
@NonNull ChoiceProvider provider,
@Nullable String current,
@Nullable URL iconUrl,
int sortPriority,
boolean supportsMultipleNodes) {
Choices choices = new DelayedChoices(id, title, callback,
current, provider, sortPriority, supportsMultipleNodes);
choices.setIconUrl(iconUrl);
return choices;
}
/**
* Creates a new {@link RuleAction} with the given id and the given title.
* Actions which have the same id and the same title are deemed equivalent.
*
* @param id The unique id of the action, which must be similar for all actions that
* perform the same task. Cannot be null.
* @param title The UI-visible title of the action.
* @param callback A callback to execute when the context menu item is
* selected.
* @param sortPriority a priority used for sorting this action
* @param supportsMultipleNodes the new return value for
* {@link #supportsMultipleNodes()}
*/
private RuleAction(
@NonNull String id,
@NonNull String title,
@NonNull IMenuCallback callback,
int sortPriority,
boolean supportsMultipleNodes) {
mId = id;
mTitle = title;
mSortPriority = sortPriority;
mSupportsMultipleNodes = supportsMultipleNodes;
mCallback = callback;
}
/**
* Returns the unique id of the action. In the context of a multiple selection,
* actions which have the same id are collapsed together and must represent the same
* action. Cannot be null.
*
* @return the unique id of the action, never null
*/
@NonNull
public String getId() {
return mId;
}
/**
* Returns the UI-visible title of the action, shown in the context menu.
* Cannot be null.
*
* @return the user name of the action, never null
*/
@NonNull
public String getTitle() {
return mTitle;
}
/**
* Actions which have the same id and the same title are deemed equivalent.
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof RuleAction) {
RuleAction rhs = (RuleAction) obj;
if (mId != rhs.mId && !(mId != null && mId.equals(rhs.mId))) return false;
if (mTitle != rhs.mTitle &&
!(mTitle != null && mTitle.equals(rhs.mTitle))) return false;
return true;
}
return false;
}
/**
* Whether this action supports multiple nodes. An action which supports
* multiple nodes can be applied to different nodes by passing in different
* nodes to its callback. Some actions are hardcoded for a specific node (typically
* one that isn't selected, such as an action which affects the parent of a selected
* node), and these actions will not be added to the context menu when more than
* one node is selected.
*
* @return true if this node supports multiple nodes
*/
public boolean supportsMultipleNodes() {
return mSupportsMultipleNodes;
}
/**
* Actions which have the same id and the same title have the same hash code.
*/
@Override
public int hashCode() {
int h = mId == null ? 0 : mId.hashCode();
h = h ^ (mTitle == null ? 0 : mTitle.hashCode());
return h;
}
/**
* Gets a URL pointing to an icon to use for this action, if any.
*
* @return a URL pointing to an icon to use for this action, or null
*/
public URL getIconUrl() {
return mIconUrl;
}
/**
* Sets a URL pointing to an icon to use for this action, if any.
*
* @param iconUrl a URL pointing to an icon to use for this action, or null
* @return this action, to allow setter chaining
*/
@NonNull
public RuleAction setIconUrl(URL iconUrl) {
mIconUrl = iconUrl;
return this;
}
/**
* Return a priority used for sorting this action
*
* @return a priority used for sorting this action
*/
public int getSortPriority() {
return mSortPriority;
}
/**
* Returns the callback executed when the action is selected in the
* context menu. Cannot be null.
*
* @return the callback, never null
*/
@NonNull
public IMenuCallback getCallback() {
return mCallback;
}
// Implements Comparable<MenuAction>
@Override
public int compareTo(RuleAction other) {
if (mSortPriority != other.mSortPriority) {
return mSortPriority - other.mSortPriority;
}
return mTitle.compareTo(other.mTitle);
}
@NonNull
@Override
public String toString() {
return "RuleAction [id=" + mId + ", title=" + mTitle + ", priority=" + mSortPriority + "]";
}
/** A separator to display between actions */
public static class Separator extends RuleAction {
/** Construct using the factory {@link #createSeparator(int)} */
private Separator(int sortPriority, boolean supportsMultipleNodes) {
super("_separator", "", IMenuCallback.NONE, sortPriority, //$NON-NLS-1$ //$NON-NLS-2$
supportsMultipleNodes);
}
}
/**
* A toggle is a simple on/off action, displayed as an item in a context menu
* with a check mark if the item is checked.
* <p/>
* Two toggles are equal if they have the same id, title and group-id.
* It is expected for the checked state and action callback to be different.
*/
public static class Toggle extends RuleAction {
/**
* True if the item is displayed with a check mark.
*/
private final boolean mIsChecked;
/**
* Creates a new immutable toggle action.
*
* @param id The unique id of the action. Cannot be null.
* @param title The UI-visible title of the context menu item. Cannot be null.
* @param isChecked Whether the context menu item has a check mark.
* @param callback A callback to execute when the context menu item is
* selected.
*/
private Toggle(
@NonNull String id,
@NonNull String title,
boolean isChecked,
@NonNull IMenuCallback callback,
int sortPriority,
boolean supportsMultipleNodes) {
super(id, title, callback, sortPriority, supportsMultipleNodes);
mIsChecked = isChecked;
}
/**
* Returns true if the item is displayed with a check mark.
*
* @return true if the item is displayed with a check mark.
*/
public boolean isChecked() {
return mIsChecked;
}
/**
* Two toggles are equal if they have the same id and title.
* It is acceptable for the checked state and action callback to be different.
*/
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
/**
* Two toggles have the same hash code if they have the same id and title.
*/
@Override
public int hashCode() {
return super.hashCode();
}
}
/**
* An ordered list of choices the user can choose between. For choosing between
* actions, there is a {@link NestedAction} class.
*/
public static class Choices extends RuleAction {
protected List<String> mTitles;
protected List<URL> mIconUrls;
protected List<String> mIds;
private boolean mRadio;
/**
* One or more id for the checked choice(s) that will be check marked.
* Can be null. Can be an id not present in the choices map.
*/
protected final String mCurrent;
private Choices(
@NonNull String id,
@NonNull String title,
@NonNull IMenuCallback callback,
@NonNull List<String> titles,
@Nullable List<URL> iconUrls,
@NonNull List<String> ids,
@Nullable String current,
int sortPriority,
boolean supportsMultipleNodes) {
super(id, title, callback, sortPriority, supportsMultipleNodes);
mTitles = titles;
mIconUrls = iconUrls;
mIds = ids;
mCurrent = current;
}
/**
* Returns the list of urls to icons to display for each choice, or null
*
* @return the list of urls to icons to display for each choice, or null
*/
@Nullable
public List<URL> getIconUrls() {
return mIconUrls;
}
/**
* Returns the list of ids for the menu choices, never null
*
* @return the list of ids for the menu choices, never null
*/
@NonNull
public List<String> getIds() {
return mIds;
}
/**
* Returns the titles to be displayed for the menu choices, never null
*
* @return the titles to be displayed for the menu choices, never null
*/
@NonNull
public List<String> getTitles() {
return mTitles;
}
/**
* Returns the current value of the choice
*
* @return the current value of the choice, possibly null
*/
@Nullable
public String getCurrent() {
return mCurrent;
}
/**
* Set whether this choice list is best visualized as a radio group (instead of a
* dropdown)
*
* @param radio true if this choice list should be visualized as a radio group
*/
public void setRadio(boolean radio) {
mRadio = radio;
}
/**
* Returns true if this choice list is best visualized as a radio group (instead
* of a dropdown)
*
* @return true if this choice list should be visualized as a radio group
*/
public boolean isRadio() {
return mRadio;
}
}
/**
* An ordered list of actions the user can choose between. Similar to
* {@link Choices} but for actions instead.
*/
public static class NestedAction extends RuleAction {
/** The provider to produce the list of nested actions when needed */
private final ActionProvider mProvider;
private NestedAction(
@NonNull String id,
@NonNull String title,
@NonNull ActionProvider provider,
@NonNull IMenuCallback callback,
int sortPriority,
boolean supportsMultipleNodes) {
super(id, title, callback, sortPriority, supportsMultipleNodes);
mProvider = provider;
}
/**
* Returns the nested actions available for the given node
*
* @param node the node to look up nested actions for
* @return a list of nested actions
*/
@NonNull
public List<RuleAction> getNestedActions(@NonNull INode node) {
return mProvider.getNestedActions(node);
}
}
/** Like {@link Choices}, but the set of choices is computed lazily */
private static class DelayedChoices extends Choices {
private final ChoiceProvider mProvider;
private boolean mInitialized;
private DelayedChoices(
@NonNull String id,
@NonNull String title,
@NonNull IMenuCallback callback,
@Nullable String current,
@NonNull ChoiceProvider provider,
int sortPriority, boolean supportsMultipleNodes) {
super(id, title, callback, new ArrayList<String>(), new ArrayList<URL>(),
new ArrayList<String>(), current, sortPriority, supportsMultipleNodes);
mProvider = provider;
}
private void ensureInitialized() {
if (!mInitialized) {
mInitialized = true;
mProvider.addChoices(mTitles, mIconUrls, mIds);
}
}
@Override
public List<URL> getIconUrls() {
ensureInitialized();
return mIconUrls;
}
@NonNull
@Override
public List<String> getIds() {
ensureInitialized();
return mIds;
}
@NonNull
@Override
public List<String> getTitles() {
ensureInitialized();
return mTitles;
}
}
/**
* Provides the set of nested action choices associated with a {@link NestedAction}
* object when they are needed. Useful for lazy initialization of context
* menus and popup menus until they are actually needed.
*/
public interface ActionProvider {
/**
* Returns the nested actions available for the given node
*
* @param node the node to look up nested actions for
* @return a list of nested actions
*/
@NonNull
List<RuleAction> getNestedActions(@NonNull INode node);
}
/**
* Provides the set of choices associated with an {@link Choices}
* object when they are needed. Useful for lazy initialization of context
* menus and popup menus until they are actually needed.
*/
public interface ChoiceProvider {
/**
* Adds in the needed titles, iconUrls (if any) and ids.
* Use {@link RuleAction#SEPARATOR} to create separators.
*
* @param titles a list of titles that the provider should append to
* @param iconUrls a list of icon URLs that the provider should append to
* @param ids a list of ids that the provider should append to
*/
void addChoices(
@NonNull List<String> titles,
@NonNull List<URL> iconUrls,
@NonNull List<String> ids);
}
}