aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--eclipse/dictionary.txt1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IMenuCallback.java17
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IViewRule.java23
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/MenuAction.java669
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/RuleAction.java656
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java47
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java418
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java13
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FrameLayoutRule.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java50
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java70
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MergeRule.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertyCallback.java25
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RelativeLayoutRule.java29
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java31
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableRowRule.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java678
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java18
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java70
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java5
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java2
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java28
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LinearLayoutRuleTest.java61
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);
}