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