diff options
23 files changed, 1500 insertions, 1426 deletions
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt index 8a41c8d..5dfa5ba 100644 --- a/eclipse/dictionary.txt +++ b/eclipse/dictionary.txt @@ -79,6 +79,7 @@ dp dpi drawable drawables +dropdown ed editable endpoint diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IMenuCallback.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IMenuCallback.java index 1906436..80f77b8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IMenuCallback.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IMenuCallback.java @@ -16,9 +16,13 @@ package com.android.ide.common.api; +import java.util.List; + /** - * Callback interface for {@link MenuAction}s. The callback performs the actual - * work of the menu. + * Callback interface for a {@link RuleAction}. The callback performs the actual + * work of the action, and this level of indirection allows multiple actions (which + * typically do not have their own class, only their own instances) to share a single + * implementation callback class. * <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> @@ -26,14 +30,15 @@ package com.android.ide.common.api; */ public interface IMenuCallback { /** - * Performs the actual work promised by the {@link MenuAction}. - * - * @param action The MenuAction being applied. + * Performs the actual work promised by the {@link RuleAction}. + * @param action The action being applied. + * @param selectedNodes The nodes to apply the action to * @param valueId For a Choices action, the string id of the selected choice * @param newValue For a toggle or for a flag, true if the item is being * checked, false if being unchecked. For enums this is not * useful; however for flags it allows one to add or remove items * to the flag's choices. */ - void action(MenuAction menuAction, String valueId, Boolean newValue); + void action(RuleAction action, List<? extends INode> selectedNodes, String valueId, + Boolean newValue); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IViewRule.java index a16db28..d29ef71 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IViewRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IViewRule.java @@ -67,19 +67,22 @@ public interface IViewRule { * If null is returned, the GLE will automatically shorten the class name using its * own heuristic, which is to keep the first 2 package components and the class name. * The class name is the <code>fqcn</code> argument that was given - * to {@link #onInitialize(String)}. + * to {@link #onInitialize(String,IClientRulesEngine)}. * * @return Null for the default behavior or a shortened string. */ String getDisplayName(); /** - * Invoked by the Rules Engine to retrieve a set of actions to customize + * Invoked by the Rules Engine to produce a set of actions to customize * the context menu displayed for this view. The result is not cached and the * method is invoked every time the context menu is about to be shown. + * <p> + * The order of the menu items is determined by the sort priority set on + * the actions. * <p/> - * Most rules should consider returning <code>super.getContextMenu(node)</code> - * and appending their own custom menu actions, if any. + * Most rules should consider calling super.{@link #addContextMenuActions(List, INode)} + * as well. * <p/> * Menu actions are either toggles or fixed lists with one currently-selected * item. It is expected that the rule will need to recreate the actions with @@ -87,16 +90,18 @@ public interface IViewRule { * is not cached. However rules are encouraged to cache some or all of the result * to speed up following calls if it makes sense. * - * @return Null for no context menu, or a new {@link MenuAction} describing one - * or more actions to display in the context menu. + * @param actions a list of actions to add new context menu actions into. The order + * of the actions in this list is not important; it will be sorted by + * {@link RuleAction#getSortPriority()} later. + * @param node the node to add actions for. */ - List<MenuAction> getContextMenu(INode node); + void addContextMenuActions(List<RuleAction> actions, INode node); /** * Invoked by the Rules Engine to ask the parent layout for the set of layout actions * to display in the layout bar. The layout rule should add these into the provided * list. The order the items are added in does not matter; the - * {@link MenuAction#getSortPriority()} values will be used to sort the actions prior + * {@link RuleAction#getSortPriority()} values will be used to sort the actions prior * to display, which makes it easier for parent rules and deriving rules to interleave * their respective actions. * @@ -104,7 +109,7 @@ public interface IViewRule { * @param parentNode the parent of the selection, or the selection itself if the root * @param targets the targeted/selected nodes, if any */ - void addLayoutActions(List<MenuAction> actions, + void addLayoutActions(List<RuleAction> actions, INode parentNode, List<? extends INode> targets); // ==== Selection ==== diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/MenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/MenuAction.java deleted file mode 100755 index 3e912f8..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/MenuAction.java +++ /dev/null @@ -1,669 +0,0 @@ -/* - * 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.Nullable; - -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * A menu action represents one item in the context menu displayed by the GLE canvas. - * <p/> - * Each action should have a reasonably unique ID. By default actions are stored using - * the lexicographical order of the IDs. - * Duplicated IDs will be ignored -- that is the first one found will be used. - * <p/> - * When the canvas has a multiple selection, only actions that are present in <em>all</em> - * the selected nodes are shown. Moreover, for a given ID all actions must be equal, for - * example they must have the same title and choice but not necessarily the same selection. <br/> - * This allows the canvas to only display compatible actions that will work on all selected - * elements. - * <p/> - * Actions can be grouped in sub-menus if necessary. Whether groups (sub-menus) can contain - * other groups is implementation dependent. Currently the canvas does not support this, but - * we may decide to change this behavior later if deemed useful. - * <p/> - * All actions and groups are sorted by their ID, using String's natural sorting order. - * The only way to change this sorting is by choosing the IDs so they the result end up - * sorted as you want it. - * <p/> - * The {@link MenuAction} is abstract. Users should instantiate either {@link Toggle}, - * {@link Choices} or {@link Group} instead. These classes are immutable. - * <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> - */ -public abstract class MenuAction implements Comparable<MenuAction> { - - /** - * 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; - - /** - * The sorting priority of this item; actions can be sorted according to these - */ - protected int mSortPriority; - - // Factories - - public static MenuAction createSeparator() { - return new Separator(sNextSortPriority++); - } - - public static MenuAction createSeparator(int sortPriority) { - return new Separator(sortPriority); - } - - - public static MenuAction createAction(String id, String title, String groupId, - IMenuCallback callback) { - MenuAction.Action action = new MenuAction.Action(id, title, groupId, callback); - action.setSortPriority(sNextSortPriority++); - return action; - } - - public static MenuAction createAction(String id, String title, String groupId, - IMenuCallback callback, URL iconUrl, int sortPriority) { - MenuAction action = new MenuAction.Action(id, title, groupId, callback); - action.setIconUrl(iconUrl); - action.setSortPriority(sortPriority); - return action; - } - - public static MenuAction createToggle(String id, String title, boolean isChecked, - IMenuCallback callback) { - Toggle action = new Toggle(id, title, isChecked, callback); - action.setSortPriority(sNextSortPriority++); - return action; - } - - public static MenuAction createToggle(String id, String title, boolean isChecked, - IMenuCallback callback, URL iconUrl, int sortPriority) { - Toggle toggle = new Toggle(id, title, isChecked, callback); - toggle.setIconUrl(iconUrl); - toggle.setSortPriority(sortPriority); - return toggle; - } - - public static MenuAction createChoices(String id, String title, String groupId, - IMenuCallback callback, List<String> titles, List<URL> iconUrls, List<String> ids, - String current) { - OrderedChoices action = new OrderedChoices(id, title, groupId, callback, titles, iconUrls, - ids, current); - action.setSortPriority(sNextSortPriority++); - return action; - } - - public static OrderedChoices createChoices(String id, String title, String groupId, - IMenuCallback callback, List<String> titles, List<URL> iconUrls, List<String> ids, - String current, URL iconUrl, int sortPriority) { - OrderedChoices choices = new OrderedChoices(id, title, groupId, callback, titles, iconUrls, - ids, current); - choices.setIconUrl(iconUrl); - choices.setSortPriority(sortPriority); - return choices; - } - - public static OrderedChoices createChoices(String id, String title, String groupId, - IMenuCallback callback, ChoiceProvider provider, - String current, URL iconUrl, int sortPriority) { - OrderedChoices choices = new DelayedOrderedChoices(id, title, groupId, callback, - current, provider); - choices.setIconUrl(iconUrl); - choices.setSortPriority(sortPriority); - return choices; - } - - /** - * Creates a new {@link MenuAction} 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. - */ - private MenuAction(String id, String title) { - mId = id; - mTitle = title; - } - - /** - * 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. - */ - public String getId() { - return mId; - } - - /** - * Returns the UI-visible title of the action, shown in the context menu. - * Cannot be null. - */ - 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 MenuAction) { - MenuAction rhs = (MenuAction) 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; - } - - /** - * 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 - */ - public void setIconUrl(URL iconUrl) { - mIconUrl = iconUrl; - } - - /** - * Sets a priority used for sorting this action - * - * @param sortPriority a priority used for sorting this action - */ - public void setSortPriority(int sortPriority) { - mSortPriority = sortPriority; - } - - private static int sNextSortPriority = 0; - - /** - * Return a priority used for sorting this action - * - * @return a priority used for sorting this action - */ - public int getSortPriority() { - return mSortPriority; - } - - // Implements Comparable<MenuAciton> - public int compareTo(MenuAction other) { - if (mSortPriority != other.mSortPriority) { - return mSortPriority - other.mSortPriority; - } - - return mTitle.compareTo(other.mTitle); - } - - /** - * A group of actions, displayed in a sub-menu. - * <p/> - * Note that group can be seen as a "group declaration": the group does not hold a list - * actions that it will contain. This merely let the canvas create a sub-menu with the - * given title and actions that define this group-id will be placed in the sub-menu. - * <p/> - * The current canvas has the following implementation details: <br/> - * - There's only one level of sub-menu. - * That is you can't have a sub-menu inside another sub-menu. - * This is expressed by the fact that groups do not have a parent group-id. <br/> - * - It is not currently necessary to define a group before defining actions that refer - * to that group. Moreover, in the context of a multiple selection, one view could - * contribute actions to any group even if created by another view. Both practices - * are discouraged. <br/> - * - Actions which group-id do not match any known group will simply be placed in the - * root context menu. <br/> - * - Empty groups do not create visible sub-menus. <br/> - * These implementations details may change in the future and should not be relied upon. - */ - public static class Group extends MenuAction { - - /** - * Constructs a new group of actions. - * - * @param id The id of the group. Must be unique. Cannot be null. - * @param title The UI-visible title of the group, shown in the sub-menu. - */ - public Group(String id, String title) { - super(id, title); - } - } - - /** - * The base class for {@link Toggle} and {@link Choices}. - */ - public static class Action extends MenuAction { - - /** - * A callback executed when the action is selected in the context menu. - */ - private final IMenuCallback mCallback; - - /** - * An optional group id, to place the action in a given sub-menu. - * @null This value can be null. - */ - @Nullable - private final String mGroupId; - - /** - * Constructs a new base {@link MenuAction} 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 groupId The optional group id, to place the action in a given sub-menu. - * Can be null. - * @param callback The callback executed when the action is selected. - * Must not be null. - */ - public Action(String id, String title, String groupId, IMenuCallback callback) { - super(id, title); - mGroupId = groupId; - mCallback = callback; - } - - /** - * Returns the callback executed when the action is selected in the - * context menu. Cannot be null. - */ - public IMenuCallback getCallback() { - return mCallback; - } - - /** - * Returns the optional id of an existing group or null - * @null This value can be null. - */ - @Nullable - public String getGroupId() { - return mGroupId; - } - - /** - * Two actions are equal if the have the same id, title and group-id. - */ - @Override - public boolean equals(Object obj) { - if (obj instanceof Action && super.equals(obj)) { - Action rhs = (Action) obj; - return mGroupId == rhs.mGroupId || - (mGroupId != null && mGroupId.equals(rhs.mGroupId)); - } - return false; - } - - /** - * Two actions have the same hash code if the have the same id, title and group-id. - */ - @Override - public int hashCode() { - int h = super.hashCode(); - h = h ^ (mGroupId == null ? 0 : mGroupId.hashCode()); - return h; - } - } - - /** A separator to display between actions */ - public static class Separator extends MenuAction { - /** Construct using the factory {@link #createSeparator(int)} */ - private Separator(int sortPriority) { - super("_separator", ""); //$NON-NLS-1$ //$NON-NLS-2$ - mSortPriority = sortPriority; - } - } - - /** - * 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 Action { - /** - * True if the item is displayed with a check mark. - */ - private final boolean mIsChecked; - - /** - * Creates a new immutable toggle action. - * This action has no group-id and will show up in the root of the context menu. - * - * @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. - */ - public Toggle(String id, String title, boolean isChecked, IMenuCallback callback) { - this(id, title, isChecked, null /* group-id */, callback); - } - - /** - * 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 groupId The optional group id, to place the action in a given sub-menu. - * Can be null. - * @param callback A callback to execute when the context menu item is - * selected. - */ - public Toggle(String id, String title, boolean isChecked, String groupId, - IMenuCallback callback) { - super(id, title, groupId, callback); - mIsChecked = isChecked; - } - - /** - * Returns 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, title and group-id. - * 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, title and group-id. - */ - @Override - public int hashCode() { - return super.hashCode(); - } - } - - /** - * Like {@link Choices}, but with an explicit ordering among the children, and with - * optional icons on each child choice - */ - public static class OrderedChoices extends Action { - 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; - - public OrderedChoices(String id, String title, String groupId, IMenuCallback callback, - List<String> titles, List<URL> iconUrls, List<String> ids, String current) { - super(id, title, groupId, callback); - mTitles = titles; - mIconUrls = iconUrls; - mIds = ids; - mCurrent = current; - } - - public List<URL> getIconUrls() { - return mIconUrls; - } - - public List<String> getIds() { - return mIds; - } - - public List<String> getTitles() { - return mTitles; - } - - 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; - } - } - - /** Provides the set of choices associated with an {@link OrderedChoices} 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. - * - * @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 - */ - public void addChoices(List<String> titles, List<URL> iconUrls, List<String> ids); - } - - /** Like {@link OrderedChoices}, but the set of choices is computed lazily */ - private static class DelayedOrderedChoices extends OrderedChoices { - private final ChoiceProvider mProvider; - - public DelayedOrderedChoices(String id, String title, String groupId, - IMenuCallback callback, String current, ChoiceProvider provider) { - super(id, title, groupId, callback, null, null, null, current); - mProvider = provider; - } - - private void ensureInitialized() { - if (mTitles == null) { - mTitles = new ArrayList<String>(); - mIconUrls = new ArrayList<URL>(); - mIds = new ArrayList<String>(); - - mProvider.addChoices(mTitles, mIconUrls, mIds); - } - } - - @Override - public List<URL> getIconUrls() { - ensureInitialized(); - return mIconUrls; - } - - @Override - public List<String> getIds() { - ensureInitialized(); - return mIds; - } - - @Override - public List<String> getTitles() { - ensureInitialized(); - return mTitles; - } - } - - /** - * A "choices" is a one-out-of-many-choices action, displayed as a sub-menu with one or more - * items, with either zero or more of them being checked. - * <p/> - * Implementation detail: empty choices will not be displayed in the context menu. - * <p/> - * Choice items are sorted by their id, using String's natural sorting order. - * <p/> - * Two multiple choices are equal if they have the same id, title, group-id and choices. - * It is expected for the current state and action callback to be different. - */ - public static class Choices extends Action { - - /** - * Special value which will insert a separator in the choices' submenu. - */ - public final static String SEPARATOR = "----"; - - /** - * Character used to split multiple checked choices, see {@link #getCurrent()}. - * The pipe character "|" is used, to natively match Android resource flag separators. - */ - public final static String CHOICE_SEP = "|"; - - /** - * A non-null map of id=>choice-title. The map could be empty but not null. - */ - private final Map<String, String> mChoices; - /** - * 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. - * If more than one choice, they must be separated by {@link #CHOICE_SEP}. - */ - private final String mCurrent; - - /** - * Creates a new immutable multiple-choice action. - * This action has no group-id and will show up in the root of the context menu. - * - * @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 choices A map id=>title for all the multiple-choice items. Cannot 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. - */ - public Choices(String id, String title, - Map<String, String> choices, - String current, - IMenuCallback callback) { - this(id, title, choices, current, null /* group-id */, callback); - } - - /** - * Creates a new immutable multiple-choice 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 choices A map id=>title for all the multiple-choice items. Cannot 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 groupId The optional group id, to place the action in a given sub-menu. - * Can be null. - * @param callback A callback to execute when the context menu item is - * selected. - */ - public Choices(String id, String title, - Map<String, String> choices, - String current, - String groupId, - IMenuCallback callback) { - super(id, title, groupId, callback); - mChoices = choices; - mCurrent = current; - } - - /** - * Return the map of id=>choice-title. The map could be empty but not null. - */ - public Map<String, String> getChoices() { - return mChoices; - } - - /** - * Returns the id(s) of the current choice(s) that are 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}. - */ - public String getCurrent() { - return mCurrent; - } - - /** - * Two multiple choices are equal if they have the same id, title, group-id and choices. - * It is acceptable for the current state and action callback to be different. - */ - @Override - public boolean equals(Object obj) { - if (obj instanceof Choices && super.equals(obj)) { - Choices rhs = (Choices) obj; - return mChoices.equals(rhs.mChoices); - } - return false; - } - - /** - * Two multiple choices have the same hash code if they have the same id, title, - * group-id and choices. - */ - @Override - public int hashCode() { - int h = super.hashCode(); - - if (mChoices != null) { - h = h ^ mChoices.hashCode(); - } - return h; - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/RuleAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/RuleAction.java new file mode 100755 index 0000000..2ebab36 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/RuleAction.java @@ -0,0 +1,656 @@ +/* + * 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.util.Pair; + +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> + */ +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 final static String CHOICE_SEP = "|"; //$NON-NLS-1$ + + /** + * Same as {@link #CHOICE_SEP} but safe for use in regular expressions. + */ + public final static 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 final static 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 + */ + 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} + */ + public static RuleAction createAction(String id, String title, + IMenuCallback callback, 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} + */ + public static Toggle createToggle(String id, String title, boolean isChecked, + IMenuCallback callback, 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} + */ + public static NestedAction createChoices(String id, String title, + IMenuCallback callback, URL iconUrl, + int sortPriority, boolean supportsMultipleNodes, 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} + */ + public static Choices createChoices(String id, String title, + IMenuCallback callback, List<String> titles, List<URL> iconUrls, List<String> ids, + String current, 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} + */ + public static Choices createChoices(String id, String title, + IMenuCallback callback, List<URL> iconUrls, + String current, URL iconUrl, int sortPriority, + boolean supportsMultipleNodes, 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} + */ + public static Choices createChoices(String id, String title, + IMenuCallback callback, ChoiceProvider provider, + String current, 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(String id, String title, 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 + */ + 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 + */ + 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 + */ + 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 + */ + public IMenuCallback getCallback() { + return mCallback; + } + + // Implements Comparable<MenuAciton> + public int compareTo(RuleAction other) { + if (mSortPriority != other.mSortPriority) { + return mSortPriority - other.mSortPriority; + } + + return mTitle.compareTo(other.mTitle); + } + + @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", "", null, 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(String id, String title, boolean isChecked, + 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(String id, String title, IMenuCallback callback, + List<String> titles, List<URL> iconUrls, List<String> ids, 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 + */ + 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 + */ + 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 + */ + public List<String> getTitles() { + return mTitles; + } + + /** + * Returns the current value of the choice + * + * @return the current value of the choice, possibly null + */ + 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(String id, String title, ActionProvider provider, + 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 + */ + public List<RuleAction> getNestedActions(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 DelayedChoices(String id, String title, + IMenuCallback callback, String current, ChoiceProvider provider, + int sortPriority, boolean supportsMultipleNodes) { + super(id, title, callback, null, null, null, current, sortPriority, + supportsMultipleNodes); + mProvider = provider; + } + + private void ensureInitialized() { + if (mTitles == null) { + mTitles = new ArrayList<String>(); + mIconUrls = new ArrayList<URL>(); + mIds = new ArrayList<String>(); + + mProvider.addChoices(mTitles, mIconUrls, mIds); + } + } + + @Override + public List<URL> getIconUrls() { + ensureInitialized(); + return mIconUrls; + } + + @Override + public List<String> getIds() { + ensureInitialized(); + return mIds; + } + + @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 + */ + public List<RuleAction> getNestedActions(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 + */ + public void addChoices(List<String> titles, List<URL> iconUrls, List<String> ids); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java index 1a99385..5191d25 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java @@ -66,8 +66,9 @@ import com.android.ide.common.api.IGraphics; import com.android.ide.common.api.IMenuCallback; import com.android.ide.common.api.INode; import com.android.ide.common.api.INodeHandler; -import com.android.ide.common.api.MenuAction; -import com.android.ide.common.api.MenuAction.ChoiceProvider; +import com.android.ide.common.api.IViewRule; +import com.android.ide.common.api.RuleAction; +import com.android.ide.common.api.RuleAction.ChoiceProvider; import com.android.ide.common.api.Point; import com.android.ide.common.api.Rect; import com.android.ide.common.api.Segment; @@ -85,6 +86,9 @@ import java.util.List; import java.util.Map; import java.util.Set; +/** + * A {@link IViewRule} for all layouts. + */ public class BaseLayoutRule extends BaseViewRule { private static final String ACTION_FILL_WIDTH = "_fillW"; //$NON-NLS-1$ private static final String ACTION_FILL_HEIGHT = "_fillH"; //$NON-NLS-1$ @@ -102,7 +106,7 @@ public class BaseLayoutRule extends BaseViewRule { // The Margin layout parameters are available for LinearLayout, FrameLayout, RelativeLayout, // and their subclasses. - protected final MenuAction createMarginAction(final INode parentNode, + protected final RuleAction createMarginAction(final INode parentNode, final List<? extends INode> children) { final List<? extends INode> targets = children == null || children.size() == 0 ? @@ -111,7 +115,8 @@ public class BaseLayoutRule extends BaseViewRule { final INode first = targets.get(0); IMenuCallback actionCallback = new IMenuCallback() { - public void action(MenuAction action, final String valueId, final Boolean newValue) { + public void action(RuleAction action, List<? extends INode> selectedNodes, + final String valueId, final Boolean newValue) { parentNode.editXml("Change Margins", new INodeHandler() { public void handle(INode n) { String uri = ANDROID_URI; @@ -137,13 +142,13 @@ public class BaseLayoutRule extends BaseViewRule { } }; - return MenuAction.createAction(ACTION_MARGIN, "Change Margins...", null, actionCallback, - ICON_MARGINS, 40); + return RuleAction.createAction(ACTION_MARGIN, "Change Margins...", actionCallback, + ICON_MARGINS, 40, false); } // Both LinearLayout and RelativeLayout have a gravity (but RelativeLayout applies it // to the parent whereas for LinearLayout it's on the children) - protected final MenuAction createGravityAction(final List<? extends INode> targets, final + protected final RuleAction createGravityAction(final List<? extends INode> targets, final String attributeName) { if (targets != null && targets.size() > 0) { final INode first = targets.get(0); @@ -162,20 +167,19 @@ public class BaseLayoutRule extends BaseViewRule { } }; - return MenuAction.createChoices("_gravity", "Change Gravity", //$NON-NLS-1$ - null, + return RuleAction.createChoices("_gravity", "Change Gravity", //$NON-NLS-1$ new PropertyCallback(targets, "Change Gravity", ANDROID_URI, attributeName), provider, first.getStringAttr(ANDROID_URI, attributeName), ICON_GRAVITY, - 43); + 43, false); } return null; } @Override - public void addLayoutActions(List<MenuAction> actions, final INode parentNode, + public void addLayoutActions(List<RuleAction> actions, final INode parentNode, final List<? extends INode> children) { super.addLayoutActions(actions, parentNode, children); @@ -186,7 +190,8 @@ public class BaseLayoutRule extends BaseViewRule { // Shared action callback IMenuCallback actionCallback = new IMenuCallback() { - public void action(MenuAction action, final String valueId, final Boolean newValue) { + public void action(RuleAction action, List<? extends INode> selectedNodes, + final String valueId, final Boolean newValue) { final String actionId = action.getId(); final String undoLabel; if (actionId.equals(ACTION_FILL_WIDTH)) { @@ -218,10 +223,10 @@ public class BaseLayoutRule extends BaseViewRule { } }; - actions.add(MenuAction.createToggle(ACTION_FILL_WIDTH, "Toggle Fill Width", - isFilled(first, ATTR_LAYOUT_WIDTH), actionCallback, ICON_FILL_WIDTH, 10)); - actions.add(MenuAction.createToggle(ACTION_FILL_HEIGHT, "Toggle Fill Height", - isFilled(first, ATTR_LAYOUT_HEIGHT), actionCallback, ICON_FILL_HEIGHT, 20)); + actions.add(RuleAction.createToggle(ACTION_FILL_WIDTH, "Toggle Fill Width", + isFilled(first, ATTR_LAYOUT_WIDTH), actionCallback, ICON_FILL_WIDTH, 10, false)); + actions.add(RuleAction.createToggle(ACTION_FILL_HEIGHT, "Toggle Fill Height", + isFilled(first, ATTR_LAYOUT_HEIGHT), actionCallback, ICON_FILL_HEIGHT, 20, false)); } // ==== Paste support ==== @@ -256,6 +261,10 @@ public class BaseLayoutRule extends BaseViewRule { * This method is invoked by BaseView when onPaste() is called -- * views don't generally accept children and instead use the target node as * a hint to paste "before" it. + * + * @param parentNode the parent node we're pasting into + * @param targetNode the first selected node + * @param elements the elements being pasted */ public void onPasteBeforeChild(INode parentNode, INode targetNode, IDragElement[] elements) { @@ -737,6 +746,12 @@ public class BaseLayoutRule extends BaseViewRule { } } + /** + * Returns the maximum number of pixels will be considered a "match" when snapping + * resize or move positions to edges or other constraints + * + * @return the maximum number of pixels to consider for snapping + */ public static final int getMaxMatchDistance() { // TODO - make constant once we're happy with the feel return 20; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java index 625ae34..66688d9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java @@ -35,22 +35,29 @@ import com.android.ide.common.api.IDragElement; import com.android.ide.common.api.IGraphics; import com.android.ide.common.api.IMenuCallback; import com.android.ide.common.api.INode; -import com.android.ide.common.api.INodeHandler; import com.android.ide.common.api.IValidator; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; -import com.android.ide.common.api.MenuAction; import com.android.ide.common.api.Point; import com.android.ide.common.api.Rect; +import com.android.ide.common.api.RuleAction; +import com.android.ide.common.api.RuleAction.ActionProvider; +import com.android.ide.common.api.RuleAction.ChoiceProvider; +import com.android.ide.common.api.RuleAction.Choices; import com.android.ide.common.api.SegmentType; +import com.android.util.Pair; +import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; /** @@ -58,16 +65,15 @@ import java.util.Set; */ public class BaseViewRule implements IViewRule { // Strings used as internal ids, group ids and prefixes for actions - private static final String FALSE_ID = "2f"; //$NON-NLS-1$ - private static final String TRUE_ID = "1t"; //$NON-NLS-1$ + private static final String FALSE_ID = "false"; //$NON-NLS-1$ + private static final String TRUE_ID = "true"; //$NON-NLS-1$ private static final String PROP_PREFIX = "@prop@"; //$NON-NLS-1$ - private static final String SEPARATOR_ID = "~1sep"; //$NON-NLS-1$ - private static final String DEFAULT_ID = "~2clr"; //$NON-NLS-1$ + private static final String CLEAR_ID = "clear"; //$NON-NLS-1$ private static final String PROPERTIES_ID = "properties"; //$NON-NLS-1$ private static final String EDIT_TEXT_ID = "edittext"; //$NON-NLS-1$ private static final String EDIT_ID_ID = "editid"; //$NON-NLS-1$ - private static final String WIDTH_ID = "layout_1width"; //$NON-NLS-1$ - private static final String HEIGHT_ID = "layout_2height"; //$NON-NLS-1$ + private static final String WIDTH_ID = "layout_width"; //$NON-NLS-1$ + private static final String HEIGHT_ID = "layout_height"; //$NON-NLS-1$ private static final String ZCUSTOM = "zcustom"; //$NON-NLS-1$ protected IClientRulesEngine mRulesEngine; @@ -115,108 +121,114 @@ public class BaseViewRule implements IViewRule { * - Explicit layout_width and layout_height attributes. * - List of all other simple toggle attributes. */ - public List<MenuAction> getContextMenu(final INode selectedNode) { - // Compute the key for mAttributesMap. This depends on the type of this - // node and its parent in the view hierarchy. - StringBuilder keySb = new StringBuilder(); - keySb.append(selectedNode.getFqcn()); - keySb.append('_'); - INode parent = selectedNode.getParent(); - if (parent != null) { - keySb.append(parent.getFqcn()); - } - final String key = keySb.toString(); - - String custom_w = null; - String curr_w = selectedNode.getStringAttr(ANDROID_URI, ATTR_LAYOUT_WIDTH); + public void addContextMenuActions(List<RuleAction> actions, final INode selectedNode) { + String width = null; + String currentWidth = selectedNode.getStringAttr(ANDROID_URI, ATTR_LAYOUT_WIDTH); String fillParent = getFillParentValueName(); boolean canMatchParent = supportsMatchParent(); - if (canMatchParent && VALUE_FILL_PARENT.equals(curr_w)) { - curr_w = VALUE_MATCH_PARENT; - } else if (!canMatchParent && VALUE_MATCH_PARENT.equals(curr_w)) { - curr_w = VALUE_FILL_PARENT; - } else if (!VALUE_WRAP_CONTENT.equals(curr_w) && !fillParent.equals(curr_w)) { - custom_w = curr_w; + if (canMatchParent && VALUE_FILL_PARENT.equals(currentWidth)) { + currentWidth = VALUE_MATCH_PARENT; + } else if (!canMatchParent && VALUE_MATCH_PARENT.equals(currentWidth)) { + currentWidth = VALUE_FILL_PARENT; + } else if (!VALUE_WRAP_CONTENT.equals(currentWidth) && !fillParent.equals(currentWidth)) { + width = currentWidth; } - String custom_h = null; - String curr_h = selectedNode.getStringAttr(ANDROID_URI, ATTR_LAYOUT_HEIGHT); + String height = null; + String currentHeight = selectedNode.getStringAttr(ANDROID_URI, ATTR_LAYOUT_HEIGHT); - if (canMatchParent && VALUE_FILL_PARENT.equals(curr_h)) { - curr_h = VALUE_MATCH_PARENT; - } else if (!canMatchParent && VALUE_MATCH_PARENT.equals(curr_h)) { - curr_h = VALUE_FILL_PARENT; - } else if (!VALUE_WRAP_CONTENT.equals(curr_h) && !fillParent.equals(curr_h)) { - custom_h = curr_h; + if (canMatchParent && VALUE_FILL_PARENT.equals(currentHeight)) { + currentHeight = VALUE_MATCH_PARENT; + } else if (!canMatchParent && VALUE_MATCH_PARENT.equals(currentHeight)) { + currentHeight = VALUE_FILL_PARENT; + } else if (!VALUE_WRAP_CONTENT.equals(currentHeight) + && !fillParent.equals(currentHeight)) { + height = currentHeight; } - final String customWidth = custom_w; - final String customHeight = custom_h; - - IMenuCallback onChange = new IMenuCallback() { + final String newWidth = width; + final String newHeight = height; + final IMenuCallback onChange = new IMenuCallback() { public void action( - final MenuAction action, - final String valueId, - final Boolean newValue) { + final RuleAction action, + final List<? extends INode> selectedNodes, + final String valueId, final Boolean newValue) { String fullActionId = action.getId(); boolean isProp = fullActionId.startsWith(PROP_PREFIX); final String actionId = isProp ? fullActionId.substring(PROP_PREFIX.length()) : fullActionId; - final INode node = selectedNode; if (fullActionId.equals(WIDTH_ID)) { - final String newAttrValue = getValue(valueId, customWidth); + final String newAttrValue = getValue(valueId, newWidth); if (newAttrValue != null) { - node.editXml("Change Attribute " + ATTR_LAYOUT_WIDTH, - new PropertySettingNodeHandler(ANDROID_URI, - ATTR_LAYOUT_WIDTH, newAttrValue)); + for (INode node : selectedNodes) { + node.editXml("Change Attribute " + ATTR_LAYOUT_WIDTH, + new PropertySettingNodeHandler(ANDROID_URI, + ATTR_LAYOUT_WIDTH, newAttrValue)); + } } return; } else if (fullActionId.equals(HEIGHT_ID)) { // Ask the user - final String newAttrValue = getValue(valueId, customHeight); + final String newAttrValue = getValue(valueId, newHeight); if (newAttrValue != null) { - node.editXml("Change Attribute " + ATTR_LAYOUT_HEIGHT, - new PropertySettingNodeHandler(ANDROID_URI, - ATTR_LAYOUT_HEIGHT, newAttrValue)); + for (INode node : selectedNodes) { + node.editXml("Change Attribute " + ATTR_LAYOUT_HEIGHT, + new PropertySettingNodeHandler(ANDROID_URI, + ATTR_LAYOUT_HEIGHT, newAttrValue)); + } } return; } else if (fullActionId.equals(EDIT_ID_ID)) { - // Strip off the @id prefix stuff - String oldId = node.getStringAttr(ANDROID_URI, ATTR_ID); - oldId = stripIdPrefix(ensureValidString(oldId)); - IValidator validator = mRulesEngine.getResourceValidator(); - String newId = mRulesEngine.displayInput("New Id:", oldId, validator); - if (newId != null && newId.trim().length() > 0) { - if (!newId.startsWith(NEW_ID_PREFIX)) { - newId = NEW_ID_PREFIX + stripIdPrefix(newId); + // Ids must be set individually so open the id dialog for each + // selected node (though allow cancel to break the loop) + for (INode node : selectedNodes) { + // Strip off the @id prefix stuff + String oldId = node.getStringAttr(ANDROID_URI, ATTR_ID); + oldId = stripIdPrefix(ensureValidString(oldId)); + IValidator validator = mRulesEngine.getResourceValidator(); + String newId = mRulesEngine.displayInput("New Id:", oldId, validator); + if (newId != null && newId.trim().length() > 0) { + if (!newId.startsWith(NEW_ID_PREFIX)) { + newId = NEW_ID_PREFIX + stripIdPrefix(newId); + } + node.editXml("Change ID", new PropertySettingNodeHandler(ANDROID_URI, + ATTR_ID, newId)); + } else if (newId == null) { + // Cancelled + break; } - node.editXml("Change ID", new PropertySettingNodeHandler(ANDROID_URI, - ATTR_ID, newId)); - } - } else if (fullActionId.equals(EDIT_TEXT_ID)) { - String oldText = node.getStringAttr(ANDROID_URI, ATTR_TEXT); - oldText = ensureValidString(oldText); - String newText = mRulesEngine.displayResourceInput("string", oldText); //$NON-NLS-1$ - if (newText != null) { - node.editXml("Change Text", new PropertySettingNodeHandler(ANDROID_URI, - ATTR_TEXT, newText.length() > 0 ? newText : null)); } - } - - if (isProp) { - Map<String, Prop> props = mAttributesMap.get(key); - final Prop prop = (props != null) ? props.get(actionId) : null; - - if (prop != null) { - // For custom values (requiring an input dialog) input the - // value outside the undo-block - final String customValue = prop.isStringEdit() - ? inputAttributeValue(node, actionId) : null; - - node.editXml("Change Attribute " + actionId, new INodeHandler() { - public void handle(INode n) { + return; + } else { + INode firstNode = selectedNodes.get(0); + if (fullActionId.equals(EDIT_TEXT_ID)) { + String oldText = selectedNodes.size() == 1 + ? firstNode.getStringAttr(ANDROID_URI, ATTR_TEXT) + : ""; //$NON-NLS-1$ + oldText = ensureValidString(oldText); + String newText = mRulesEngine.displayResourceInput("string", oldText); //$NON-NLS-1$ + if (newText != null) { + for (INode node : selectedNodes) { + node.editXml("Change Text", + new PropertySettingNodeHandler(ANDROID_URI, + ATTR_TEXT, newText.length() > 0 ? newText : null)); + } + } + return; + } else if (isProp) { + String key = getPropertyMapKey(selectedNode); + Map<String, Prop> props = mAttributesMap.get(key); + final Prop prop = (props != null) ? props.get(actionId) : null; + + if (prop != null) { + // For custom values (requiring an input dialog) input the + // value outside the undo-block + final String customValue = prop.isStringEdit() + ? inputAttributeValue(firstNode, actionId) : null; + + for (INode n : selectedNodes) { if (prop.isToggle()) { // case of toggle String value = ""; //$NON-NLS-1$ @@ -229,7 +241,7 @@ public class BaseViewRule implements IViewRule { } else if (prop.isFlag()) { // case of a flag String values = ""; //$NON-NLS-1$ - if (!valueId.equals(DEFAULT_ID)) { + if (!valueId.equals(CLEAR_ID)) { values = n.getStringAttr(ANDROID_URI, actionId); Set<String> newValues = new HashSet<String>(); if (values != null) { @@ -247,7 +259,7 @@ public class BaseViewRule implements IViewRule { } else if (prop.isEnum()) { // case of an enum String value = ""; //$NON-NLS-1$ - if (!valueId.equals(DEFAULT_ID)) { + if (!valueId.equals(CLEAR_ID)) { value = newValue ? valueId : ""; //$NON-NLS-1$ } n.setAttribute(ANDROID_URI, actionId, value); @@ -259,7 +271,7 @@ public class BaseViewRule implements IViewRule { } } } - }); + } } } } @@ -318,40 +330,91 @@ public class BaseViewRule implements IViewRule { } }; - MenuAction.Action editText = null; IAttributeInfo textAttribute = selectedNode.getAttributeInfo(ANDROID_URI, ATTR_TEXT); if (textAttribute != null) { - editText = new MenuAction.Action(EDIT_TEXT_ID, "Edit Text...", null, onChange); + actions.add(RuleAction.createAction(EDIT_TEXT_ID, "Edit Text...", onChange, + null, 10, true)); } - List<MenuAction> list1 = Arrays.asList(new MenuAction[] { - editText, // could be null - will be ignored by menu creation code - new MenuAction.Action(EDIT_ID_ID, "Edit ID...", null, onChange), - - new MenuAction.Choices(WIDTH_ID, "Layout Width", - mapify( - VALUE_WRAP_CONTENT, "Wrap Content", - canMatchParent ? VALUE_MATCH_PARENT : VALUE_FILL_PARENT, - canMatchParent ? "Match Parent" : "Fill Parent", - custom_w, custom_w, - ZCUSTOM, "Other..." - ), - curr_w, - onChange ), - new MenuAction.Choices(HEIGHT_ID, "Layout Height", - mapify( - VALUE_WRAP_CONTENT, "Wrap Content", - canMatchParent ? VALUE_MATCH_PARENT : VALUE_FILL_PARENT, - canMatchParent ? "Match Parent" : "Fill Parent", - custom_h, custom_h, - ZCUSTOM, "Other..." - ), - curr_h, - onChange ), - new MenuAction.Group(PROPERTIES_ID, "Properties") + actions.add(RuleAction.createAction(EDIT_ID_ID, "Edit ID...", onChange, null, 20, true)); + + // Create width choice submenu + List<Pair<String, String>> widthChoices = new ArrayList<Pair<String,String>>(4); + widthChoices.add(Pair.of(VALUE_WRAP_CONTENT, "Wrap Content")); + if (canMatchParent) { + widthChoices.add(Pair.of(VALUE_MATCH_PARENT, "Match Parent")); + } else { + widthChoices.add(Pair.of(VALUE_FILL_PARENT, "Fill Parent")); + } + if (width != null) { + widthChoices.add(Pair.of(width, width)); + } + widthChoices.add(Pair.of(ZCUSTOM, "Other...")); + actions.add(RuleAction.createChoices( + WIDTH_ID, "Layout Width", + onChange, + null /* iconUrls */, + currentWidth, + null, 30, + true, // supportsMultipleNodes + widthChoices)); + + // Create height choice submenu + List<Pair<String, String>> heightChoices = new ArrayList<Pair<String,String>>(4); + heightChoices.add(Pair.of(VALUE_WRAP_CONTENT, "Wrap Content")); + if (canMatchParent) { + heightChoices.add(Pair.of(VALUE_MATCH_PARENT, "Match Parent")); + } else { + heightChoices.add(Pair.of(VALUE_FILL_PARENT, "Fill Parent")); + } + if (height != null) { + heightChoices.add(Pair.of(height, height)); + } + heightChoices.add(Pair.of(ZCUSTOM, "Other...")); + actions.add(RuleAction.createChoices( + HEIGHT_ID, "Layout Height", + onChange, + null /* iconUrls */, + currentHeight, + null, 40, + true, + heightChoices)); + + actions.add(RuleAction.createSeparator(45)); + RuleAction properties = RuleAction.createChoices(PROPERTIES_ID, "Properties", + onChange /*callback*/, null /*icon*/, 50, + true /*supportsMultipleNodes*/, new ActionProvider() { + public List<RuleAction> getNestedActions(INode node) { + List<RuleAction> propertyActions = createPropertyActions(node, + getPropertyMapKey(node), onChange); + + return propertyActions; + } }); - // Prepare a list of all simple properties. + actions.add(properties); + } + + private static String getPropertyMapKey(INode node) { + // Compute the key for mAttributesMap. This depends on the type of this + // node and its parent in the view hierarchy. + StringBuilder sb = new StringBuilder(); + sb.append(node.getFqcn()); + sb.append('_'); + INode parent = node.getParent(); + if (parent != null) { + sb.append(parent.getFqcn()); + } + return sb.toString(); + } + + /** + * Creates a list of nested actions representing the property-setting + * actions for the given selected node + */ + private List<RuleAction> createPropertyActions(final INode selectedNode, final String key, + final IMenuCallback onChange) { + List<RuleAction> propertyActions = new ArrayList<RuleAction>(); Map<String, Prop> props = mAttributesMap.get(key); if (props == null) { @@ -399,12 +462,10 @@ public class BaseViewRule implements IViewRule { mAttributesMap.put(key, props); } - List<MenuAction> list2 = new ArrayList<MenuAction>(); - + int nextPriority = 10; for (Map.Entry<String, Prop> entry : props.entrySet()) { String id = entry.getKey(); Prop p = entry.getValue(); - MenuAction a = null; if (p.isToggle()) { // Toggles are handled as a multiple-choice between true, false // and nothing (clear) @@ -416,51 +477,91 @@ public class BaseViewRule implements IViewRule { } else if ("false".equals(value)) { //$NON-NLS-1$ value = FALSE_ID; } else { - value = "4clr"; //$NON-NLS-1$ + value = CLEAR_ID; } - - a = new MenuAction.Choices( - PROP_PREFIX + id, - p.getTitle(), - mapify( - TRUE_ID, "True", - FALSE_ID, "False", - "3sep", MenuAction.Choices.SEPARATOR, //$NON-NLS-1$ - "4clr", "Default"), //$NON-NLS-1$ - value, - PROPERTIES_ID, - onChange); + Choices action = RuleAction.createChoices(PROP_PREFIX + id, p.getTitle(), + onChange, BOOLEAN_CHOICE_PROVIDER, + value, + null, nextPriority++, + true); + propertyActions.add(action); } else if (p.getChoices() != null) { // Enum or flags. Their possible values are the multiple-choice // items, with an extra "clear" option to remove everything. String current = selectedNode.getStringAttr(ANDROID_URI, id); if (current == null || current.length() == 0) { - current = DEFAULT_ID; + current = CLEAR_ID; } - a = new MenuAction.Choices( - PROP_PREFIX + id, - p.getTitle(), - concatenate( - p.getChoices(), - mapify( - SEPARATOR_ID, MenuAction.Choices.SEPARATOR, - DEFAULT_ID, "Default" - ) - ), - current, - PROPERTIES_ID, - onChange); + Choices action = RuleAction.createChoices(PROP_PREFIX + id, p.getTitle(), + onChange, new EnumPropertyChoiceProvider(p), + current, + null, nextPriority++, + true); + propertyActions.add(action); } else { - a = new MenuAction.Action( + RuleAction action = RuleAction.createAction( PROP_PREFIX + id, p.getTitle(), - PROPERTIES_ID, - onChange); + onChange, + null, nextPriority++, + true); + propertyActions.add(action); + } + } + + // The properties are coming out of map key order which isn't right + Collections.sort(propertyActions, new Comparator<RuleAction>() { + public int compare(RuleAction action1, RuleAction action2) { + return action1.getTitle().compareTo(action2.getTitle()); } - list2.add(a); + }); + return propertyActions; + } + + /** + * A {@link ChoiceProvder} which provides alternatives suitable for choosing + * values for a boolean property: true, false, or "default". + */ + private static ChoiceProvider BOOLEAN_CHOICE_PROVIDER = new ChoiceProvider() { + public void addChoices(List<String> titles, List<URL> iconUrls, List<String> ids) { + titles.add("True"); + ids.add(TRUE_ID); + + titles.add("False"); + ids.add(FALSE_ID); + + titles.add(RuleAction.SEPARATOR); + ids.add(RuleAction.SEPARATOR); + + titles.add("Default"); + ids.add(CLEAR_ID); + } + }; + + /** + * A {@link ChoiceProvider} which provides the various available + * attribute values available for a given {@link Prop} property descriptor. + */ + private static class EnumPropertyChoiceProvider implements ChoiceProvider { + private Prop mProperty; + + public EnumPropertyChoiceProvider(Prop property) { + super(); + this.mProperty = property; } - return concatenate(list1, list2); + public void addChoices(List<String> titles, List<URL> iconUrls, List<String> ids) { + for (Entry<String, String> entry : mProperty.getChoices().entrySet()) { + ids.add(entry.getKey()); + titles.add(entry.getValue()); + } + + titles.add(RuleAction.SEPARATOR); + ids.add(RuleAction.SEPARATOR); + + titles.add("Default"); + ids.add(CLEAR_ID); + } } /** @@ -504,21 +605,6 @@ public class BaseViewRule implements IViewRule { return sb.toString(); } - // Concatenate two menu action lists. Move these utilities into MenuAction - static List<MenuAction> concatenate(List<MenuAction> pre, List<MenuAction> post) { - List<MenuAction> result = new ArrayList<MenuAction>(pre.size() + post.size()); - result.addAll(pre); - result.addAll(post); - return result; - } - - static List<MenuAction> concatenate(List<MenuAction> pre, MenuAction post) { - List<MenuAction> result = new ArrayList<MenuAction>(pre.size() + 1); - result.addAll(pre); - result.add(post); - return result; - } - static Map<String, String> concatenate(Map<String, String> pre, Map<String, String> post) { Map<String, String> result = new HashMap<String, String>(pre.size() + post.size()); result.putAll(pre); @@ -555,7 +641,7 @@ public class BaseViewRule implements IViewRule { return null; } - public void addLayoutActions(List<MenuAction> actions, INode parentNode, + public void addLayoutActions(List<RuleAction> actions, INode parentNode, List<? extends INode> children) { } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java index dfe9d6f..e26df79 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java @@ -23,7 +23,7 @@ import com.android.ide.common.api.INode; import com.android.ide.common.api.INodeHandler; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; -import com.android.ide.common.api.MenuAction; +import com.android.ide.common.api.RuleAction; import java.util.List; @@ -51,12 +51,15 @@ public class EditTextRule extends BaseViewRule { * Adds a "Request Focus" menu item. */ @Override - public List<MenuAction> getContextMenu(final INode selectedNode) { + public void addContextMenuActions(List<RuleAction> actions, final INode selectedNode) { + super.addContextMenuActions(actions, selectedNode); + final boolean hasFocus = hasFocus(selectedNode); final String label = hasFocus ? "Clear Focus" : "Request Focus"; IMenuCallback onChange = new IMenuCallback() { - public void action(MenuAction menuAction, String valueId, Boolean newValue) { + public void action(RuleAction menuAction, List<? extends INode> selectedNodes, + String valueId, Boolean newValue) { selectedNode.editXml(label, new INodeHandler() { public void handle(INode node) { INode focus = findFocus(findRoot(node)); @@ -71,8 +74,8 @@ public class EditTextRule extends BaseViewRule { } }; - return concatenate(super.getContextMenu(selectedNode), - new MenuAction.Action("_setfocus", label, null, onChange)); //$NON-NLS-1$ + actions.add(RuleAction.createAction("_setfocus", label, onChange, //$NON-NLS-1$ + null, 5, false /*supportsMultipleNodes*/)); } /** Returns true if the given node currently has focus */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FrameLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FrameLayoutRule.java index b8c7408..884034f 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FrameLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FrameLayoutRule.java @@ -31,7 +31,7 @@ import com.android.ide.common.api.INodeHandler; import com.android.ide.common.api.IViewMetadata; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; -import com.android.ide.common.api.MenuAction; +import com.android.ide.common.api.RuleAction; import com.android.ide.common.api.Point; import com.android.ide.common.api.Rect; import com.android.ide.common.api.IViewMetadata.FillPreference; @@ -156,10 +156,10 @@ public class FrameLayoutRule extends BaseLayoutRule { } @Override - public void addLayoutActions(List<MenuAction> actions, final INode parentNode, + public void addLayoutActions(List<RuleAction> actions, final INode parentNode, final List<? extends INode> children) { super.addLayoutActions(actions, parentNode, children); - actions.add(MenuAction.createSeparator(25)); + actions.add(RuleAction.createSeparator(25)); actions.add(createMarginAction(parentNode, children)); if (children != null && children.size() > 0) { actions.add(createGravityAction(children, ATTR_LAYOUT_GRAVITY)); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java index 2e28713..47ea3d9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java @@ -31,8 +31,8 @@ import com.android.ide.common.api.IMenuCallback; import com.android.ide.common.api.INode; import com.android.ide.common.api.INodeHandler; import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.MenuAction; -import com.android.ide.common.api.MenuAction.OrderedChoices; +import com.android.ide.common.api.RuleAction; +import com.android.ide.common.api.RuleAction.Choices; import com.android.ide.common.api.Point; import com.android.ide.common.api.Rect; import com.android.ide.common.api.SegmentType; @@ -130,32 +130,32 @@ public class GridLayoutRule extends BaseLayoutRule { } @Override - public void addLayoutActions(List<MenuAction> actions, final INode parentNode, + public void addLayoutActions(List<RuleAction> actions, final INode parentNode, final List<? extends INode> children) { super.addLayoutActions(actions, parentNode, children); - OrderedChoices orientationAction = MenuAction.createChoices( + Choices orientationAction = RuleAction.createChoices( ACTION_ORIENTATION, "Orientation", //$NON-NLS-1$ - null, new PropertyCallback(Collections.singletonList(parentNode), + new PropertyCallback(Collections.singletonList(parentNode), "Change LinearLayout Orientation", ANDROID_URI, ATTR_ORIENTATION), Arrays .<String> asList("Set Horizontal Orientation", "Set Vertical Orientation"), Arrays.<URL> asList(ICON_HORIZONTAL, ICON_VERTICAL), Arrays.<String> asList( "horizontal", "vertical"), getCurrentOrientation(parentNode), - null /* icon */, -10); + null /* icon */, -10, false); orientationAction.setRadio(true); actions.add(orientationAction); // Gravity and margins if (children != null && children.size() > 0) { - actions.add(MenuAction.createSeparator(35)); + actions.add(RuleAction.createSeparator(35)); actions.add(createMarginAction(parentNode, children)); actions.add(createGravityAction(children, ATTR_LAYOUT_GRAVITY)); } IMenuCallback actionCallback = new IMenuCallback() { - public void action(final MenuAction action, final String valueId, - final Boolean newValue) { + public void action(final RuleAction action, List<? extends INode> selectedNodes, + final String valueId, final Boolean newValue) { parentNode.editXml("Add/Remove Row/Column", new INodeHandler() { public void handle(INode n) { String id = action.getId(); @@ -194,33 +194,33 @@ public class GridLayoutRule extends BaseLayoutRule { }; // Add Row and Add Column - actions.add(MenuAction.createSeparator(150)); - actions.add(MenuAction.createAction(ACTION_ADD_COL, "Add Column", null, actionCallback, - ICON_ADD_COL, 160)); - actions.add(MenuAction.createAction(ACTION_ADD_ROW, "Add Row", null, actionCallback, - ICON_ADD_ROW, 165)); + actions.add(RuleAction.createSeparator(150)); + actions.add(RuleAction.createAction(ACTION_ADD_COL, "Add Column", actionCallback, + ICON_ADD_COL, 160, false /* supportsMultipleNodes */)); + actions.add(RuleAction.createAction(ACTION_ADD_ROW, "Add Row", actionCallback, + ICON_ADD_ROW, 165, false)); // Remove Row and Remove Column (if something is selected) if (children != null && children.size() > 0) { // TODO: Add "Merge Columns" and "Merge Rows" ? - actions.add(MenuAction.createAction(ACTION_REMOVE_COL, "Remove Column", null, - actionCallback, ICON_REMOVE_COL, 170)); - actions.add(MenuAction.createAction(ACTION_REMOVE_ROW, "Remove Row", null, - actionCallback, ICON_REMOVE_ROW, 175)); + actions.add(RuleAction.createAction(ACTION_REMOVE_COL, "Remove Column", + actionCallback, ICON_REMOVE_COL, 170, false)); + actions.add(RuleAction.createAction(ACTION_REMOVE_ROW, "Remove Row", + actionCallback, ICON_REMOVE_ROW, 175, false)); } - actions.add(MenuAction.createSeparator(185)); + actions.add(RuleAction.createSeparator(185)); - actions.add(MenuAction.createToggle(ACTION_SNAP, "Snap to Grid", - sSnapToGrid, actionCallback, ICON_SNAP, 190)); + actions.add(RuleAction.createToggle(ACTION_SNAP, "Snap to Grid", + sSnapToGrid, actionCallback, ICON_SNAP, 190, false)); - actions.add(MenuAction.createToggle(ACTION_SHOW_GRID, "Show Structure", - sShowStructure, actionCallback, ICON_SHOW_GRID, 200)); + actions.add(RuleAction.createToggle(ACTION_SHOW_GRID, "Show Structure", + sShowStructure, actionCallback, ICON_SHOW_GRID, 200, false)); // Temporary: Diagnostics for GridLayout - actions.add(MenuAction.createToggle(ACTION_DEBUG, "Debug", - sDebugGridLayout, actionCallback, null, 210)); + actions.add(RuleAction.createToggle(ACTION_DEBUG, "Debug", + sDebugGridLayout, actionCallback, null, 210, false)); } /** diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java index bf1efb5..ae42fc3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java @@ -44,13 +44,14 @@ import com.android.ide.common.api.IViewMetadata; import com.android.ide.common.api.IViewMetadata.FillPreference; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; -import com.android.ide.common.api.MenuAction; -import com.android.ide.common.api.MenuAction.OrderedChoices; +import com.android.ide.common.api.RuleAction; +import com.android.ide.common.api.RuleAction.Choices; import com.android.ide.common.api.Point; import com.android.ide.common.api.Rect; import com.android.ide.common.api.SegmentType; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.sdklib.SdkConstants; +import com.android.util.Pair; import java.net.URL; import java.util.ArrayList; @@ -91,21 +92,26 @@ public class LinearLayoutRule extends BaseLayoutRule { * Add an explicit Orientation toggle to the context menu. */ @Override - public List<MenuAction> getContextMenu(final INode selectedNode) { + public void addContextMenuActions(List<RuleAction> actions, final INode selectedNode) { + super.addContextMenuActions(actions, selectedNode); if (supportsOrientation()) { String current = getCurrentOrientation(selectedNode); - IMenuCallback onChange = new PropertyCallback(Collections.singletonList(selectedNode), + IMenuCallback onChange = new PropertyCallback( + null, // use passed in nodes instead to support multiple nodes "Change LinearLayout Orientation", ANDROID_URI, ATTR_ORIENTATION); - return concatenate(super.getContextMenu(selectedNode), - new MenuAction.Choices(ACTION_ORIENTATION, "Orientation", //$NON-NLS-1$ - mapify( - "horizontal", "Horizontal", //$NON-NLS-1$ - "vertical", "Vertical" //$NON-NLS-1$ - ), - current, onChange)); - } else { - return super.getContextMenu(selectedNode); + List<Pair<String, String>> alternatives = new ArrayList<Pair<String,String>>(2); + alternatives.add(Pair.of("horizontal", "Horizontal")); //$NON-NLS-1$ + alternatives.add(Pair.of("vertical", "Vertical")); //$NON-NLS-1$ + RuleAction action = RuleAction.createChoices( + ACTION_ORIENTATION, "Orientation", //$NON-NLS-1$ + onChange, + null /* iconUrls */, + current, + null /* icon */, 5, true, + alternatives); + + actions.add(action); } } @@ -145,22 +151,22 @@ public class LinearLayoutRule extends BaseLayoutRule { } @Override - public void addLayoutActions(List<MenuAction> actions, final INode parentNode, + public void addLayoutActions(List<RuleAction> actions, final INode parentNode, final List<? extends INode> children) { super.addLayoutActions(actions, parentNode, children); if (supportsOrientation()) { - OrderedChoices action = MenuAction.createChoices( + Choices action = RuleAction.createChoices( ACTION_ORIENTATION, "Orientation", //$NON-NLS-1$ - null, new PropertyCallback(Collections.singletonList(parentNode), "Change LinearLayout Orientation", ANDROID_URI, ATTR_ORIENTATION), - Arrays.<String>asList("Set Horizontal Orientation", "Set Vertical Orientation"), + Arrays.<String>asList("Set Horizontal Orientation","Set Vertical Orientation"), Arrays.<URL>asList(ICON_HORIZONTAL, ICON_VERTICAL), Arrays.<String>asList("horizontal", "vertical"), getCurrentOrientation(parentNode), null /* icon */, - -10 + -10, + false /* supportsMultipleNodes */ ); action.setRadio(true); actions.add(action); @@ -168,17 +174,17 @@ public class LinearLayoutRule extends BaseLayoutRule { if (!isVertical(parentNode)) { String current = parentNode.getStringAttr(ANDROID_URI, ATTR_BASELINE_ALIGNED); boolean isAligned = current == null || Boolean.valueOf(current); - actions.add(MenuAction.createToggle(null, "Toggle Baseline Alignment", + actions.add(RuleAction.createToggle(null, "Toggle Baseline Alignment", isAligned, new PropertyCallback(Collections.singletonList(parentNode), "Change Baseline Alignment", ANDROID_URI, ATTR_BASELINE_ALIGNED), // TODO: Also set index? - ICON_BASELINE, 38)); + ICON_BASELINE, 38, false)); } // Gravity if (children != null && children.size() > 0) { - actions.add(MenuAction.createSeparator(35)); + actions.add(RuleAction.createSeparator(35)); // Margins actions.add(createMarginAction(parentNode, children)); @@ -188,8 +194,8 @@ public class LinearLayoutRule extends BaseLayoutRule { // Weights IMenuCallback actionCallback = new IMenuCallback() { - public void action(final MenuAction action, final String valueId, - final Boolean newValue) { + public void action(final RuleAction action, List<? extends INode> selectedNodes, + final String valueId, final Boolean newValue) { parentNode.editXml("Change Weight", new INodeHandler() { public void handle(INode n) { String id = action.getId(); @@ -222,15 +228,15 @@ public class LinearLayoutRule extends BaseLayoutRule { }); } }; - actions.add(MenuAction.createSeparator(50)); - actions.add(MenuAction.createAction(ACTION_DISTRIBUTE, "Distribute Weights Evenly", - null, actionCallback, ICON_DISTRIBUTE, 60)); - actions.add(MenuAction.createAction(ACTION_DOMINATE, "Assign All Weight", - null, actionCallback, ICON_DOMINATE, 70)); - actions.add(MenuAction.createAction(ACTION_WEIGHT, "Change Layout Weight", null, - actionCallback, ICON_WEIGHTS, 80)); - actions.add(MenuAction.createAction(ACTION_CLEAR, "Clear All Weights", - null, actionCallback, ICON_CLEAR_WEIGHTS, 90)); + actions.add(RuleAction.createSeparator(50)); + actions.add(RuleAction.createAction(ACTION_DISTRIBUTE, "Distribute Weights Evenly", + actionCallback, ICON_DISTRIBUTE, 60, false /*supportsMultipleNodes*/)); + actions.add(RuleAction.createAction(ACTION_DOMINATE, "Assign All Weight", + actionCallback, ICON_DOMINATE, 70, false)); + actions.add(RuleAction.createAction(ACTION_WEIGHT, "Change Layout Weight", + actionCallback, ICON_WEIGHTS, 80, false)); + actions.add(RuleAction.createAction(ACTION_CLEAR, "Clear All Weights", + actionCallback, ICON_CLEAR_WEIGHTS, 90, false)); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MergeRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MergeRule.java index 77f5c22..12358f9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MergeRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MergeRule.java @@ -17,7 +17,7 @@ package com.android.ide.common.layout; import com.android.ide.common.api.INode; -import com.android.ide.common.api.MenuAction; +import com.android.ide.common.api.RuleAction; import java.util.List; @@ -29,9 +29,8 @@ public class MergeRule extends FrameLayoutRule { // on top of each other at (0,0) @Override - public List<MenuAction> getContextMenu(INode selectedNode) { + public void addContextMenuActions(List<RuleAction> actions, final INode selectedNode) { // Deliberately ignore super.getContextMenu(); we don't want to attempt to list // properties for the <merge> tag - return null; } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertyCallback.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertyCallback.java index 559aac8..45cd2c5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertyCallback.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertyCallback.java @@ -19,7 +19,7 @@ package com.android.ide.common.layout; import com.android.ide.common.api.IMenuCallback; import com.android.ide.common.api.INode; import com.android.ide.common.api.INodeHandler; -import com.android.ide.common.api.MenuAction; +import com.android.ide.common.api.RuleAction; import java.util.List; @@ -33,6 +33,16 @@ public class PropertyCallback implements IMenuCallback { private final String mUri; private final String mAttribute; + /** + * Creates a new property callback. + * + * @param targetNodes the nodes to apply the property to, or null to use the + * nodes pass into the + * {@link #action(RuleAction, List, String, Boolean)} method. + * @param undoLabel the label to use for the undo action + * @param uri the attribute URI to apply + * @param attribute the attribute name to apply + */ public PropertyCallback(List<? extends INode> targetNodes, String undoLabel, String uri, String attribute) { super(); @@ -43,13 +53,18 @@ public class PropertyCallback implements IMenuCallback { } // ---- Implements IMenuCallback ---- - public void action(MenuAction action, final String valueId, final Boolean newValue) { - if (mTargetNodes == null || mTargetNodes.size() == 0) { + public void action(RuleAction action, List<? extends INode> selectedNodes, + final String valueId, final Boolean newValue) { + if (mTargetNodes != null && mTargetNodes.size() > 0) { + selectedNodes = mTargetNodes; + } + if (selectedNodes == null || selectedNodes.size() == 0) { return; } - mTargetNodes.get(0).editXml(mUndoLabel, new INodeHandler() { + final List<? extends INode> nodes = selectedNodes; + selectedNodes.get(0).editXml(mUndoLabel, new INodeHandler() { public void handle(INode n) { - for (INode targetNode : mTargetNodes) { + for (INode targetNode : nodes) { if (valueId != null) { targetNode.setAttribute(mUri, mAttribute, valueId); } else { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RelativeLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RelativeLayoutRule.java index d53436f..90952c9 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RelativeLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RelativeLayoutRule.java @@ -50,7 +50,7 @@ import com.android.ide.common.api.INode.IAttribute; import com.android.ide.common.api.INodeHandler; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; -import com.android.ide.common.api.MenuAction; +import com.android.ide.common.api.RuleAction; import com.android.ide.common.api.Point; import com.android.ide.common.api.Rect; import com.android.ide.common.api.SegmentType; @@ -315,17 +315,18 @@ public class RelativeLayoutRule extends BaseLayoutRule { // ==== Layout Actions Bar ==== @Override - public void addLayoutActions(List<MenuAction> actions, final INode parentNode, + public void addLayoutActions(List<RuleAction> actions, final INode parentNode, final List<? extends INode> children) { super.addLayoutActions(actions, parentNode, children); actions.add(createGravityAction(Collections.<INode>singletonList(parentNode), ATTR_GRAVITY)); - actions.add(MenuAction.createSeparator(25)); + actions.add(RuleAction.createSeparator(25)); actions.add(createMarginAction(parentNode, children)); IMenuCallback callback = new IMenuCallback() { - public void action(MenuAction action, final String valueId, final Boolean newValue) { + public void action(RuleAction action, List<? extends INode> selectedNodes, + final String valueId, final Boolean newValue) { final String id = action.getId(); if (id.equals(ACTION_CENTER_VERTICAL)|| id.equals(ACTION_CENTER_HORIZONTAL)) { parentNode.editXml("Center", new INodeHandler() { @@ -356,18 +357,18 @@ public class RelativeLayoutRule extends BaseLayoutRule { // Centering actions if (children != null && children.size() > 0) { - actions.add(MenuAction.createSeparator(150)); - actions.add(MenuAction.createAction(ACTION_CENTER_VERTICAL, "Center Vertically", null, - callback, ICON_CENTER_VERTICALLY, 160)); - actions.add(MenuAction.createAction(ACTION_CENTER_HORIZONTAL, "Center Horizontally", - null, callback, ICON_CENTER_HORIZONTALLY, 170)); + actions.add(RuleAction.createSeparator(150)); + actions.add(RuleAction.createAction(ACTION_CENTER_VERTICAL, "Center Vertically", + callback, ICON_CENTER_VERTICALLY, 160, false)); + actions.add(RuleAction.createAction(ACTION_CENTER_HORIZONTAL, "Center Horizontally", + callback, ICON_CENTER_HORIZONTALLY, 170, false)); } - actions.add(MenuAction.createSeparator(80)); - actions.add(MenuAction.createToggle(ACTION_SHOW_CONSTRAINTS, "Show Constraints", - sShowConstraints, callback, ICON_SHOW_CONSTRAINTS, 180)); - actions.add(MenuAction.createToggle(ACTION_SHOW_STRUCTURE, "Show All Relationships", - sShowStructure, callback, ICON_SHOW_STRUCTURE, 190)); + actions.add(RuleAction.createSeparator(80)); + actions.add(RuleAction.createToggle(ACTION_SHOW_CONSTRAINTS, "Show Constraints", + sShowConstraints, callback, ICON_SHOW_CONSTRAINTS, 180, false)); + actions.add(RuleAction.createToggle(ACTION_SHOW_STRUCTURE, "Show All Relationships", + sShowStructure, callback, ICON_SHOW_STRUCTURE, 190, false)); } private void centerHorizontally(INode node) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java index d556e7d..4ae31b7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java @@ -24,7 +24,7 @@ import com.android.ide.common.api.INode; import com.android.ide.common.api.INodeHandler; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; -import com.android.ide.common.api.MenuAction; +import com.android.ide.common.api.RuleAction; import com.android.ide.common.api.SegmentType; import java.net.URL; @@ -69,21 +69,22 @@ public class TableLayoutRule extends LinearLayoutRule { * Add an explicit "Add Row" action to the context menu */ @Override - public List<MenuAction> getContextMenu(final INode selectedNode) { + public void addContextMenuActions(List<RuleAction> actions, final INode selectedNode) { + super.addContextMenuActions(actions, selectedNode); + IMenuCallback addTab = new IMenuCallback() { - public void action(MenuAction action, final String valueId, Boolean newValue) { + public void action(RuleAction action, List<? extends INode> selectedNodes, + final String valueId, Boolean newValue) { final INode node = selectedNode; INode newRow = node.appendChild(FQCN_TABLE_ROW); mRulesEngine.select(Collections.singletonList(newRow)); } }; - return concatenate(super.getContextMenu(selectedNode), - new MenuAction.Action("_addrow", "Add Row", //$NON-NLS-1$ - null, addTab)); + actions.add(RuleAction.createAction("_addrow", "Add Row", addTab, null, 5, false)); //$NON-NLS-1$ } @Override - public void addLayoutActions(List<MenuAction> actions, final INode parentNode, + public void addLayoutActions(List<RuleAction> actions, final INode parentNode, final List<? extends INode> children) { super.addLayoutActions(actions, parentNode, children); addTableLayoutActions(mRulesEngine, actions, parentNode, children); @@ -93,11 +94,11 @@ public class TableLayoutRule extends LinearLayoutRule { * Adds layout actions to add and remove toolbar items */ static void addTableLayoutActions(final IClientRulesEngine rulesEngine, - List<MenuAction> actions, final INode parentNode, + List<RuleAction> actions, final INode parentNode, final List<? extends INode> children) { IMenuCallback actionCallback = new IMenuCallback() { - public void action(final MenuAction action, final String valueId, - final Boolean newValue) { + public void action(final RuleAction action, List<? extends INode> selectedNodes, + final String valueId, final Boolean newValue) { parentNode.editXml("Add/Remove Table Row", new INodeHandler() { public void handle(INode n) { if (action.getId().equals(ACTION_ADD_ROW)) { @@ -155,14 +156,14 @@ public class TableLayoutRule extends LinearLayoutRule { }; // Add Row - actions.add(MenuAction.createSeparator(150)); - actions.add(MenuAction.createAction(ACTION_ADD_ROW, "Add Table Row", null, actionCallback, - ICON_ADD_ROW, 160)); + actions.add(RuleAction.createSeparator(150)); + actions.add(RuleAction.createAction(ACTION_ADD_ROW, "Add Table Row", actionCallback, + ICON_ADD_ROW, 160, false)); // Remove Row (if something is selected) if (children != null && children.size() > 0) { - actions.add(MenuAction.createAction(ACTION_REMOVE_ROW, "Remove Table Row", null, - actionCallback, ICON_REMOVE_ROW, 170)); + actions.add(RuleAction.createAction(ACTION_REMOVE_ROW, "Remove Table Row", + actionCallback, ICON_REMOVE_ROW, 170, false)); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableRowRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableRowRule.java index 031e17b..dad71ed 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableRowRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableRowRule.java @@ -21,7 +21,7 @@ import com.android.ide.common.api.DropFeedback; import com.android.ide.common.api.INode; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; -import com.android.ide.common.api.MenuAction; +import com.android.ide.common.api.RuleAction; import com.android.ide.common.api.SegmentType; import java.util.List; @@ -48,7 +48,7 @@ public class TableRowRule extends LinearLayoutRule { } @Override - public void addLayoutActions(List<MenuAction> actions, final INode parentNode, + public void addLayoutActions(List<RuleAction> actions, final INode parentNode, final List<? extends INode> children) { super.addLayoutActions(actions, parentNode, children); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java index e8cd418..dfc30fe 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java @@ -16,6 +16,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; +import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; import static com.android.ide.common.layout.LayoutConstants.EXPANDABLE_LIST_VIEW; import static com.android.ide.common.layout.LayoutConstants.FQCN_GESTURE_OVERLAY_VIEW; import static com.android.ide.common.layout.LayoutConstants.GRID_VIEW; @@ -23,12 +25,15 @@ import static com.android.ide.common.layout.LayoutConstants.LIST_VIEW; import static com.android.ide.common.layout.LayoutConstants.SPINNER; import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_FRAGMENT; -import com.android.ide.common.api.IMenuCallback; +import com.android.ide.common.api.INode; import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.MenuAction; -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.internal.editors.IconFactory; +import com.android.ide.common.api.RuleAction; +import com.android.ide.common.api.RuleAction.Choices; +import com.android.ide.common.api.RuleAction.NestedAction; +import com.android.ide.common.api.RuleAction.Toggle; +import com.android.ide.common.layout.BaseViewRule; import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory; import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeLayoutAction; import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeViewAction; @@ -39,20 +44,23 @@ import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElement import org.eclipse.jface.action.Action; import org.eclipse.jface.action.ActionContributionItem; +import org.eclipse.jface.action.ContributionItem; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IContributionItem; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Menu; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.TreeMap; -import java.util.regex.Pattern; +import java.util.Set; /** * Helper class that is responsible for adding and managing the dynamic menu items @@ -65,7 +73,7 @@ import java.util.regex.Pattern; * created by {@link OutlinePage}. Different root {@link MenuManager}s are populated, however * they are both linked to the current selection state of the {@link LayoutCanvas}. */ -/* package */ class DynamicContextMenu { +class DynamicContextMenu { /** The XML layout editor that contains the canvas that uses this menu. */ private final LayoutEditor mEditor; @@ -76,7 +84,6 @@ import java.util.regex.Pattern; /** The root menu manager of the context menu. */ private final MenuManager mMenuManager; - /** * Creates a new helper responsible for adding and managing the dynamic menu items * contributed by the {@link IViewRule} instances, based on the current selection @@ -125,22 +132,12 @@ import java.util.regex.Pattern; } /** - * This is invoked by <code>menuAboutToShow</code> on {@link #mMenuManager}. + * This method is invoked by <code>menuAboutToShow</code> on {@link #mMenuManager}. * All previous dynamic menu actions have been removed and this method can now insert * any new actions that depend on the current selection. */ private void populateDynamicContextMenu() { - // Map action-id => action object (one per selected view that defined it) - final TreeMap<String /*id*/, ArrayList<MenuAction>> actionsMap = - new TreeMap<String, ArrayList<MenuAction>>(); - - // Map group-id => actions to place in this group. - TreeMap<String /*id*/, MenuAction.Group> groupsMap = - new TreeMap<String, MenuAction.Group>(); - - int maxMenuSelection = collectDynamicMenuActions(actionsMap, groupsMap); - - // Now create the actual menu contributions + // Create the actual menu contributions String endId = mMenuManager.getItems()[0].getId(); Separator sep = new Separator(); @@ -148,73 +145,85 @@ import java.util.regex.Pattern; mMenuManager.insertBefore(endId, sep); endId = sep.getId(); - // First create the groups - Map<String, MenuManager> menuGroups = new HashMap<String, MenuManager>(); - for (MenuAction.Group group : groupsMap.values()) { - String id = group.getId(); - MenuManager submenu = new MenuManager(group.getTitle(), id); - menuGroups.put(id, submenu); - mMenuManager.insertBefore(endId, submenu); - endId = id; + List<SelectionItem> selections = mCanvas.getSelectionManager().getSelections(); + if (selections.size() == 0) { + return; + } + List<INode> nodes = new ArrayList<INode>(selections.size()); + for (SelectionItem item : selections) { + nodes.add(item.getNode()); } - boolean needGroupSep = !menuGroups.isEmpty(); + List<IContributionItem> menuItems = getMenuItems(nodes); + for (IContributionItem menuItem : menuItems) { + mMenuManager.insertBefore(endId, menuItem); + } - // Now fill in the actions - for (ArrayList<MenuAction> actions : actionsMap.values()) { - // Filter actions... if we have a multiple selection, only accept actions - // which are common to *all* the selection which actually returned at least - // one menu action. - if (actions == null || - actions.isEmpty() || - actions.size() != maxMenuSelection) { - continue; - } + insertTagSpecificMenus(endId); + insertVisualRefactorings(endId); + insertParentItems(endId); + } - if (!(actions.get(0) instanceof MenuAction.Action)) { - continue; - } + /** + * Returns the list of node-specific actions applicable to the given + * collection of nodes + * + * @param nodes the collection of nodes to look up actions for + * @return a list of contribution items applicable for all the nodes + */ + private List<IContributionItem> getMenuItems(List<INode> nodes) { + Map<INode, List<RuleAction>> allActions = new HashMap<INode, List<RuleAction>>(); + for (INode node : nodes) { + List<RuleAction> actionList = getMenuActions((NodeProxy) node); + allActions.put(node, actionList); + } - // Arbitrarily select the first action, as all the actions with the same id - // should have the same constant attributes such as id and title. - final MenuAction.Action firstAction = (MenuAction.Action) actions.get(0); + Set<String> availableIds = computeApplicableActionIds(allActions); - IContributionItem contrib = null; + // +10: Make room for separators too + List<IContributionItem> items = new ArrayList<IContributionItem>(availableIds.size() + 10); - if (firstAction instanceof MenuAction.Toggle) { - contrib = createDynamicMenuToggle((MenuAction.Toggle) firstAction, actionsMap); + // We'll use the actions returned by the first node. Even when there + // are multiple items selected, we'll use the first action, but pass + // the set of all selected nodes to that first action. Actions are required + // to work this way to facilitate multi selection and actions which apply + // to multiple nodes. + List<RuleAction> firstSelectedActions = allActions.get(nodes.get(0)); - } else if (firstAction instanceof MenuAction.Choices) { - Map<String, String> choiceMap = ((MenuAction.Choices) firstAction).getChoices(); - if (choiceMap != null && !choiceMap.isEmpty()) { - contrib = createDynamicChoices( - (MenuAction.Choices)firstAction, choiceMap, actionsMap); - } - } else { - // Must be a plain action - contrib = createDynamicAction(firstAction, actionsMap); + for (RuleAction action : firstSelectedActions) { + if (!availableIds.contains(action.getId()) + && !(action instanceof RuleAction.Separator)) { + // This action isn't supported by all selected items. + continue; } - if (contrib != null) { - MenuManager groupMenu = menuGroups.get(firstAction.getGroupId()); - if (groupMenu != null) { - groupMenu.add(contrib); - } else { - if (needGroupSep) { - needGroupSep = false; + items.add(createContributionItem(action, nodes)); + } - sep = new Separator(); - sep.setId("-dyn-gle-sep2"); //$NON-NLS-1$ - mMenuManager.insertBefore(endId, sep); - endId = sep.getId(); - } - mMenuManager.insertBefore(endId, contrib); + return items; + } + + private void insertParentItems(String endId) { + List<SelectionItem> selection = mCanvas.getSelectionManager().getSelections(); + if (selection.size() == 1) { + mMenuManager.insertBefore(endId, new Separator()); + INode parent = selection.get(0).getNode().getParent(); + while (parent != null) { + String id = parent.getStringAttr(ANDROID_URI, ATTR_ID); + String label; + if (id != null && id.length() > 0) { + label = BaseViewRule.stripIdPrefix(id); + } else { + // Use the view name, such as "Button", as the label + label = parent.getFqcn(); + // Strip off package + label = label.substring(label.lastIndexOf('.') + 1); } + mMenuManager.insertBefore(endId, new NestedParentMenu(label, parent)); + parent = parent.getParent(); } + mMenuManager.insertBefore(endId, new Separator()); } - - insertTagSpecificMenus(endId); - insertVisualRefactorings(endId); } private void insertVisualRefactorings(String endId) { @@ -264,98 +273,68 @@ import java.util.regex.Pattern; } /** - * Collects all the {@link MenuAction} contributed by the {@link IViewRule} of the - * current selection. - * This is the first step of {@link #populateDynamicContextMenu()}. + * Given a map from selection items to list of applicable actions (produced + * by {@link #computeApplicableActions()}) this method computes the set of + * common actions and returns the action ids of these actions. * - * @param outActionsMap Map that collects all the contributed actions. - * @param outGroupsMap Map that collects all the contributed groups (sub-menus). - * @return The max number of selected items that contributed the same action ID. - * This is used later to filter on multiple selections so that we can display only - * actions that are common to all selected items that contributed at least one action. + * @param actions a map from selection item to list of actions applicable to + * that selection item + * @return set of action ids for the actions that are present in the action + * lists for all selected items */ - private int collectDynamicMenuActions( - final TreeMap<String, ArrayList<MenuAction>> outActionsMap, - final TreeMap<String, MenuAction.Group> outGroupsMap) { - int maxMenuSelection = 0; - for (SelectionItem selection : mCanvas.getSelectionManager().getSelections()) { - List<MenuAction> viewActions = null; - if (selection != null) { - CanvasViewInfo vi = selection.getViewInfo(); - if (vi != null) { - viewActions = getMenuActions(vi); - } - } - if (viewActions == null) { - continue; - } - - boolean foundAction = false; - for (MenuAction action : viewActions) { - - // Allow nulls - ignore these. Make it easier to define action lists - // literals where some items may not be included (because their references - // are null). - if (action == null) { - continue; - } - - if (action.getId() == null || action.getTitle() == null) { - // TODO Log verbose error for invalid action. - continue; - } - - String id = action.getId(); - - if (action instanceof MenuAction.Group) { - if (!outGroupsMap.containsKey(id)) { - outGroupsMap.put(id, (MenuAction.Group) action); - } - continue; - } - - ArrayList<MenuAction> actions = outActionsMap.get(id); - if (actions == null) { - actions = new ArrayList<MenuAction>(); - outActionsMap.put(id, actions); - } - - // All the actions for the same id should have be equal - if (!actions.isEmpty()) { - if (!action.equals(actions.get(0))) { - // TODO Log verbose error for invalid type mismatch. + private Set<String> computeApplicableActionIds(Map<INode, List<RuleAction>> actions) { + if (actions.size() > 1) { + // More than one view is selected, so we have to filter down the available + // actions such that only those actions that are defined for all the views + // are shown + Map<String, Integer> idCounts = new HashMap<String, Integer>(); + for (Map.Entry<INode, List<RuleAction>> entry : actions.entrySet()) { + List<RuleAction> actionList = entry.getValue(); + for (RuleAction action : actionList) { + if (!action.supportsMultipleNodes()) { continue; } + String id = action.getId(); + if (id != null) { + assert id != null : action; + Integer count = idCounts.get(id); + if (count == null) { + idCounts.put(id, Integer.valueOf(1)); + } else { + idCounts.put(id, count + 1); + } + } } - - actions.add(action); - foundAction = true; } - - if (foundAction) { - maxMenuSelection++; + Integer selectionCount = Integer.valueOf(actions.size()); + Set<String> validIds = new HashSet<String>(idCounts.size()); + for (Map.Entry<String, Integer> entry : idCounts.entrySet()) { + Integer count = entry.getValue(); + if (selectionCount.equals(count)) { + String id = entry.getKey(); + validIds.add(id); + } } + return validIds; + } else { + List<RuleAction> actionList = actions.values().iterator().next(); + Set<String> validIds = new HashSet<String>(actionList.size()); + for (RuleAction action : actionList) { + String id = action.getId(); + validIds.add(id); + } + return validIds; } - return maxMenuSelection; } /** - * Returns the menu actions computed by the rule associated with this view. + * Returns the menu actions computed by the rule associated with this node. * - * @param vi the canvas view info we need menu actions for - * @return a list of {@link MenuAction} objects applicable to the view info + * @param node the canvas node we need menu actions for + * @return a list of {@link RuleAction} objects applicable to the node */ - public List<MenuAction> getMenuActions(CanvasViewInfo vi) { - if (vi == null) { - return null; - } - - NodeProxy node = mCanvas.getNodeFactory().create(vi); - if (node == null) { - return null; - } - - List<MenuAction> actions = mCanvas.getRulesEngine().callGetContextMenu(node); + private List<RuleAction> getMenuActions(NodeProxy node) { + List<RuleAction> actions = mCanvas.getRulesEngine().callGetContextMenu(node); if (actions == null || actions.size() == 0) { return null; } @@ -364,273 +343,234 @@ import java.util.regex.Pattern; } /** - * Invoked by {@link #populateDynamicContextMenu()} to create a new menu item - * for a {@link MenuAction.Toggle}. - * <p/> - * Toggles are represented by a checked menu item. + * Creates a {@link ContributionItem} for the given {@link RuleAction}. * - * @param firstAction The toggle action to convert to a menu item. In the case of a - * multiple selection, this is the first of many similar actions. - * @param actionsMap Map of all contributed actions. - * @return a new {@link IContributionItem} to add to the context menu + * @param action the action to create a {@link ContributionItem} for + * @param nodes the set of nodes the action should be applied to + * @return a new {@link ContributionItem} which implements the given action + * on the given nodes */ - private IContributionItem createDynamicMenuToggle( - final MenuAction.Toggle firstAction, - final TreeMap<String, ArrayList<MenuAction>> actionsMap) { - - final boolean isChecked = firstAction.isChecked(); + private ContributionItem createContributionItem(final RuleAction action, + final List<INode> nodes) { + if (action instanceof RuleAction.Separator) { + return new Separator(); + } else if (action instanceof NestedAction) { + NestedAction parentAction = (NestedAction) action; + return new ActionContributionItem(new NestedActionMenu(parentAction, nodes)); + } else if (action instanceof Choices) { + Choices parentAction = (Choices) action; + return new ActionContributionItem(new NestedChoiceMenu(parentAction, nodes)); + } else if (action instanceof Toggle) { + return new ActionContributionItem(createToggleAction(action, nodes)); + } else { + return new ActionContributionItem(createPlainAction(action, nodes)); + } + } - Action a = new Action(firstAction.getTitle(), IAction.AS_CHECK_BOX) { + private Action createToggleAction(final RuleAction action, final List<INode> nodes) { + Toggle toggleAction = (Toggle) action; + final boolean isChecked = toggleAction.isChecked(); + Action a = new Action(action.getTitle(), IAction.AS_CHECK_BOX) { @Override public void run() { - final List<MenuAction> actions = actionsMap.get(firstAction.getId()); - if (actions == null || actions.isEmpty()) { - return; - } - - String label = String.format("Toggle attribute %s", actions.get(0).getTitle()); - if (actions.size() > 1) { - label += String.format(" (%d elements)", actions.size()); - } - - if (mEditor.isEditXmlModelPending()) { - // This should not be happening. - logError("Action '%s' failed: XML changes pending, document might be corrupt.", //$NON-NLS-1$ - label); - return; - } - + String label = createActionLabel(action, nodes); mEditor.wrapUndoEditXmlModel(label, new Runnable() { public void run() { - // Invoke the callbacks of all the actions using the same action-id - for (MenuAction a2 : actions) { - if (a2 instanceof MenuAction.Action) { - IMenuCallback c = ((MenuAction.Action) a2).getCallback(); - if (c != null) { - try { - c.action(a2, null /* no valueId for a toggle */, - !isChecked); - } catch (Exception e) { - AdtPlugin.log(e, "XML edit operation failed: %s", - e.toString()); - } - } - } - } + action.getCallback().action(action, nodes, + null/* no valueId for a toggle */, !isChecked); + applyPendingChanges(); } }); } }; - a.setId(firstAction.getId()); + a.setId(action.getId()); a.setChecked(isChecked); - - return new ActionContributionItem(a); + return a; } - /** - * Invoked by {@link #populateDynamicContextMenu()} to create a new menu item - * for a plain action. This is nearly identical to {@link #createDynamicMenuToggle}, - * except for the {@link IAction} type and the removal of setChecked, isChecked, etc. - * - * @param firstAction The action to convert to a menu item. In the case of a - * multiple selection, this is the first of many similar actions. - * @param actionsMap Map of all contributed actions. - * @return a new {@link IContributionItem} to add to the context menu - */ - private IContributionItem createDynamicAction( - final MenuAction.Action firstAction, - final TreeMap<String, ArrayList<MenuAction>> actionsMap) { - - Action a = new Action(firstAction.getTitle(), IAction.AS_PUSH_BUTTON) { + private IAction createPlainAction(final RuleAction action, final List<INode> nodes) { + IAction a = new Action(action.getTitle(), IAction.AS_PUSH_BUTTON) { @Override public void run() { - final List<MenuAction> actions = actionsMap.get(firstAction.getId()); - if (actions == null || actions.isEmpty()) { - return; - } - - String label = actions.get(0).getTitle(); - if (actions.size() > 1) { - label += String.format(" (%d elements)", actions.size()); - } - - if (mEditor.isEditXmlModelPending()) { - // This should not be happening. - logError("Action '%s' failed: XML changes pending, document might be corrupt.", //$NON-NLS-1$ - label); - return; - } - + String label = createActionLabel(action, nodes); mEditor.wrapUndoEditXmlModel(label, new Runnable() { public void run() { - // Invoke the callbacks of all the actions using the same action-id - for (MenuAction a2 : actions) { - if (a2 instanceof MenuAction.Action) { - IMenuCallback c = ((MenuAction.Action) a2).getCallback(); - if (c != null) { - try { - // Values do not apply for plain actions - c.action(a2, null /* valueId */, null /* newValue */); - } catch (Exception e) { - AdtPlugin.log(e, "XML edit operation failed: %s", - e.toString()); - } - } - } - } + action.getCallback().action(action, nodes, null, + Boolean.TRUE); + applyPendingChanges(); } }); } }; - a.setId(firstAction.getId()); + a.setId(action.getId()); + return a; + } - return new ActionContributionItem(a); + private static String createActionLabel(final RuleAction action, final List<INode> nodes) { + String label = action.getTitle(); + if (nodes.size() > 1) { + label += String.format(" (%d elements)", nodes.size()); + } + return label; } /** - * Invoked by {@link #populateDynamicContextMenu()} to create a new menu item - * for a {@link MenuAction.Choices}. - * <p/> - * Multiple-choices are represented by a sub-menu containing checked items. - * - * @param firstAction The choices action to convert to a menu item. In the case of a - * multiple selection, this is the first of many similar actions. - * @param actionsMap Map of all contributed actions. - * @return a new {@link IContributionItem} to add to the context menu + * The {@link NestedParentMenu} provides submenu content which adds actions + * available on one of the selected node's parent nodes. This will be + * similar to the menu content for the selected node, except the parent + * menus will not be embedded within the nested menu. */ - private IContributionItem createDynamicChoices( - final MenuAction.Choices firstAction, - Map<String, String> choiceMap, - final TreeMap<String, ArrayList<MenuAction>> actionsMap) { + private class NestedParentMenu extends SubmenuAction { + INode mParent; - IconFactory factory = IconFactory.getInstance(); - MenuManager submenu = new MenuManager(firstAction.getTitle(), firstAction.getId()); - - // Convert to a tree map as needed so that keys be naturally ordered. - if (!(choiceMap instanceof TreeMap<?, ?>)) { - choiceMap = new TreeMap<String, String>(choiceMap); + NestedParentMenu(String title, INode parent) { + super(title); + mParent = parent; } - String sepPattern = Pattern.quote(MenuAction.Choices.CHOICE_SEP); - - for (Entry<String, String> entry : choiceMap.entrySet() ) { - final String key = entry.getKey(); - String title = entry.getValue(); - - if (key == null || title == null) { - continue; + @Override + protected void addMenuItems(Menu menu) { + List<SelectionItem> selection = mCanvas.getSelectionManager().getSelections(); + if (selection.size() == 0) { + return; } - if (MenuAction.Choices.SEPARATOR.equals(title)) { - submenu.add(new Separator()); - continue; + List<IContributionItem> menuItems = getMenuItems(Collections.singletonList(mParent)); + for (IContributionItem menuItem : menuItems) { + menuItem.fill(menu, -1); } + } + } - final List<MenuAction> actions = actionsMap.get(firstAction.getId()); + /** + * The {@link NestedActionMenu} creates a lazily populated pull-right menu + * where the children are {@link RuleAction}'s themselves. + */ + private class NestedActionMenu extends SubmenuAction { + private final NestedAction mParentAction; + private final List<INode> mNodes; - if (actions == null || actions.isEmpty()) { - continue; + NestedActionMenu(NestedAction parentAction, List<INode> nodes) { + super(parentAction.getTitle()); + mParentAction = parentAction; + mNodes = nodes; + + assert mNodes.size() > 0; + } + + @Override + protected void addMenuItems(Menu menu) { + Map<INode, List<RuleAction>> allActions = new HashMap<INode, List<RuleAction>>(); + for (INode node : mNodes) { + List<RuleAction> actionList = mParentAction.getNestedActions(node); + allActions.put(node, actionList); } - // Are all actions for this id checked, unchecked, or in a mixed state? - int numOff = 0; - int numOn = 0; - for (MenuAction a2 : actions) { - MenuAction.Choices choice = (MenuAction.Choices) a2; - String current = choice.getCurrent(); - if (current == null) { - // None of the choices were selected. This can for example happen if - // the user does not have an attribute for "layout_width" set on the element - // and the context menu is opened to see the width choices. - numOff++; + Set<String> availableIds = computeApplicableActionIds(allActions); + List<RuleAction> firstSelectedActions = allActions.get(mNodes.get(0)); + + for (RuleAction firstAction : firstSelectedActions) { + if (!availableIds.contains(firstAction.getId()) + && !(firstAction instanceof RuleAction.Separator)) { + // This action isn't supported by all selected items. continue; } - boolean found = false; - - if (current.indexOf(MenuAction.Choices.CHOICE_SEP) >= 0) { - // current choice has a separator, so it's a flag with multiple values - // selected. Compare keys with the split values. - if (current.indexOf(key) >= 0) { - for(String value : current.split(sepPattern)) { - if (key.equals(value)) { - found = true; - break; - } - } - } - } else { - // current choice has no separator, simply compare to the key - found = key.equals(current); - } - if (found) { - numOn++; - } else { - numOff++; - } + createContributionItem(firstAction, mNodes).fill(menu, -1); } + } + } - // We consider the item to be checked if all actions are all checked. - // This means a mixed item will be first toggled from off to on by all the callbacks. - final boolean isChecked = numOff == 0 && numOn > 0; - boolean isMixed = numOff > 0 && numOn > 0; - - if (isMixed) { - title += String.format(" (%1$d/%2$d)", numOn, numOff + numOn); + private void applyPendingChanges() { + LayoutCanvas canvas = mEditor.getGraphicalEditor().getCanvasControl(); + CanvasViewInfo root = canvas.getViewHierarchy().getRoot(); + if (root != null) { + UiViewElementNode uiViewNode = root.getUiViewNode(); + NodeFactory nodeFactory = canvas.getNodeFactory(); + NodeProxy rootNode = nodeFactory.create(uiViewNode); + if (rootNode != null) { + rootNode.applyPendingChanges(); } + } + } - Action a = new Action(title, IAction.AS_CHECK_BOX) { - @Override - public void run() { - - String label = - String.format("Change attribute %1$s", actions.get(0).getTitle()); - if (actions.size() > 1) { - label += String.format(" (%1$d elements)", actions.size()); - } + /** + * The {@link NestedChoiceMenu} creates a lazily populated pull-right menu + * where the items in the menu are strings + */ + private class NestedChoiceMenu extends SubmenuAction { + private final Choices mParentAction; + private final List<INode> mNodes; + + NestedChoiceMenu(Choices parentAction, List<INode> nodes) { + super(parentAction.getTitle()); + mParentAction = parentAction; + mNodes = nodes; + } - if (mEditor.isEditXmlModelPending()) { - // This should not be happening. - logError("Action '%1$s' failed: XML changes pending, document might be corrupt.", //$NON-NLS-1$ - label); - return; - } + @Override + protected void addMenuItems(Menu menu) { + List<String> titles = mParentAction.getTitles(); + List<String> ids = mParentAction.getIds(); + String current = mParentAction.getCurrent(); + assert titles.size() == ids.size(); + String[] currentValues = current != null + && current.indexOf(RuleAction.CHOICE_SEP) != -1 ? + current.split(RuleAction.CHOICE_SEP_PATTERN) : null; + for (int i = 0, n = Math.min(titles.size(), ids.size()); i < n; i++) { + final String id = ids.get(i); + if (id == null || id.equals(RuleAction.SEPARATOR)) { + new Separator().fill(menu, -1); + continue; + } - mEditor.wrapUndoEditXmlModel(label, new Runnable() { - public void run() { - // Invoke the callbacks of all the actions using the same action-id - for (MenuAction a2 : actions) { - if (a2 instanceof MenuAction.Action) { - try { - ((MenuAction.Action) a2).getCallback().action(a2, key, - !isChecked); - } catch (Exception e) { - AdtPlugin.log(e, "XML edit operation failed: %s", - e.toString()); - } + // Find out whether this item is selected + boolean select = false; + if (current != null) { + // The current choice has a separator, so it's a flag with + // multiple values selected. Compare keys with the split + // values. + if (currentValues != null) { + if (current.indexOf(id) >= 0) { + for (String value : currentValues) { + if (id.equals(value)) { + select = true; + break; } } } - }); + } else { + // current choice has no separator, simply compare to the key + select = id.equals(current); + } } - }; - a.setId(String.format("%1$s_%2$s", firstAction.getId(), key)); //$NON-NLS-1$ - a.setChecked(isChecked); - if (isMixed) { - a.setImageDescriptor(factory.getImageDescriptor("match_multiple")); //$NON-NLS-1$ - } - - submenu.add(a); - } - return submenu; - } + String title = titles.get(i); + IAction a = new Action(title, IAction.AS_PUSH_BUTTON) { + @Override + public void runWithEvent(Event event) { + run(); + } + @Override + public void run() { + String label = createActionLabel(mParentAction, mNodes); + mEditor.wrapUndoEditXmlModel(label, new Runnable() { + public void run() { + mParentAction.getCallback().action(mParentAction, mNodes, id, + Boolean.TRUE); + applyPendingChanges(); + } + }); + } + }; + a.setId(id); + a.setEnabled(true); + if (select) { + a.setChecked(true); + } - private void logError(String format, Object...args) { - AdtPlugin.logAndPrintError( - null, // exception - mCanvas.getRulesEngine().getProject().getName(), // tag - format, args); + new ActionContributionItem(a).fill(menu, -1); + } + } } - } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java index 151a240..74960cf 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java @@ -184,24 +184,6 @@ public class GestureManager { } /** - * Returns the {@link DropTargetListener} used by the GestureManager. This - * is a bit leaky, but the Outline is reusing all this code... This should - * be separated out. - */ - /* package */DropTargetListener getDropTargetListener() { - return mDropListener; - } - - /** - * Returns the {@link DragSourceListener} used by the GestureManager. This - * is a bit leaky, but the Outline is reusing all this code... This should - * be separated out. - */ - /* package */DragSourceListener getDragSourceListener() { - return mDragSourceListener; - } - - /** * Registers all the listeners needed by the {@link GestureManager}. * * @param dragSource The drag source in the {@link LayoutCanvas} to listen diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java index 0dcd83e..844c8d3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java @@ -18,12 +18,12 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; -import com.android.ide.common.api.MenuAction; -import com.android.ide.common.api.MenuAction.OrderedChoices; -import com.android.ide.common.api.MenuAction.Separator; -import com.android.ide.common.api.MenuAction.Toggle; +import com.android.ide.common.api.INode; +import com.android.ide.common.api.RuleAction; +import com.android.ide.common.api.RuleAction.Choices; +import com.android.ide.common.api.RuleAction.Separator; +import com.android.ide.common.api.RuleAction.Toggle; import com.android.ide.common.layout.BaseViewRule; -import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.editors.IconFactory; import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite; import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; @@ -31,7 +31,6 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.sdkuilib.internal.widgets.ResolutionChooserDialog; -import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; @@ -67,6 +66,13 @@ public class LayoutActionBar extends Composite { private ToolItem mZoomInButton; private ToolItem mZoomFitButton; + /** + * Creates a new {@link LayoutActionBar} and adds it to the given parent. + * + * @param parent the parent composite to add the actions bar to + * @param style the SWT style to apply + * @param editor the associated layout editor + */ public LayoutActionBar(Composite parent, int style, GraphicalEditorPart editor) { super(parent, style | SWT.NO_FOCUS); mEditor = editor; @@ -118,7 +124,7 @@ public class LayoutActionBar extends Composite { for (SelectionItem item : selections) { selectedNodes.add(item.getNode()); } - List<MenuAction> actions = new ArrayList<MenuAction>(); + List<RuleAction> actions = new ArrayList<RuleAction>(); engine.callAddLayoutActions(actions, parent, selectedNodes); // Place actions in the correct order (the actions may come from different @@ -131,14 +137,14 @@ public class LayoutActionBar extends Composite { int index = -1; String label = null; if (selectedNodes.size() == 1) { - List<MenuAction> itemActions = new ArrayList<MenuAction>(); + List<RuleAction> itemActions = new ArrayList<RuleAction>(); NodeProxy selectedNode = selectedNodes.get(0); engine.callAddLayoutActions(itemActions, selectedNode, null); if (itemActions.size() > 0) { Collections.sort(itemActions); - if (!(itemActions.get(0) instanceof MenuAction.Separator)) { - actions.add(MenuAction.createSeparator(0)); + if (!(itemActions.get(0) instanceof RuleAction.Separator)) { + actions.add(RuleAction.createSeparator(0)); } label = selectedNode.getStringAttr(ANDROID_URI, ATTR_ID); if (label != null) { @@ -155,7 +161,7 @@ public class LayoutActionBar extends Composite { mLayoutToolBar.layout(); } - private void addActions(List<MenuAction> actions, int labelIndex, String label) { + private void addActions(List<RuleAction> actions, int labelIndex, String label) { if (actions.size() > 0) { // Flag used to indicate that if there are any actions -after- this, it // should be separated from this current action (we don't unconditionally @@ -164,7 +170,7 @@ public class LayoutActionBar extends Composite { boolean needSeparator = false; int index = 0; - for (MenuAction action : actions) { + for (RuleAction action : actions) { if (index == labelIndex) { final ToolItem button = new ToolItem(mLayoutToolBar, SWT.PUSH); button.setText(label); @@ -181,8 +187,8 @@ public class LayoutActionBar extends Composite { needSeparator = false; } - if (action instanceof MenuAction.OrderedChoices) { - MenuAction.OrderedChoices choices = (OrderedChoices) action; + if (action instanceof RuleAction.Choices) { + RuleAction.Choices choices = (Choices) action; if (!choices.isRadio()) { addDropdown(choices); } else { @@ -190,13 +196,10 @@ public class LayoutActionBar extends Composite { addRadio(choices); needSeparator = true; } - } else if (action instanceof MenuAction.Toggle) { + } else if (action instanceof RuleAction.Toggle) { addToggle((Toggle) action); - } else if (action instanceof MenuAction.Action) { - addAction((MenuAction.Action) action); } else { - AdtPlugin.log(IStatus.ERROR, "Action not supported in toolbar: %1$s", - action.getTitle()); + addPlainAction(action); } } } @@ -226,8 +229,8 @@ public class LayoutActionBar extends Composite { button.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - toggle.getCallback().action(toggle, toggle.getId(), - button.getSelection()); + toggle.getCallback().action(toggle, getSelectedNodes(), + toggle.getId(), button.getSelection()); updateSelection(); } }); @@ -236,7 +239,19 @@ public class LayoutActionBar extends Composite { } } - private void addAction(final MenuAction.Action menuAction) { + private List<INode> getSelectedNodes() { + List<SelectionItem> selections = + mEditor.getCanvasControl().getSelectionManager().getSelections(); + List<INode> nodes = new ArrayList<INode>(selections.size()); + for (SelectionItem item : selections) { + nodes.add(item.getNode()); + } + + return nodes; + } + + + private void addPlainAction(final RuleAction menuAction) { final ToolItem button = new ToolItem(mLayoutToolBar, SWT.PUSH); URL iconUrl = menuAction.getIconUrl(); @@ -251,13 +266,14 @@ public class LayoutActionBar extends Composite { button.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - menuAction.getCallback().action(menuAction, menuAction.getId(), false); + menuAction.getCallback().action(menuAction, getSelectedNodes(), menuAction.getId(), + false); updateSelection(); } }); } - private void addRadio(final MenuAction.OrderedChoices choices) { + private void addRadio(final RuleAction.Choices choices) { List<URL> icons = choices.getIconUrls(); List<String> titles = choices.getTitles(); List<String> ids = choices.getIds(); @@ -277,7 +293,7 @@ public class LayoutActionBar extends Composite { @Override public void widgetSelected(SelectionEvent e) { if (item.getSelection()) { - choices.getCallback().action(choices, id, null); + choices.getCallback().action(choices, getSelectedNodes(), id, null); updateSelection(); } } @@ -289,7 +305,7 @@ public class LayoutActionBar extends Composite { } } - private void addDropdown(final MenuAction.OrderedChoices choices) { + private void addDropdown(final RuleAction.Choices choices) { final ToolItem combo = new ToolItem(mLayoutToolBar, SWT.DROP_DOWN); URL iconUrl = choices.getIconUrl(); if (iconUrl != null) { @@ -331,7 +347,7 @@ public class LayoutActionBar extends Composite { item.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - choices.getCallback().action(choices, id, null); + choices.getCallback().action(choices, getSelectedNodes(), id, null); updateSelection(); } }); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java index 32cc45d..1e6d958 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java @@ -1047,11 +1047,8 @@ public class LayoutCanvas extends Canvas { /** * Helper to create the drop target for the given control. - * <p/> - * This is static with package-access so that {@link OutlinePage} can also - * create an exact copy of the drop target with the same attributes. */ - /* package */static DropTarget createDropTarget(Control control) { + private static DropTarget createDropTarget(Control control) { DropTarget dropTarget = new DropTarget( control, DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_DEFAULT); dropTarget.setTransfer(new Transfer[] { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java index 4ee3aac..770f0eb 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java @@ -23,7 +23,7 @@ import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT; import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT; import com.android.ide.common.api.InsertType; -import com.android.ide.common.api.MenuAction.Toggle; +import com.android.ide.common.api.RuleAction.Toggle; import com.android.ide.common.api.Rect; import com.android.ide.common.rendering.LayoutLibrary; import com.android.ide.common.rendering.api.Capability; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java index 15f4379..9cf683a 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java @@ -25,7 +25,7 @@ import com.android.ide.common.api.IGraphics; import com.android.ide.common.api.INode; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.InsertType; -import com.android.ide.common.api.MenuAction; +import com.android.ide.common.api.RuleAction; import com.android.ide.common.api.Point; import com.android.ide.common.api.Rect; import com.android.ide.common.api.SegmentType; @@ -52,6 +52,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -211,21 +212,24 @@ public class RulesEngine { } /** - * Invokes {@link IViewRule#getContextMenu(INode)} on the rule matching the specified element. + * Invokes {@link IViewRule#addContextMenuActions(List, INode)} on the rule matching the specified element. * * @param selectedNode The node selected. Never null. * @return Null if the rule failed, there's no rule or the rule does not provide - * any custom menu actions. Otherwise, a list of {@link MenuAction}. + * any custom menu actions. Otherwise, a list of {@link RuleAction}. */ - public List<MenuAction> callGetContextMenu(NodeProxy selectedNode) { + public List<RuleAction> callGetContextMenu(NodeProxy selectedNode) { // try to find a rule for this element's FQCN IViewRule rule = loadRule(selectedNode.getNode()); if (rule != null) { try { mInsertType = InsertType.CREATE; - return rule.getContextMenu(selectedNode); + List<RuleAction> actions = new ArrayList<RuleAction>(); + rule.addContextMenuActions(actions, selectedNode); + Collections.sort(actions); + return actions; } catch (Exception e) { AdtPlugin.log(e, "%s.getContextMenu() failed: %s", rule.getClass().getSimpleName(), @@ -237,16 +241,18 @@ public class RulesEngine { } /** - * Invokes {@link IViewRule#getContextMenu(INode)} on the rule matching the specified element. + * Invokes {@link IViewRule#addLayoutActions(List, INode, List)} on the rule + * matching the specified element. * * @param actions The list of actions to add layout actions into * @param parentNode The layout node - * @param children The selected children of the node, if any (used to initialize values - * of child layout controls, if applicable) - * @return Null if the rule failed, there's no rule or the rule does not provide - * any custom menu actions. Otherwise, a list of {@link MenuAction}. + * @param children The selected children of the node, if any (used to + * initialize values of child layout controls, if applicable) + * @return Null if the rule failed, there's no rule or the rule does not + * provide any custom menu actions. Otherwise, a list of + * {@link RuleAction}. */ - public List<MenuAction> callAddLayoutActions(List<MenuAction> actions, + public List<RuleAction> callAddLayoutActions(List<RuleAction> actions, NodeProxy parentNode, List<NodeProxy> children ) { // try to find a rule for this element's FQCN IViewRule rule = loadRule(parentNode.getNode()); diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LinearLayoutRuleTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LinearLayoutRuleTest.java index 26993bd..145be61 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LinearLayoutRuleTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LinearLayoutRuleTest.java @@ -29,14 +29,14 @@ import com.android.ide.common.api.IDragElement; import com.android.ide.common.api.IMenuCallback; import com.android.ide.common.api.INode; import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.MenuAction; +import com.android.ide.common.api.RuleAction; import com.android.ide.common.api.Point; import com.android.ide.common.api.Rect; -import com.android.ide.common.api.MenuAction.Choices; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; -import java.util.Map; /** Test the {@link LinearLayoutRule} */ public class LinearLayoutRuleTest extends LayoutTestBase { @@ -126,17 +126,19 @@ public class LinearLayoutRuleTest extends LayoutTestBase { initialize(rule, "android.widget.LinearLayout"); INode node = TestNode.create("android.widget.Button").id("@+id/Button012"); - List<MenuAction> contextMenu = rule.getContextMenu(node); + List<RuleAction> contextMenu = new ArrayList<RuleAction>(); + rule.addContextMenuActions(contextMenu, node); assertEquals(6, contextMenu.size()); - assertNull(contextMenu.get(0)); // Edit Text... not available - assertEquals("Edit ID...", contextMenu.get(1).getTitle()); - assertEquals("Layout Width", contextMenu.get(2).getTitle()); - assertEquals("Layout Height", contextMenu.get(3).getTitle()); + assertEquals("Edit ID...", contextMenu.get(0).getTitle()); + assertEquals("Layout Width", contextMenu.get(1).getTitle()); + assertEquals("Layout Height", contextMenu.get(2).getTitle()); + assertTrue(contextMenu.get(3) instanceof RuleAction.Separator); assertEquals("Properties", contextMenu.get(4).getTitle()); assertEquals("Orientation", contextMenu.get(5).getTitle()); - MenuAction propertiesMenu = contextMenu.get(4); - assertTrue(propertiesMenu.getClass().getName(), propertiesMenu instanceof MenuAction.Group); + RuleAction propertiesMenu = contextMenu.get(4); + assertTrue(propertiesMenu.getClass().getName(), + propertiesMenu instanceof RuleAction.NestedAction); // TODO: Test Properties-list } @@ -147,17 +149,21 @@ public class LinearLayoutRuleTest extends LayoutTestBase { .set(ANDROID_URI, ATTR_LAYOUT_WIDTH, "42dip") .set(ANDROID_URI, ATTR_LAYOUT_HEIGHT, "50sp"); - List<MenuAction> contextMenu = rule.getContextMenu(node); + List<RuleAction> contextMenu = new ArrayList<RuleAction>(); + rule.addContextMenuActions(contextMenu, node); assertEquals(6, contextMenu.size()); - assertEquals("Layout Width", contextMenu.get(2).getTitle()); - MenuAction menuAction = contextMenu.get(2); - assertTrue(menuAction instanceof MenuAction.Choices); - MenuAction.Choices choices = (Choices) menuAction; - Map<String, String> items = choices.getChoices(); - assertTrue(items.containsKey("42dip")); - assertTrue(items.containsValue("42dip")); - assertEquals("Other...", items.get("zcustom")); - assertEquals("Match Parent", items.get("match_parent")); + assertEquals("Layout Width", contextMenu.get(1).getTitle()); + RuleAction menuAction = contextMenu.get(1); + assertTrue(menuAction instanceof RuleAction.Choices); + RuleAction.Choices choices = (RuleAction.Choices) menuAction; + List<String> titles = choices.getTitles(); + List<String> ids = choices.getIds(); + assertEquals("Wrap Content", titles.get(0)); + assertEquals("wrap_content", ids.get(0)); + assertEquals("Match Parent", titles.get(1)); + assertEquals("match_parent", ids.get(1)); + assertEquals("42dip", titles.get(2)); + assertEquals("42dip", ids.get(2)); assertEquals("42dip", choices.getCurrent()); } @@ -169,21 +175,24 @@ public class LinearLayoutRuleTest extends LayoutTestBase { assertNull(node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION)); - List<MenuAction> contextMenu = rule.getContextMenu(node); + List<RuleAction> contextMenu = new ArrayList<RuleAction>(); + rule.addContextMenuActions(contextMenu, node); assertEquals(6, contextMenu.size()); - MenuAction orientationAction = contextMenu.get(5); + RuleAction orientationAction = contextMenu.get(5); + assertEquals("Orientation", orientationAction.getTitle()); assertTrue(orientationAction.getClass().getName(), - orientationAction instanceof MenuAction.Choices); + orientationAction instanceof RuleAction.Choices); - MenuAction.Choices choices = (Choices) orientationAction; + RuleAction.Choices choices = (RuleAction.Choices) orientationAction; IMenuCallback callback = choices.getCallback(); - callback.action(orientationAction, VALUE_VERTICAL, true); + callback.action(orientationAction, Collections.singletonList(node), VALUE_VERTICAL, true); String orientation = node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION); assertEquals(VALUE_VERTICAL, orientation); - callback.action(orientationAction, VALUE_HORIZONTAL, true); + callback.action(orientationAction, Collections.singletonList(node), VALUE_HORIZONTAL, + true); orientation = node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION); assertEquals(VALUE_HORIZONTAL, orientation); } |