diff options
Diffstat (limited to 'eclipse')
35 files changed, 1518 insertions, 531 deletions
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt index 83fcce5..bb8efd9 100644 --- a/eclipse/dictionary.txt +++ b/eclipse/dictionary.txt @@ -195,6 +195,7 @@ textfields thematically themed tmp +toolbar tooltip tooltips traceview diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IClientRulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IClientRulesEngine.java index 4f91552..efd8086 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IClientRulesEngine.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/IClientRulesEngine.java @@ -115,8 +115,23 @@ public interface IClientRulesEngine { * @param currentValue the current reference to select * @param resourceTypeName resource type, such as "id", "string", and so on (never * null) - * @return the resource selected by the user, or null + * @return the margins selected by the user in the same order as the input arguments, + * or null */ String displayResourceInput(String resourceTypeName, String currentValue); + + /** + * Displays an input dialog tailored for editing margin properties. + * + * @param all The current, initial value display for "all" margins (applied to all + * sides) + * @param left The current, initial value to display for the "left" margin + * @param right The current, initial value to display for the "right" margin + * @param top The current, initial value to display for the "top" margin + * @param bottom The current, initial value to display for the "bottom" margin + * @return an array of length 5 containing the user entered values for the all, left, + * right, top and bottom margins respectively + */ + String[] displayMarginInput(String all, String left, String right, String top, String bottom); } 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 fba22ba..3a28896 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 @@ -95,6 +95,20 @@ public interface IViewRule { */ List<MenuAction> getContextMenu(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 + * to display, which makes it easier for parent rules and deriving rules to interleave + * their respective actions. + * + * @param actions the list of actions to add newly registered actions into + * @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, + 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 index e4d7ebf..e54ae36 100755 --- 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 @@ -18,6 +18,9 @@ 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; /** @@ -48,7 +51,7 @@ import java.util.Map; * to adjust your code for the next tools release.</b> * </p> */ -public abstract class MenuAction { +public abstract class MenuAction implements Comparable<MenuAction> { /** * The unique id of the action. @@ -60,6 +63,84 @@ public abstract class MenuAction { */ 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 MenuAction 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 MenuAction 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. @@ -117,6 +198,53 @@ public abstract class MenuAction { } /** + * 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 @@ -223,6 +351,15 @@ public abstract class MenuAction { } } + /** 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. @@ -234,7 +371,7 @@ public abstract class MenuAction { /** * True if the item is displayed with a check mark. */ - final private boolean mIsChecked; + private final boolean mIsChecked; /** * Creates a new immutable toggle action. @@ -293,6 +430,100 @@ public abstract class MenuAction { } /** + * 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; + + /** + * 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; + } + } + + /** 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/> @@ -309,6 +540,7 @@ public abstract class MenuAction { * 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. 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 91110cb..a33594c 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 @@ -18,13 +18,24 @@ package com.android.ide.common.layout; 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.ATTR_LAYOUT_GRAVITY; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN_BOTTOM; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN_LEFT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN_RIGHT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_MARGIN_TOP; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT; +import static com.android.ide.common.layout.LayoutConstants.VALUE_FILL_PARENT; +import static com.android.ide.common.layout.LayoutConstants.VALUE_MATCH_PARENT; +import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT; import com.android.ide.common.api.DropFeedback; import com.android.ide.common.api.IAttributeInfo; -import com.android.ide.common.api.IClientRulesEngine; 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.MenuAction; @@ -32,9 +43,12 @@ import com.android.ide.common.api.Point; import com.android.ide.common.api.Rect; import com.android.ide.common.api.IAttributeInfo.Format; import com.android.ide.common.api.IDragElement.IDragAttribute; +import com.android.ide.common.api.MenuAction.ChoiceProvider; import com.android.util.Pair; +import java.net.URL; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -42,20 +56,141 @@ import java.util.Map; import java.util.Set; 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$ + private static final String ACTION_MARGIN = "_margin"; //$NON-NLS-1$ + private static final URL ICON_MARGINS = + BaseLayoutRule.class.getResource("margins.png"); //$NON-NLS-1$ + private static final URL ICON_GRAVITY = + BaseLayoutRule.class.getResource("gravity.png"); //$NON-NLS-1$ + private static final URL ICON_FILL_WIDTH = + BaseLayoutRule.class.getResource("fillwidth.png"); //$NON-NLS-1$ + private static final URL ICON_FILL_HEIGHT = + BaseLayoutRule.class.getResource("fillheight.png"); //$NON-NLS-1$ + + // ==== Layout Actions support ==== + + // The Margin layout parameters are available for LinearLayout, FrameLayout, RelativeLayout, + // and their subclasses. + protected MenuAction createMarginAction(final INode parentNode, + final List<? extends INode> children) { + + final List<? extends INode> targets = children == null || children.size() == 0 ? + Collections.singletonList(parentNode) + : children; + final INode first = targets.get(0); + + IMenuCallback actionCallback = new IMenuCallback() { + public void action(MenuAction action, final String valueId, final Boolean newValue) { + parentNode.editXml("Change Margins", new INodeHandler() { + public void handle(INode n) { + String uri = ANDROID_URI; + String all = first.getStringAttr(uri, ATTR_LAYOUT_MARGIN); + String left = first.getStringAttr(uri, ATTR_LAYOUT_MARGIN_LEFT); + String right = first.getStringAttr(uri, ATTR_LAYOUT_MARGIN_RIGHT); + String top = first.getStringAttr(uri, ATTR_LAYOUT_MARGIN_TOP); + String bottom = first.getStringAttr(uri, ATTR_LAYOUT_MARGIN_BOTTOM); + String[] margins = mRulesEngine.displayMarginInput(all, left, + right, top, bottom); + if (margins != null) { + assert margins.length == 5; + for (INode child : targets) { + child.setAttribute(uri, ATTR_LAYOUT_MARGIN, margins[0]); + child.setAttribute(uri, ATTR_LAYOUT_MARGIN_LEFT, margins[1]); + child.setAttribute(uri, ATTR_LAYOUT_MARGIN_RIGHT, margins[2]); + child.setAttribute(uri, ATTR_LAYOUT_MARGIN_TOP, margins[3]); + child.setAttribute(uri, ATTR_LAYOUT_MARGIN_BOTTOM, margins[4]); + } + } + } + }); + } + }; - @Override - public boolean onInitialize(String fqcn, IClientRulesEngine engine) { - return super.onInitialize(fqcn, engine); + return MenuAction.createAction(ACTION_MARGIN, "Change Margins...", null, actionCallback, + ICON_MARGINS, 40); } - @Override - public void onDispose() { - super.onDispose(); + // Both LinearLayout and RelativeLayout have a gravity (but RelativeLayout applies it + // to the parent whereas for LinearLayout it's on the children) + protected MenuAction createGravityAction(final List<? extends INode> targets) { + if (targets != null && targets.size() > 0) { + final INode first = targets.get(0); + ChoiceProvider provider = new ChoiceProvider() { + public void addChoices(List<String> titles, List<URL> iconUrls, + List<String> ids) { + IAttributeInfo info = first.getAttributeInfo(ANDROID_URI, ATTR_LAYOUT_GRAVITY); + if (info != null) { + // Generate list of possible gravity value constants + assert IAttributeInfo.Format.FLAG.in(info.getFormats()); + for (String name : info.getFlagValues()) { + titles.add(prettyName(name)); + ids.add(name); + } + } + } + }; + + return MenuAction.createChoices("_gravity", "Change Gravity", //$NON-NLS-1$ + null, + new PropertyCallback(targets, "Change Gravity", ANDROID_URI, + ATTR_LAYOUT_GRAVITY), + provider, + first.getStringAttr(ANDROID_URI, ATTR_LAYOUT_GRAVITY), ICON_GRAVITY, + 43); + } + + return null; } @Override - public List<MenuAction> getContextMenu(INode selectedNode) { - return super.getContextMenu(selectedNode); + public void addLayoutActions(List<MenuAction> actions, final INode parentNode, + final List<? extends INode> children) { + super.addLayoutActions(actions, parentNode, children); + + final List<? extends INode> targets = children == null || children.size() == 0 ? + Collections.singletonList(parentNode) + : children; + final INode first = targets.get(0); + + // Shared action callback + IMenuCallback actionCallback = new IMenuCallback() { + public void action(MenuAction action, final String valueId, final Boolean newValue) { + final String actionId = action.getId(); + final String undoLabel; + if (actionId.equals(ACTION_FILL_WIDTH)) { + undoLabel = "Change Width Fill"; + } else if (actionId.equals(ACTION_FILL_HEIGHT)) { + undoLabel = "Change Height Fill"; + } else { + return; + } + parentNode.editXml(undoLabel, new INodeHandler() { + public void handle(INode n) { + String attribute = actionId.equals(ACTION_FILL_WIDTH) + ? ATTR_LAYOUT_WIDTH : ATTR_LAYOUT_HEIGHT; + String value; + if (newValue) { + if (supportsMatchParent()) { + value = VALUE_MATCH_PARENT; + } else { + value = VALUE_FILL_PARENT; + } + } else { + value = VALUE_WRAP_CONTENT; + } + for (INode child : targets) { + child.setAttribute(ANDROID_URI, attribute, value); + } + } + }); + } + }; + + 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)); } // ==== Paste support ==== 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 1b0d2bd..d21c43d 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 @@ -452,6 +452,15 @@ public class BaseViewRule implements IViewRule { } /** + * Returns true if the given node is "filled" (e.g. has layout width set to match + * parent or fill parent + */ + protected boolean isFilled(INode node, String attribute) { + String value = node.getStringAttr(ANDROID_URI, attribute); + return VALUE_MATCH_PARENT.equals(value) || VALUE_FILL_PARENT.equals(value); + } + + /** * Returns fill_parent or match_parent, depending on whether the minimum supported * platform supports match_parent or not * @@ -534,6 +543,10 @@ public class BaseViewRule implements IViewRule { return null; } + public void addLayoutActions(List<MenuAction> actions, INode parentNode, + List<? extends INode> children) { + } + // ==== Drag'n'drop support ==== // By default Views do not accept drag'n'drop. 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 1054c56..0f8fa18 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 @@ -24,10 +24,12 @@ import com.android.ide.common.api.IGraphics; 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.Point; import com.android.ide.common.api.Rect; import com.android.util.Pair; +import java.util.List; import java.util.Map; /** @@ -144,4 +146,15 @@ public class FrameLayoutRule extends BaseLayoutRule { } }); } + + @Override + public void addLayoutActions(List<MenuAction> actions, final INode parentNode, + final List<? extends INode> children) { + super.addLayoutActions(actions, parentNode, children); + actions.add(MenuAction.createSeparator(25)); + actions.add(createMarginAction(parentNode, children)); + if (children.size() > 0) { + actions.add(createGravityAction(children)); + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java index 9342f3c..ef7bba7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LayoutConstants.java @@ -53,6 +53,14 @@ public class LayoutConstants { public static final String ATTR_LAYOUT_PREFIX = "layout_"; //$NON-NLS-1$ public static final String ATTR_LAYOUT_HEIGHT = "layout_height"; //$NON-NLS-1$ public static final String ATTR_LAYOUT_WIDTH = "layout_width"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_GRAVITY = "layout_gravity"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_WEIGHT = "layout_weight"; //$NON-NLS-1$ + + public static final String ATTR_LAYOUT_MARGIN = "layout_margin"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_MARGIN_LEFT = "layout_marginLeft"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_MARGIN_RIGHT = "layout_marginRight"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_MARGIN_TOP = "layout_marginTop"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_MARGIN_BOTTOM = "layout_marginBottom"; //$NON-NLS-1$ public static final String ATTR_LAYOUT_ALIGN_PARENT_TOP = "layout_alignParentTop"; //$NON-NLS-1$ public static final String ATTR_LAYOUT_ALIGN_PARENT_BOTTOM = "layout_alignParentBottom"; //$NON-NLS-1$ @@ -135,6 +143,8 @@ public class LayoutConstants { // like fill_parent for API 8 public static final String VALUE_MATCH_PARENT = "match_parent"; //$NON-NLS-1$ + public static final String ATTR_WEIGHT_SUM = "weightSum"; //$NON-NLS-1$ + public static final String ATTR_BASELINE_ALIGNED = "baselineAligned"; //$NON-NLS-1$ public static String ATTR_ORIENTATION = "orientation"; //$NON-NLS-1$ public static String VALUE_HORIZONTAL = "horizontal"; //$NON-NLS-1$ 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 c098e00..3efa220 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 @@ -17,9 +17,12 @@ package com.android.ide.common.layout; import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI; +import static com.android.ide.common.layout.LayoutConstants.ATTR_BASELINE_ALIGNED; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT; +import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WEIGHT; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH; import static com.android.ide.common.layout.LayoutConstants.ATTR_ORIENTATION; +import static com.android.ide.common.layout.LayoutConstants.ATTR_WEIGHT_SUM; import static com.android.ide.common.layout.LayoutConstants.VALUE_HORIZONTAL; import static com.android.ide.common.layout.LayoutConstants.VALUE_VERTICAL; @@ -39,7 +42,10 @@ import com.android.ide.common.api.Point; import com.android.ide.common.api.Rect; import com.android.ide.common.api.IViewMetadata.FillPreference; +import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -47,45 +53,60 @@ import java.util.List; * classes. */ public class LinearLayoutRule extends BaseLayoutRule { + private static final String ACTION_ORIENTATION = "_orientation"; //$NON-NLS-1$ + private static final String ACTION_WEIGHT = "_weight"; //$NON-NLS-1$ + private static final String ACTION_DISTRIBUTE = "_distribute"; //$NON-NLS-1$ + private static final String ACTION_BASELINE = "_baseline"; //$NON-NLS-1$ + + private static final URL ICON_HORIZONTAL = + LinearLayoutRule.class.getResource("hlinear.png"); //$NON-NLS-1$ + private static final URL ICON_VERTICAL = + LinearLayoutRule.class.getResource("vlinear.png"); //$NON-NLS-1$ + private static final URL ICON_WEIGHTS = + LinearLayoutRule.class.getResource("weights.png"); //$NON-NLS-1$ + private static final URL ICON_DISTRIBUTE = + LinearLayoutRule.class.getResource("distribute.png"); //$NON-NLS-1$ + private static final URL ICON_BASELINE = + LinearLayoutRule.class.getResource("baseline.png"); //$NON-NLS-1$ + /** * Add an explicit Orientation toggle to the context menu. */ @Override public List<MenuAction> getContextMenu(final INode selectedNode) { if (supportsOrientation()) { - String curr_orient = selectedNode.getStringAttr(ANDROID_URI, ATTR_ORIENTATION); - if (curr_orient == null || curr_orient.length() == 0) { - curr_orient = VALUE_HORIZONTAL; - } - - IMenuCallback onChange = new IMenuCallback() { - public void action(MenuAction action, final String valueId, Boolean newValue) { - String actionId = action.getId(); - final INode node = selectedNode; - - if (actionId.equals("_orientation")) { //$NON-NLS-1$ - node.editXml("Change LinearLayout " + ATTR_ORIENTATION, new INodeHandler() { - public void handle(INode n) { - node.setAttribute(ANDROID_URI, ATTR_ORIENTATION, valueId); - } - }); - } - } - }; - + String current = getCurrentOrientation(selectedNode); + IMenuCallback onChange = new PropertyCallback(Collections.singletonList(selectedNode), + "Change LinearLayout Orientation", + ANDROID_URI, ATTR_ORIENTATION); return concatenate(super.getContextMenu(selectedNode), - new MenuAction.Choices("_orientation", "Orientation", //$NON-NLS-1$ + new MenuAction.Choices(ACTION_ORIENTATION, "Orientation", //$NON-NLS-1$ mapify( "horizontal", "Horizontal", //$NON-NLS-1$ "vertical", "Vertical" //$NON-NLS-1$ ), - curr_orient, onChange)); + current, onChange)); } else { return super.getContextMenu(selectedNode); } } /** + * Returns the current orientation, regardless of whether it has been defined in XML + * + * @param node The LinearLayout to look up the orientation for + * @return "horizontal" or "vertical" depending on the current orientation of the + * linear layout + */ + private String getCurrentOrientation(final INode node) { + String orientation = node.getStringAttr(ANDROID_URI, ATTR_ORIENTATION); + if (orientation == null || orientation.length() == 0) { + orientation = VALUE_HORIZONTAL; + } + return orientation; + } + + /** * Returns true if the given node represents a vertical linear layout. * @param node the node to check layout orientation for * @return true if the layout is in vertical mode, otherwise false @@ -105,6 +126,113 @@ public class LinearLayoutRule extends BaseLayoutRule { return true; } + @Override + public void addLayoutActions(List<MenuAction> actions, final INode parentNode, + final List<? extends INode> children) { + super.addLayoutActions(actions, parentNode, children); + if (supportsOrientation()) { + actions.add(MenuAction.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.<URL>asList(ICON_HORIZONTAL, ICON_VERTICAL), + Arrays.<String>asList("horizontal", "vertical"), + getCurrentOrientation(parentNode), + null /* icon */, + -10 + )); + } + 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", + isAligned, + new PropertyCallback(Collections.singletonList(parentNode), + "Change Baseline Alignment", + ANDROID_URI, ATTR_BASELINE_ALIGNED), // TODO: Also set index? + ICON_BASELINE, 38)); + } + + // Gravity + if (children != null && children.size() > 0) { + actions.add(MenuAction.createSeparator(35)); + + // Margins + actions.add(createMarginAction(parentNode, children)); + + // Gravity + actions.add(createGravityAction(children)); + + // Weights + IMenuCallback actionCallback = new IMenuCallback() { + public void action(final MenuAction action, final String valueId, + final Boolean newValue) { + parentNode.editXml("Change Weight", new INodeHandler() { + public void handle(INode n) { + if (action.getId().equals(ACTION_WEIGHT)) { + String weight = + children.get(0).getStringAttr(ANDROID_URI, ATTR_LAYOUT_WEIGHT); + if (weight == null || weight.length() == 0) { + weight = "0.0"; //$NON-NLS-1$ + } + weight = mRulesEngine.displayInput("Enter Weight Value:", weight, + null); + if (weight != null) { + for (INode child : children) { + child.setAttribute(ANDROID_URI, + ATTR_LAYOUT_WEIGHT, weight); + } + } + } else if (action.getId().equals(ACTION_DISTRIBUTE)) { + // Any XML to get weight sum? + String weightSum = parentNode.getStringAttr(ANDROID_URI, + ATTR_WEIGHT_SUM); + double sum = -1.0; + if (weightSum != null) { + // Distribute + try { + sum = Double.parseDouble(weightSum); + } catch (NumberFormatException nfe) { + // Just keep using the default + } + } + INode[] targets = parentNode.getChildren(); + int numTargets = targets.length; + double share; + if (sum <= 0.0) { + // The sum will be computed from the children, so just + // use arbitrary amount + share = 1.0; + } else { + share = sum / numTargets; + } + String value; + if (share != (int) share) { + value = String.format("%.2f", (float) share); //$NON-NLS-1$ + } else { + value = Integer.toString((int) share); + } + for (INode target : targets) { + target.setAttribute(ANDROID_URI, ATTR_LAYOUT_WEIGHT, value); + } + } else { + assert action.getId().equals(ACTION_BASELINE); + } + } + }); + } + }; + actions.add(MenuAction.createSeparator(50)); + actions.add(MenuAction.createAction(ACTION_DISTRIBUTE, "Distribute Weights Evenly", + null, actionCallback, ICON_DISTRIBUTE, 60)); + actions.add(MenuAction.createAction(ACTION_WEIGHT, "Change Layout Weight", null, + actionCallback, ICON_WEIGHTS, 70)); + } + } + // ==== Drag'n'drop support ==== @Override 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 new file mode 100644 index 0000000..4024d5a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertyCallback.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2011 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.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 java.util.List; + +/** + * Convenience implementation of {@link IMenuCallback} which can be used to set a + * particular property to the new valueId or newValue passed from the {@link IMenuCallback} + */ +public class PropertyCallback implements IMenuCallback { + private final List<? extends INode> mTargetNodes; + private final String mUndoLabel; + private final String mUri; + private final String mAttribute; + + public PropertyCallback(List<? extends INode> targetNodes, String undoLabel, + String uri, String attribute) { + super(); + mTargetNodes = targetNodes; + mUndoLabel = undoLabel; + mUri = uri; + mAttribute = attribute; + } + + // ---- Implements IMenuCallback ---- + public void action(MenuAction action, final String valueId, final Boolean newValue) { + if (mTargetNodes != null && mTargetNodes.size() == 0) { + return; + } + mTargetNodes.get(0).editXml(mUndoLabel, new INodeHandler() { + public void handle(INode n) { + for (INode targetNode : mTargetNodes) { + if (valueId != null) { + targetNode.setAttribute(mUri, mAttribute, valueId); + } else { + assert newValue != null; + targetNode.setAttribute(mUri, mAttribute, Boolean.toString(newValue)); + } + } + } + }); + } +} 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 b80f2ec..b7160ed 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 @@ -46,6 +46,7 @@ import com.android.ide.common.api.IGraphics; 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.Point; import com.android.ide.common.api.Rect; import com.android.ide.common.api.IAttributeInfo.Format; @@ -653,6 +654,16 @@ public class RelativeLayoutRule extends BaseLayoutRule { }); } + @Override + public void addLayoutActions(List<MenuAction> actions, final INode parentNode, + final List<? extends INode> children) { + super.addLayoutActions(actions, parentNode, children); + + actions.add(createGravityAction(Collections.<INode>singletonList(parentNode))); + actions.add(MenuAction.createSeparator(25)); + actions.add(createMarginAction(parentNode, children)); + } + /** * Internal state used by the RelativeLayoutRule, stored as userData in the * {@link DropFeedback}. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/baseline.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/baseline.png Binary files differnew file mode 100644 index 0000000..acb187c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/baseline.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/distribute.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/distribute.png Binary files differnew file mode 100644 index 0000000..eac2340 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/distribute.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/fillheight.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/fillheight.png Binary files differnew file mode 100644 index 0000000..38e137d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/fillheight.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/fillwidth.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/fillwidth.png Binary files differnew file mode 100644 index 0000000..f272aab --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/fillwidth.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/gravity.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/gravity.png Binary files differnew file mode 100644 index 0000000..4f20928 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/gravity.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/hlinear.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/hlinear.png Binary files differnew file mode 100644 index 0000000..b293fe7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/hlinear.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/margins.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/margins.png Binary files differnew file mode 100644 index 0000000..b0d8141 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/margins.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/vlinear.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/vlinear.png Binary files differnew file mode 100644 index 0000000..e03c16e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/vlinear.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/weights.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/weights.png Binary files differnew file mode 100644 index 0000000..cb654a1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/weights.png diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java index 7f70859..9636ec8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java @@ -1360,7 +1360,7 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger { public void stopEditors() { sAndroidLogo.dispose(); - IconFactory.getInstance().Dispose(); + IconFactory.getInstance().dispose(); // Remove the resource listener that handles compiled resources. IWorkspace ws = ResourcesPlugin.getWorkspace(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java index 0c10363..dfdc740 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java @@ -31,7 +31,9 @@ import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import java.net.URL; import java.util.HashMap; /** @@ -40,7 +42,6 @@ import java.util.HashMap; * Icons are kept here and reused. */ public class IconFactory { - public static final int COLOR_RED = SWT.COLOR_DARK_RED; public static final int COLOR_GREEN = SWT.COLOR_DARK_GREEN; public static final int COLOR_BLUE = SWT.COLOR_DARK_BLUE; @@ -53,6 +54,7 @@ public class IconFactory { private static IconFactory sInstance; private HashMap<String, Image> mIconMap = new HashMap<String, Image>(); + private HashMap<URL, Image> mUrlMap = new HashMap<URL, Image>(); private HashMap<String, ImageDescriptor> mImageDescMap = new HashMap<String, ImageDescriptor>(); private IconFactory() { @@ -65,7 +67,7 @@ public class IconFactory { return sInstance; } - public void Dispose() { + public void dispose() { // Dispose icons for (Image icon : mIconMap.values()) { // The map can contain null values @@ -74,6 +76,13 @@ public class IconFactory { } } mIconMap.clear(); + for (Image icon : mUrlMap.values()) { + // The map can contain null values + if (icon != null) { + icon.dispose(); + } + } + mUrlMap.clear(); } /** @@ -147,7 +156,7 @@ public class IconFactory { String key = Character.toString((char) shape) + Integer.toString(color) + osName; ImageDescriptor id = mImageDescMap.get(key); if (id == null && !mImageDescMap.containsKey(key)) { - id = AdtPlugin.imageDescriptorFromPlugin( + id = AbstractUIPlugin.imageDescriptorFromPlugin( AdtPlugin.PLUGIN_ID, String.format("/icons/%1$s.png", osName)); //$NON-NLS-1$ @@ -163,6 +172,23 @@ public class IconFactory { } /** + * Returns the image indicated by the given URL + * + * @param url the url to the image resources + * @return the image for the url, or null if it cannot be initialized + */ + public Image getIcon(URL url) { + Image image = mUrlMap.get(url); + if (image == null) { + ImageDescriptor descriptor = ImageDescriptor.createFromURL(url); + image = descriptor.createImage(); + mUrlMap.put(url, image); + } + + return image; + } + + /** * A simple image description that generates a 16x16 image which consists * of a colored letter inside a black & white circle. */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java index 12289b9..6f521cc 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java @@ -57,7 +57,6 @@ import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; @@ -355,138 +354,6 @@ public class ConfigurationComposite extends Composite { } /** - * Abstract class implemented by the part which owns a {@link ConfigurationComposite} - * to define and handle custom buttons in the button bar. Each button is - * implemented using a button, with a callback when the button is selected. - */ - public static abstract class CustomButton { - - /** The UI label of the toggle. Can be null if the image exists. */ - private final String mUiLabel; - - /** The image to use for this toggle. Can be null if the label exists. */ - private final Image mImage; - - /** The tooltip for the toggle. Can be null. */ - private final String mUiTooltip; - - /** Whether the button is a toggle */ - private final boolean mIsToggle; - /** The default value of the toggle. */ - private final boolean mDefaultValue; - - /** - * the SWT button. - */ - private Button mButton; - - - /** - * Initializes a new {@link CustomButton}. The values set here will be used - * later to create the actual button. - * - * @param uiLabel The UI label of the button. Can be null if the image exists. - * @param image The image to use for this button. Can be null if the label exists. - * @param uiTooltip The tooltip for the button. Can be null. - * @param isToggle Whether the button is a toggle button. - * @param defaultValue The default value of the toggle if <var>isToggle</var> is true. - */ - public CustomButton( - String uiLabel, - Image image, - String uiTooltip, - boolean isToggle, - boolean defaultValue) { - mUiLabel = uiLabel; - mImage = image; - mUiTooltip = uiTooltip; - mIsToggle = isToggle; - mDefaultValue = defaultValue; - } - - /** - * Initializes a new {@link CustomButton} that is <b>not</b> a toggle. - * The values set here will be used later to create the actual button. - * - * @param uiLabel The UI label of the button. Can be null if the image exists. - * @param image The image to use for this button. Can be null if the label exists. - * @param uiTooltip The tooltip for the button. Can be null. - */ - public CustomButton( - String uiLabel, - Image image, - String uiTooltip) { - this(uiLabel, image, uiTooltip, false, false); - } - - - /** - * Called by the {@link ConfigurationComposite} when the button is selected - * @param newState the state of the button if the button is a toggle. - */ - public abstract void onSelected(boolean newState); - - private void createButton(Composite parent) { - int style = SWT.FLAT; - if (mIsToggle) { - style |= SWT.TOGGLE; - } - mButton = new Button(parent, style); - - if (mUiTooltip != null) { - mButton.setToolTipText(mUiTooltip); - } - if (mImage != null) { - mButton.setImage(mImage); - } - if (mUiLabel != null) { - mButton.setText(mUiLabel); - } - - if (mIsToggle && mDefaultValue) { - mButton.setSelection(true); - } - - mButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - onSelected(mButton.getSelection()); - } - }); - } - - public boolean isEnabled() { - return mButton != null && mButton.isEnabled(); - } - - public void setEnabled(boolean enabledState) { - if (mButton != null) { - mButton.setEnabled(enabledState); - } - } - - public void setToolTipText(String text) { - if (mButton != null) { - mButton.setToolTipText(text); - } - } - - public void setSelection(boolean selected) { - if (mButton != null) { - mButton.setSelection(selected); - } - } - - public boolean getSelection() { - if (mButton != null) { - return mButton.getSelection(); - } - - return mDefaultValue; - } - } - - /** * Creates a new {@link ConfigurationComposite} and adds it to the parent. * * The method also receives custom buttons to set into the configuration composite. The list @@ -495,31 +362,25 @@ public class ConfigurationComposite extends Composite { * * @param listener An {@link IConfigListener} that gets and sets configuration properties. * Mandatory, cannot be null. - * @param customButtons An array of array of {@link CustomButton} to define extra action/toggle - * buttons to display at the top of the composite. Can be empty or null. * @param parent The parent composite. * @param style The style of this composite. * @param initialState The initial state (serialized form) to use for the configuration */ public ConfigurationComposite(IConfigListener listener, - CustomButton[][] customButtons, Composite parent, int style, String initialState) { super(parent, style); mListener = listener; mInitialState = initialState; - if (customButtons == null) { - customButtons = new CustomButton[0][0]; - } - GridLayout gl; GridData gd; - int cols = 9; // device+config+locale+dock+day/night+separator*2+theme+apiLevel + int cols = 6; // device+config+separator*2+theme+apiLevel - // ---- First line: custom buttons, clipping button, editing config display. + // ---- First line: locale, day/night, dock, editing config display. Composite labelParent = new Composite(this, SWT.NONE); - labelParent.setLayout(gl = new GridLayout(2 + customButtons.length + 1, false)); + labelParent.setLayout(gl = new GridLayout(6, false)); gl.marginWidth = gl.marginHeight = 0; + gl.marginTop = 3; labelParent.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); gd.horizontalSpan = cols; @@ -528,20 +389,42 @@ public class ConfigurationComposite extends Composite { mCurrentLayoutLabel.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); gd.widthHint = 50; - for (CustomButton[] buttons : customButtons) { - if (buttons.length == 1) { - buttons[0].createButton(labelParent); - } else if (buttons.length > 1) { - Composite buttonParent = new Composite(labelParent, SWT.NONE); - buttonParent.setLayout(gl = new GridLayout(buttons.length, false)); - gl.marginWidth = gl.marginHeight = 0; - gl.horizontalSpacing = 1; - for (CustomButton button : buttons) { - button.createButton(buttonParent); - } + mLocaleCombo = new Combo(labelParent, SWT.DROP_DOWN | SWT.READ_ONLY); + mLocaleCombo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onLocaleChange(); + } + }); + + // Layout bug workaround. Without this, in -some- scenarios the Locale combo box was + // coming up tiny. Setting a minimumWidth hint does not work either. We need to have + // 2 or more items in the locale combo box when the layout is first run. These items + // are removed as part of the locale initialization when the SDK is loaded. + mLocaleCombo.add("Locale"); //$NON-NLS-1$ // Dummy place holders + mLocaleCombo.add("Locale"); //$NON-NLS-1$ + mDockCombo = new Combo(labelParent, SWT.DROP_DOWN | SWT.READ_ONLY); + for (DockMode mode : DockMode.values()) { + mDockCombo.add(mode.getLongDisplayValue()); + } + mDockCombo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onDockChange(); } + }); + + mNightCombo = new Combo(labelParent, SWT.DROP_DOWN | SWT.READ_ONLY); + for (NightMode mode : NightMode.values()) { + mNightCombo.add(mode.getLongDisplayValue()); } + mNightCombo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onDayChange(); + } + }); mCreateButton = new Button(labelParent, SWT.PUSH | SWT.FLAT); mCreateButton.setText("Create..."); @@ -582,42 +465,6 @@ public class ConfigurationComposite extends Composite { } }); - mLocaleCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); - mLocaleCombo.setLayoutData(new GridData( - GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - mLocaleCombo.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - onLocaleChange(); - } - }); - - mDockCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); - mDockCombo.setLayoutData(new GridData( - GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - for (DockMode mode : DockMode.values()) { - mDockCombo.add(mode.getLongDisplayValue()); - } - mDockCombo.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - onDockChange(); - } - }); - - mNightCombo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); - mNightCombo.setLayoutData(new GridData( - GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - for (NightMode mode : NightMode.values()) { - mNightCombo.add(mode.getLongDisplayValue()); - } - mNightCombo.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - onDayChange(); - } - }); - // first separator Label separator = new Label(this, SWT.SEPARATOR | SWT.VERTICAL); separator.setLayoutData(gd = new GridData( @@ -1552,7 +1399,6 @@ public class ConfigurationComposite extends Composite { } } - /** * Returns the current theme, or null if the combo has no selection. * diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java index 831367a..d00908c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java @@ -122,8 +122,10 @@ public class CanvasTransform implements ICanvasTransform { if (sx < cx) { mTranslate = 0; mScrollbar.setEnabled(false); + mScrollbar.setVisible(false); } else { mScrollbar.setEnabled(true); + mScrollbar.setVisible(true); int selection = mScrollbar.getSelection(); int thumb = cx; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java index 8f59710..5506319 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java @@ -48,7 +48,6 @@ import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.C import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener; import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite; import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog; -import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.CustomButton; import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite.IConfigListener; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference; import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; @@ -56,7 +55,6 @@ import com.android.ide.eclipse.adt.internal.editors.ui.DecorComposite; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; import com.android.ide.eclipse.adt.internal.editors.xml.Hyperlinks; -import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration; import com.android.ide.eclipse.adt.internal.resources.configurations.ScreenSizeQualifier; import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; @@ -75,7 +73,6 @@ import com.android.sdklib.SdkConstants; import com.android.sdklib.io.IAbstractFile; import com.android.sdklib.io.StreamException; import com.android.sdklib.xml.AndroidManifest; -import com.android.sdkuilib.internal.widgets.ResolutionChooserDialog; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; @@ -244,15 +241,9 @@ public class GraphicalEditorPart extends EditorPart private boolean mUseExplodeMode; - private CustomButton mZoomRealSizeButton; - private CustomButton mZoomOutButton; - private CustomButton mZoomResetButton; - private CustomButton mZoomInButton; - private CustomButton mZoomFitButton; - private CustomButton mClippingButton; - private int mMinSdkVersion; private int mTargetSdkVersion; + private LayoutActionBar mActionBar; public GraphicalEditorPart(LayoutEditor layoutEditor) { mLayoutEditor = layoutEditor; @@ -299,114 +290,6 @@ public class GraphicalEditorPart extends EditorPart parent.setLayout(gl); gl.marginHeight = gl.marginWidth = 0; - // create the top part for the configuration control - IconFactory iconFactory = IconFactory.getInstance(); - CustomButton[][] customButtons = new CustomButton[][] { - new CustomButton[] { - mZoomRealSizeButton = new CustomButton( - null, // label - iconFactory.getIcon("zoomreal"), //$NON-NLS-1$ - "Emulate Real Size", - true /*isToggle*/, - false /*defaultValue*/ - ) { - @Override - public void onSelected(boolean newState) { - if (rescaleToReal(newState)) { - mZoomOutButton.setEnabled(!newState); - mZoomResetButton.setEnabled(!newState); - mZoomInButton.setEnabled(!newState); - mZoomFitButton.setEnabled(!newState); - } else { - mZoomRealSizeButton.setSelection(!newState); - } - } - }, - mZoomFitButton = new CustomButton( - null, // label - iconFactory.getIcon("zoomfit"), //$NON-NLS-1$ - "Zoom to Fit (0)" - ) { - @Override - public void onSelected(boolean newState) { - rescaleToFit(); - } - }, - mZoomResetButton = new CustomButton( - null, // label - iconFactory.getIcon("zoom100"), //$NON-NLS-1$ - "Reset Zoom to 100% (1)" - ) { - @Override - public void onSelected(boolean newState) { - resetScale(); - } - } - }, - // Group zoom in/out separately - new CustomButton[] { - mZoomOutButton = new CustomButton( - null, // label - iconFactory.getIcon("zoomminus"), //$NON-NLS-1$ - "Zoom Out (-)" - ) { - @Override - public void onSelected(boolean newState) { - rescale(-1); - } - }, - mZoomInButton = new CustomButton( - null, // label - iconFactory.getIcon("zoomplus"), //$NON-NLS-1$ - "Zoom In (+)" - ) { - @Override - public void onSelected(boolean newState) { - rescale(+1); - } - }, - }, - new CustomButton[] { - new CustomButton( - null, //text - iconFactory.getIcon("explode"), //$NON-NLS-1$ - "Displays extra margins in the layout", - true /*toggle*/, - false /*defaultValue*/ - ) { - @Override - public void onSelected(boolean newState) { - mUseExplodeMode = newState; - recomputeLayout(); - } - }, - new CustomButton( - null, //text - iconFactory.getIcon("outline"), //$NON-NLS-1$ - "Shows the outline of all views in the layout", - true /*toggle*/, - false /*defaultValue*/ - ) { - @Override - public void onSelected(boolean newState) { - mCanvasViewer.getCanvas().setShowOutline(newState); - } - }, - mClippingButton = new CustomButton( - null, //text - iconFactory.getIcon("clipping"), //$NON-NLS-1$ - "Toggles screen clipping on/off", - true /*toggle*/, - true /*defaultValue*/ - ) { - @Override - public void onSelected(boolean newState) { - recomputeLayout(); - } - } - } - }; - mConfigListener = new ConfigListener(); // Check whether somebody has requested an initial state for the newly opened file. @@ -433,7 +316,7 @@ public class GraphicalEditorPart extends EditorPart } } - mConfigComposite = new ConfigurationComposite(mConfigListener, customButtons, parent, + mConfigComposite = new ConfigurationComposite(mConfigListener, parent, SWT.BORDER, initialState); mSashPalette = new SashForm(parent, SWT.HORIZONTAL); @@ -443,10 +326,22 @@ public class GraphicalEditorPart extends EditorPart paleteDecor.setContent(new PaletteControl.PaletteDecor(this)); mPalette = (PaletteControl) paleteDecor.getContentControl(); - mSashError = new SashForm(mSashPalette, SWT.VERTICAL | SWT.BORDER); + Composite layoutBarAndCanvas = new Composite(mSashPalette, SWT.NONE); + GridLayout gridLayout = new GridLayout(1, false); + gridLayout.horizontalSpacing = 0; + gridLayout.verticalSpacing = 0; + gridLayout.marginWidth = 0; + gridLayout.marginHeight = 0; + layoutBarAndCanvas.setLayout(gridLayout); + mActionBar = new LayoutActionBar(layoutBarAndCanvas, SWT.NONE, this); + GridData detailsData = new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1); + mActionBar.setLayoutData(detailsData); + + mSashError = new SashForm(layoutBarAndCanvas, SWT.VERTICAL | SWT.BORDER); mSashError.setLayoutData(new GridData(GridData.FILL_BOTH)); mCanvasViewer = new LayoutCanvasViewer(mLayoutEditor, mRulesEngine, mSashError, SWT.NONE); + mSashError.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); mErrorLabel = new StyledText(mSashError, SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL); mErrorLabel.setEditable(false); @@ -466,16 +361,6 @@ public class GraphicalEditorPart extends EditorPart } /** - * Returns true if zooming in/out/to-fit/etc is allowed (which is not the case while - * emulating real size) - * - * @return true if zooming is allowed - */ - boolean isZoomingAllowed() { - return mZoomInButton.isEnabled(); - } - - /** * Listens to workbench selections that does NOT come from {@link LayoutEditor} * (those are generated by ourselves). * <p/> @@ -501,80 +386,6 @@ public class GraphicalEditorPart extends EditorPart } } - /** - * Rescales canvas. - * @param direction +1 for zoom in, -1 for zoom out - */ - void rescale(int direction) { - double s = mCanvasViewer.getCanvas().getScale(); - - if (direction > 0) { - s = s * 1.2; - } else { - s = s / 1.2; - } - - // Some operations are faster if the zoom is EXACTLY 1.0 rather than ALMOST 1.0. - // (This is because there is a fast-path when image copying and the scale is 1.0; - // in that case it does not have to do any scaling). - // - // If you zoom out 10 times and then back in 10 times, small rounding errors mean - // that you end up with a scale=1.0000000000000004. In the cases, when you get close - // to 1.0, just make the zoom an exact 1.0. - if (Math.abs(s-1.0) < 0.0001) { - s = 1.0; - } - - mCanvasViewer.getCanvas().setScale(s, true /*redraw*/); - - } - - /** - * Reset the canvas scale to 100% - */ - private void resetScale() { - mCanvasViewer.getCanvas().setScale(1, true /*redraw*/); - } - - /** - * Reset the canvas scale to best fit (so content is as large as possible without scrollbars) - */ - private void rescaleToFit() { - mCanvasViewer.getCanvas().setFitScale(); - } - - private boolean rescaleToReal(boolean real) { - if (real) { - return computeAndSetRealScale(true /*redraw*/); - } else { - // reset the scale to 100% - mCanvasViewer.getCanvas().setScale(1, true /*redraw*/); - return true; - } - } - - private boolean computeAndSetRealScale(boolean redraw) { - // compute average dpi of X and Y - float dpi = (mConfigComposite.getXDpi() + mConfigComposite.getYDpi()) / 2.f; - - // get the monitor dpi - float monitor = AdtPrefs.getPrefs().getMonitorDensity(); - if (monitor == 0.f) { - ResolutionChooserDialog dialog = new ResolutionChooserDialog( - mConfigComposite.getShell()); - if (dialog.open() == Window.OK) { - monitor = dialog.getDensity(); - AdtPrefs.getPrefs().setMonitorDensity(monitor); - } else { - return false; - } - } - - mCanvasViewer.getCanvas().setScale(monitor / dpi, redraw); - return true; - } - - @Override public void dispose() { getSite().getPage().removeSelectionListener(this); @@ -1138,8 +949,6 @@ public class GraphicalEditorPart extends EditorPart private void updateCapabilities(AndroidTargetData targetData) { if (targetData != null) { LayoutLibrary layoutLib = targetData.getLayoutLibrary(); - setClippingSupport(layoutLib.supports(Capability.UNBOUND_RENDERING)); - if (mIncludedWithin != null && !layoutLib.supports(Capability.EMBEDDED_LAYOUT)) { showIn(null); } @@ -1226,8 +1035,8 @@ public class GraphicalEditorPart extends EditorPart if (layoutLib != null) { // if drawing in real size, (re)set the scaling factor. - if (mZoomRealSizeButton.getSelection()) { - computeAndSetRealScale(false /* redraw */); + if (mActionBar.isZoomingRealSize()) { + mActionBar.computeAndSetRealScale(false /* redraw */); } IProject project = mEditedFile.getProject(); @@ -1315,16 +1124,6 @@ public class GraphicalEditorPart extends EditorPart // --- private methods --- - private void setClippingSupport(boolean b) { - mClippingButton.setEnabled(b); - if (b) { - mClippingButton.setToolTipText("Toggles screen clipping on/off"); - } else { - mClippingButton.setSelection(true); - mClippingButton.setToolTipText("Non clipped rendering is not supported"); - } - } - /** * Ensure that the file associated with this editor is valid (exists and is * synchronized). Any reasons why it is not are displayed in the editor's error area. @@ -1503,15 +1302,11 @@ public class GraphicalEditorPart extends EditorPart RenderLogger logger = new RenderLogger(mEditedFile.getName()); RenderingMode renderingMode = RenderingMode.NORMAL; - if (mClippingButton.getSelection() == false) { - renderingMode = RenderingMode.FULL_EXPAND; - } else { - // FIXME set the rendering mode using ViewRule or something. - List<UiElementNode> children = model.getUiChildren(); - if (children.size() > 0 && - children.get(0).getDescriptor().getXmlLocalName().equals(SCROLL_VIEW)) { - renderingMode = RenderingMode.V_SCROLL; - } + // FIXME set the rendering mode using ViewRule or something. + List<UiElementNode> children = model.getUiChildren(); + if (children.size() > 0 && + children.get(0).getDescriptor().getXmlLocalName().equals(SCROLL_VIEW)) { + renderingMode = RenderingMode.V_SCROLL; } RenderSession session = renderWithBridge(iProject, model, layoutLib, width, height, @@ -2525,4 +2320,8 @@ public class GraphicalEditorPart extends EditorPart public ConfigurationComposite getConfigurationComposite() { return mConfigComposite; } + + public LayoutActionBar getLayoutActionBar() { + return mActionBar; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java index d455fd8..d3349e4 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java @@ -43,7 +43,7 @@ public class ImageControl extends Canvas implements MouseTrackListener { private int mTopMargin; private int mRightMargin; private int mBottomMargin; - private boolean mShowMouseOver; + private boolean mDisposeImage = true; private boolean mMouseIn; private Color mHoverColor; private float mScale = 1.0f; @@ -92,10 +92,20 @@ public class ImageControl extends Canvas implements MouseTrackListener { public void dispose() { super.dispose(); - mImage.dispose(); + if (mDisposeImage) { + mImage.dispose(); + } mImage = null; } + public void setDisposeImage(boolean mDisposeImage) { + this.mDisposeImage = mDisposeImage; + } + + public boolean getDisposeImage() { + return mDisposeImage; + } + @Override public Point computeSize(int wHint, int hHint, boolean changed) { checkWidget(); 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 new file mode 100644 index 0000000..e64004d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java @@ -0,0 +1,461 @@ +/* + * Copyright (C) 2011 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.eclipse.adt.internal.editors.layout.gle2; + +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.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; +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; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Toolbar shown at the top of the layout editor, which adds a number of context-sensitive + * layout actions (as well as zooming controls on the right). + */ +public class LayoutActionBar extends Composite { + private GraphicalEditorPart mEditor; + private ToolBar mLayoutToolBar; + private ToolBar mZoomToolBar; + private ToolItem mZoomRealSizeButton; + private ToolItem mZoomOutButton; + private ToolItem mZoomResetButton; + private ToolItem mZoomInButton; + private ToolItem mZoomFitButton; + + public LayoutActionBar(Composite parent, int style, GraphicalEditorPart editor) { + super(parent, style | SWT.NO_FOCUS); + mEditor = editor; + + GridLayout layout = new GridLayout(2, false); + setLayout(layout); + + mLayoutToolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL); + mLayoutToolBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, true, false)); + mZoomToolBar = createZoomControls(); + mZoomToolBar.setLayoutData(new GridData(SWT.END, SWT.BEGINNING, true, false)); + } + + /** Updates the layout contents based on the current selection */ + void updateSelection() { + // Get rid of any previous children + for (ToolItem c : mLayoutToolBar.getItems()) { + c.dispose(); + } + mLayoutToolBar.pack(); + + NodeProxy parent = null; + LayoutCanvas canvas = mEditor.getCanvasControl(); + SelectionManager selectionManager = canvas.getSelectionManager(); + List<SelectionItem> selections = selectionManager.getSelections(); + if (selections.size() > 0) { + // TODO: better handle multi-selection -- maybe we should disable it or + // something. + // What if you select children with different parents? Of different types? + // etc. + NodeProxy node = selections.get(0).getNode(); + if (node != null && node.getParent() != null) { + parent = (NodeProxy) node.getParent(); + } + } + + if (parent == null) { + // Show the background's properties + CanvasViewInfo root = canvas.getViewHierarchy().getRoot(); + if (root == null) { + return; + } + parent = canvas.getNodeFactory().create(root); + selections = Collections.emptyList(); + } + + RulesEngine engine = mEditor.getRulesEngine(); + List<NodeProxy> selectedNodes = new ArrayList<NodeProxy>(); + for (SelectionItem item : selections) { + selectedNodes.add(item.getNode()); + } + List<MenuAction> actions = new ArrayList<MenuAction>(); + engine.callAddLayoutActions(actions, parent, selectedNodes); + addActions(actions); + + mLayoutToolBar.pack(); + mLayoutToolBar.layout(); + } + + private void addActions(List<MenuAction> actions) { + if (actions.size() > 0) { + // Place actions in the correct order (the actions may come from different + // rules and should be merged properly via sorting keys) + Collections.sort(actions); + + // Flag used to indicate that if there are any actions -after- this, it + // should be separated from this current action (we don't unconditionally + // add a separator at the end of these groups in case there are no more + // actions at the end so that we don't have a trailing separator) + boolean needSeparator = false; + + for (final MenuAction action : actions) { + if (action instanceof Separator) { + addSeparator(mLayoutToolBar); + needSeparator = false; + continue; + } else if (needSeparator) { + addSeparator(mLayoutToolBar); + needSeparator = false; + } + + if (action instanceof MenuAction.OrderedChoices) { + final MenuAction.OrderedChoices choices = (OrderedChoices) action; + final List<URL> icons = choices.getIconUrls(); + if (icons == null || icons.size() == 0) { + addDropdown(choices); + } else { + addSeparator(mLayoutToolBar); + addRadio(choices); + needSeparator = true; + } + } else if (action instanceof MenuAction.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()); + } + } + } + } + + /** Add a separator to the toolbar, unless there already is one there at the end already */ + private static void addSeparator(ToolBar toolBar) { + int n = toolBar.getItemCount(); + if (n > 0 && (toolBar.getItem(n - 1).getStyle() & SWT.SEPARATOR) == 0) { + ToolItem separator = new ToolItem(toolBar, SWT.SEPARATOR); + separator.setWidth(15); + } + } + + private void addToggle(final Toggle toggle) { + final ToolItem button = new ToolItem(mLayoutToolBar, SWT.CHECK); + + URL iconUrl = toggle.getIconUrl(); + String title = toggle.getTitle(); + if (iconUrl != null) { + button.setImage(IconFactory.getInstance().getIcon(iconUrl)); + button.setToolTipText(title); + } else { + button.setText(title); + } + + button.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + toggle.getCallback().action(toggle, toggle.getId(), + button.getSelection()); + } + }); + if (toggle.isChecked()) { + button.setSelection(true); + } + } + + private void addAction(final MenuAction.Action menuAction) { + final ToolItem button = new ToolItem(mLayoutToolBar, SWT.PUSH); + + URL iconUrl = menuAction.getIconUrl(); + String title = menuAction.getTitle(); + if (iconUrl != null) { + button.setImage(IconFactory.getInstance().getIcon(iconUrl)); + button.setToolTipText(title); + } else { + button.setText(title); + } + + button.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + menuAction.getCallback().action(menuAction, menuAction.getId(), false); + } + }); + } + + private void addRadio(final MenuAction.OrderedChoices choices) { + final List<URL> icons = choices.getIconUrls(); + final List<String> titles = choices.getTitles(); + final List<String> ids = choices.getIds(); + final String current = choices.getCurrent() != null ? choices.getCurrent() : ""; //$NON-NLS-1$ + + assert icons != null; + assert icons.size() == titles.size(); + + for (int i = 0; i < icons.size(); i++) { + URL iconUrl = icons.get(i); + String title = titles.get(i); + final String id = ids.get(i); + final ToolItem item = new ToolItem(mLayoutToolBar, SWT.RADIO); + item.setToolTipText(title); + item.setImage(IconFactory.getInstance().getIcon(iconUrl)); + item.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + if (item.getSelection()) { + choices.getCallback().action(choices, id, null); + } + } + }); + boolean selected = current.equals(id); + if (selected) { + item.setSelection(true); + } + } + } + + private void addDropdown(final MenuAction.OrderedChoices choices) { + final List<URL> icons = choices.getIconUrls(); + final List<String> titles = choices.getTitles(); + final List<String> ids = choices.getIds(); + final String current = choices.getCurrent() != null ? choices.getCurrent() : ""; //$NON-NLS-1$ + + assert icons == null || icons.size() == 0; + + final ToolItem combo = new ToolItem(mLayoutToolBar, SWT.DROP_DOWN); + URL iconUrl = choices.getIconUrl(); + if (iconUrl != null) { + combo.setImage(IconFactory.getInstance().getIcon(iconUrl)); + combo.setToolTipText(choices.getTitle()); + } else { + combo.setText(choices.getTitle()); + } + + Listener menuListener = new Listener() { + public void handleEvent(Event event) { + // if (event.detail == SWT.ARROW) { + Point point = new Point(event.x, event.y); + point = combo.getDisplay().map(mLayoutToolBar, null, point); + + final Menu menu = new Menu(mLayoutToolBar.getShell(), SWT.POP_UP); + for (int i = 0; i < titles.size(); i++) { + String title = titles.get(i); + final String id = ids.get(i); + MenuItem item = new MenuItem(menu, SWT.CHECK); + item.setText(title); + + boolean selected = id.equals(current); + if (selected) { + item.setSelection(true); + } + + item.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + choices.getCallback().action(choices, id, null); + } + }); + } + + // TODO - how do I dispose of this? + + menu.setLocation(point); + menu.setVisible(true); + } + }; + combo.addListener(SWT.Selection, menuListener); + } + + // ---- Zoom Controls ---- + + private ToolBar createZoomControls() { + ToolBar toolBar = new ToolBar(this, SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL); + + IconFactory iconFactory = IconFactory.getInstance(); + mZoomRealSizeButton = new ToolItem(toolBar, SWT.CHECK); + mZoomRealSizeButton.setToolTipText("Emulate Real Size"); + mZoomRealSizeButton.setImage(iconFactory.getIcon("zoomreal")); //$NON-NLS-1$); + mZoomRealSizeButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + boolean newState = mZoomRealSizeButton.getSelection(); + if (rescaleToReal(newState)) { + mZoomOutButton.setEnabled(!newState); + mZoomResetButton.setEnabled(!newState); + mZoomInButton.setEnabled(!newState); + mZoomFitButton.setEnabled(!newState); + } else { + mZoomRealSizeButton.setSelection(!newState); + } + } + }); + + mZoomFitButton = new ToolItem(toolBar, SWT.PUSH); + mZoomFitButton.setToolTipText("Zoom to Fit (0)"); + mZoomFitButton.setImage(iconFactory.getIcon("zoomfit")); //$NON-NLS-1$); + mZoomFitButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + rescaleToFit(); + } + }); + + mZoomResetButton = new ToolItem(toolBar, SWT.PUSH); + mZoomResetButton.setToolTipText("Reset Zoom to 100% (1)"); + mZoomResetButton.setImage(iconFactory.getIcon("zoom100")); //$NON-NLS-1$); + mZoomResetButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + resetScale(); + } + }); + + // Group zoom in/out separately + new ToolItem(toolBar, SWT.SEPARATOR); + + mZoomOutButton = new ToolItem(toolBar, SWT.PUSH); + mZoomOutButton.setToolTipText("Zoom Out (-)"); + mZoomOutButton.setImage(iconFactory.getIcon("zoomminus")); //$NON-NLS-1$); + mZoomOutButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + rescale(-1); + } + }); + + mZoomInButton = new ToolItem(toolBar, SWT.PUSH); + mZoomInButton.setToolTipText("Zoom In (+)"); + mZoomInButton.setImage(iconFactory.getIcon("zoomplus")); //$NON-NLS-1$); + mZoomInButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + rescale(+1); + } + }); + + return toolBar; + } + + /** + * Returns true if zooming in/out/to-fit/etc is allowed (which is not the case while + * emulating real size) + * + * @return true if zooming is allowed + */ + boolean isZoomingAllowed() { + return mZoomInButton.isEnabled(); + } + + boolean isZoomingRealSize() { + return mZoomRealSizeButton.getSelection(); + } + + /** + * Rescales canvas. + * @param direction +1 for zoom in, -1 for zoom out + */ + void rescale(int direction) { + LayoutCanvas canvas = mEditor.getCanvasControl(); + double s = canvas.getScale(); + + if (direction > 0) { + s = s * 1.2; + } else { + s = s / 1.2; + } + + // Some operations are faster if the zoom is EXACTLY 1.0 rather than ALMOST 1.0. + // (This is because there is a fast-path when image copying and the scale is 1.0; + // in that case it does not have to do any scaling). + // + // If you zoom out 10 times and then back in 10 times, small rounding errors mean + // that you end up with a scale=1.0000000000000004. In the cases, when you get close + // to 1.0, just make the zoom an exact 1.0. + if (Math.abs(s-1.0) < 0.0001) { + s = 1.0; + } + + canvas.setScale(s, true /*redraw*/); + } + + /** + * Reset the canvas scale to 100% + */ + void resetScale() { + mEditor.getCanvasControl().setScale(1, true /*redraw*/); + } + + /** + * Reset the canvas scale to best fit (so content is as large as possible without scrollbars) + */ + void rescaleToFit() { + mEditor.getCanvasControl().setFitScale(); + } + + boolean rescaleToReal(boolean real) { + if (real) { + return computeAndSetRealScale(true /*redraw*/); + } else { + // reset the scale to 100% + mEditor.getCanvasControl().setScale(1, true /*redraw*/); + return true; + } + } + + boolean computeAndSetRealScale(boolean redraw) { + // compute average dpi of X and Y + ConfigurationComposite config = mEditor.getConfigurationComposite(); + float dpi = (config.getXDpi() + config.getYDpi()) / 2.f; + + // get the monitor dpi + float monitor = AdtPrefs.getPrefs().getMonitorDensity(); + if (monitor == 0.f) { + ResolutionChooserDialog dialog = new ResolutionChooserDialog( + config.getShell()); + if (dialog.open() == Window.OK) { + monitor = dialog.getDensity(); + AdtPrefs.getPrefs().setMonitorDensity(monitor); + } else { + return false; + } + } + + mEditor.getCanvasControl().setScale(monitor / dpi, redraw); + return true; + } +} 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 c7f8919..ece4c2a 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 @@ -270,15 +270,16 @@ public class LayoutCanvas extends Canvas { } else { // Zooming actions char c = e.character; - GraphicalEditorPart editor = mLayoutEditor.getGraphicalEditor(); - if (c == '1' && editor.isZoomingAllowed()) { + LayoutActionBar actionBar = mLayoutEditor.getGraphicalEditor() + .getLayoutActionBar(); + if (c == '1' && actionBar.isZoomingAllowed()) { setScale(1, true); - } else if (c == '0' && editor.isZoomingAllowed()) { + } else if (c == '0' && actionBar.isZoomingAllowed()) { setFitScale(); - } else if (c == '+' && editor.isZoomingAllowed()) { - editor.rescale(1); - } else if (c == '-' && editor.isZoomingAllowed()) { - editor.rescale(-1); + } else if (c == '+' && actionBar.isZoomingAllowed()) { + actionBar.rescale(1); + } else if (c == '-' && actionBar.isZoomingAllowed()) { + actionBar.rescale(-1); } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java index 85490c2..983bcf5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java @@ -21,7 +21,6 @@ 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.gre.RulesEngine; import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode; -import com.android.sdklib.SdkConstants; import org.eclipse.swt.graphics.Rectangle; import org.w3c.dom.Node; @@ -43,9 +42,6 @@ import java.util.List; /** The node proxy for drawing the selection. Null when mCanvasViewInfo is null. */ private final NodeProxy mNodeProxy; - /** The name displayed over the selection, typically the widget class name. Can be null. */ - private final String mName; - /** * Creates a new {@link SelectionItem} object. * @param canvasViewInfo The view info being selected. Must not be null. @@ -68,8 +64,6 @@ import java.util.List; mRect = new Rectangle(r.x, r.y, r.width, r.height); mNodeProxy = nodeFactory.create(canvasViewInfo); } - - mName = initDisplayName(canvasViewInfo, gre); } /** @@ -96,14 +90,6 @@ import java.util.List; return mRect; } - /** - * The name displayed over the selection, typically the widget class name. - * Can be null. - */ - public String getName() { - return mName; - } - /** Returns the node associated with this selection (may be null) */ /* package */ NodeProxy getNode() { return mNodeProxy; @@ -111,51 +97,6 @@ import java.util.List; //---- - private String initDisplayName(CanvasViewInfo canvasViewInfo, RulesEngine gre) { - if (canvasViewInfo == null) { - return null; - } - - String fqcn = canvasViewInfo.getName(); - if (fqcn == null) { - return null; - } - - if (fqcn.equals(SdkConstants.CLASS_MOCK_VIEW)) { - // The MockView class from the layout bridge is used to display views that - // cannot be rendered properly (such as SurfaceView or missing custom views). - // This view itself already displays the class name it represents so we don't - // need to display anything here. - return ""; - } - - String name = gre.callGetDisplayName(canvasViewInfo.getUiViewNode()); - - if (name == null) { - // The name is typically a fully-qualified class name. Let's make it a tad shorter. - - if (fqcn.startsWith("android.")) { //$NON-NLS-1$ - // For android classes, convert android.foo.Name to android...Name - int first = fqcn.indexOf('.'); - int last = fqcn.lastIndexOf('.'); - if (last > first) { - name = fqcn.substring(0, first) + ".." + fqcn.substring(last); //$NON-NLS-1$ - } - } else { - // For custom non-android classes, it's best to keep the 2 first segments of - // the namespace, e.g. we want to get something like com.example...MyClass - int first = fqcn.indexOf('.'); - first = fqcn.indexOf('.', first + 1); - int last = fqcn.lastIndexOf('.'); - if (last > first) { - name = fqcn.substring(0, first) + ".." + fqcn.substring(last); //$NON-NLS-1$ - } - } - } - - return name; - } - /** * Gets the XML text from the given selection for a text transfer. * The returned string can be empty but not null. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java index cad3301..1aad0f2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java @@ -604,6 +604,8 @@ public class SelectionManager implements ISelectionProvider { // Update menu actions that depend on the selection updateMenuActions(); + // Update the layout actions bar + mCanvas.getLayoutEditor().getGraphicalEditor().getLayoutActionBar().updateSelection(); } finally { mInsideUpdateSelection = false; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionOverlay.java index 817f2f9..a2dc88f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionOverlay.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionOverlay.java @@ -62,8 +62,7 @@ public class SelectionOverlay extends Overlay { NodeProxy node = s.getNode(); if (node != null) { - String name = s.getName(); - paintSelection(gcWrapper, s.getViewInfo(), node, name, isMultipleSelection); + paintSelection(gcWrapper, s.getViewInfo(), node, isMultipleSelection); } } @@ -121,7 +120,7 @@ public class SelectionOverlay extends Overlay { } /** Called by the canvas when a view is being selected. */ - private void paintSelection(IGraphics gc, CanvasViewInfo view, INode selectedNode, String displayName, + private void paintSelection(IGraphics gc, CanvasViewInfo view, INode selectedNode, boolean isMultipleSelection) { Rect r = selectedNode.getBounds(); 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 751def1..cde4e28 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 @@ -42,6 +42,7 @@ import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.internal.sdk.ProjectState; import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.ide.eclipse.adt.internal.ui.MarginChooser; import com.android.ide.eclipse.adt.internal.ui.ReferenceChooserDialog; import com.android.ide.eclipse.adt.internal.ui.ResourceChooser; import com.android.resources.ResourceType; @@ -236,6 +237,35 @@ public class RulesEngine { } /** + * Invokes {@link IViewRule#getContextMenu(INode)} 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}. + */ + public List<MenuAction> callAddLayoutActions(List<MenuAction> actions, + NodeProxy parentNode, List<NodeProxy> children ) { + // try to find a rule for this element's FQCN + IViewRule rule = loadRule(parentNode.getNode()); + + if (rule != null) { + try { + mInsertType = InsertType.CREATE; + rule.addLayoutActions(actions, parentNode, children); + } catch (Exception e) { + AdtPlugin.log(e, "%s.getContextMenu() failed: %s", + rule.getClass().getSimpleName(), + e.toString()); + } + } + + return null; + } + + /** * Invokes {@link IViewRule#getSelectionHint(INode, INode)} * on the rule matching the specified element. * @@ -872,5 +902,24 @@ public class RulesEngine { return null; } + public String[] displayMarginInput(String all, String left, String right, String top, + String bottom) { + AndroidXmlEditor editor = mEditor.getLayoutEditor(); + IProject project = editor.getProject(); + if (project != null) { + Shell shell = AdtPlugin.getDisplay().getActiveShell(); + if (shell == null) { + return null; + } + AndroidTargetData data = editor.getTargetData(); + MarginChooser dialog = new MarginChooser(shell, project, data, all, left, right, + top, bottom); + if (dialog.open() == Window.OK) { + return dialog.getMargins(); + } + } + + return null; + } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/AndroidJUnitLaunchConfigurationTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/AndroidJUnitLaunchConfigurationTab.java index d51cd8f..e5957d7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/AndroidJUnitLaunchConfigurationTab.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/junit/AndroidJUnitLaunchConfigurationTab.java @@ -71,7 +71,6 @@ import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; @@ -249,14 +248,13 @@ public class AndroidJUnitLaunchConfigurationTab extends AbstractLaunchConfigurat GridData gd = new GridData(); gd.horizontalSpan = 3; mTestContainerRadioButton.setLayoutData(gd); - mTestContainerRadioButton.addSelectionListener(new SelectionListener() { + mTestContainerRadioButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { if (mTestContainerRadioButton.getSelection()) { testModeChanged(); } } - public void widgetDefaultSelected(SelectionEvent e) { - } }); mContainerText = new Text(comp, SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java new file mode 100644 index 0000000..1a9a78f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2011 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.eclipse.adt.internal.ui; + +import com.android.ide.eclipse.adt.internal.resources.IResourceRepository; +import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; +import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; +import com.android.resources.ResourceType; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.dialogs.SelectionStatusDialog; + +public class MarginChooser extends SelectionStatusDialog implements Listener { + private IProject mProject; + private AndroidTargetData mTargetData; + private Text mLeftField; + private Text mRightField; + private Text mTopField; + private Text mBottomField; + private Text mAllField; + private String mInitialAll; + private String mInitialLeft; + private String mInitialRight; + private String mInitialTop; + private String mInitialBottom; + private Label mErrorLabel; + private String[] mMargins; + + // Client data key for resource buttons pointing to the associated text field + private final static String PROP_TEXTFIELD = "textField"; //$NON-NLS-1$ + + public MarginChooser(Shell parent, IProject project, AndroidTargetData targetData, String all, + String left, String right, String top, String bottom) { + super(parent); + setTitle("Edit Margins"); + mProject = project; + mTargetData = targetData; + mInitialAll = all; + mInitialLeft = left; + mInitialRight = right; + mInitialTop = top; + mInitialBottom = bottom; + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite container = new Composite(parent, SWT.NONE); + container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); + + container.setLayout(new GridLayout(3, false)); + + Label allLabel = new Label(container, SWT.NONE); + allLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + allLabel.setText("All:"); + + mAllField = new Text(container, SWT.BORDER | SWT.LEFT); + mAllField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mAllField.setText(mInitialAll != null ? mInitialAll : ""); //$NON-NLS-1$ + + Button allButton = new Button(container, SWT.NONE); + allButton.setText("Resource..."); + allButton.setData(PROP_TEXTFIELD, mAllField); + + Label label = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL); + label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 3, 1)); + + Label leftLabel = new Label(container, SWT.NONE); + leftLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + leftLabel.setText("Left:"); + + mLeftField = new Text(container, SWT.BORDER | SWT.LEFT); + mLeftField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mLeftField.setText(mInitialLeft != null ? mInitialLeft : ""); //$NON-NLS-1$ + + Button leftButton = new Button(container, SWT.NONE); + leftButton.setText("Resource..."); + leftButton.setData(PROP_TEXTFIELD, mLeftField); + + Label rightLabel = new Label(container, SWT.NONE); + rightLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + rightLabel.setText("Right:"); + + mRightField = new Text(container, SWT.BORDER | SWT.LEFT); + mRightField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mRightField.setText(mInitialRight != null ? mInitialRight : ""); //$NON-NLS-1$ + + Button rightButton = new Button(container, SWT.NONE); + rightButton.setText("Resource..."); + rightButton.setData(PROP_TEXTFIELD, mRightField); + + Label topLabel = new Label(container, SWT.NONE); + topLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + topLabel.setText("Top:"); + + mTopField = new Text(container, SWT.BORDER | SWT.LEFT); + mTopField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mTopField.setText(mInitialTop != null ? mInitialTop : ""); //$NON-NLS-1$ + + Button topButton = new Button(container, SWT.NONE); + topButton.setText("Resource..."); + topButton.setData(PROP_TEXTFIELD, mTopField); + + Label bottomLabel = new Label(container, SWT.NONE); + bottomLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + bottomLabel.setText("Bottom:"); + + mBottomField = new Text(container, SWT.BORDER | SWT.LEFT); + mBottomField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mBottomField.setText(mInitialBottom != null ? mInitialBottom : ""); //$NON-NLS-1$ + + Button bottomButton = new Button(container, SWT.NONE); + bottomButton.setText("Resource..."); + bottomButton.setData(PROP_TEXTFIELD, mBottomField); + + allButton.addListener(SWT.Selection, this); + leftButton.addListener(SWT.Selection, this); + rightButton.addListener(SWT.Selection, this); + topButton.addListener(SWT.Selection, this); + bottomButton.addListener(SWT.Selection, this); + + mAllField.addListener(SWT.Modify, this); + mLeftField.addListener(SWT.Modify, this); + mRightField.addListener(SWT.Modify, this); + mTopField.addListener(SWT.Modify, this); + mBottomField.addListener(SWT.Modify, this); + + new Label(container, SWT.NONE); + mErrorLabel = new Label(container, SWT.WRAP); + mErrorLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + + return container; + } + + @Override + protected void computeResult() { + mMargins = new String[] { + mAllField.getText().trim(), + mLeftField.getText().trim(), mRightField.getText().trim(), + mTopField.getText().trim(), mBottomField.getText().trim() + }; + } + + public String[] getMargins() { + return mMargins; + } + + public void handleEvent(Event event) { + if (event.type == SWT.Modify) { + // Text field modification -- warn about non-dip numbers + if (event.widget instanceof Text) { + Text text = (Text) event.widget; + String input = text.getText().trim(); + boolean isNumber = false; + try { + if (Integer.parseInt(input) > 0) { + isNumber = true; + } + } catch (NumberFormatException nufe) { + // Users are allowed to enter non-numbers here, not an error + } + if (isNumber) { + String message = String.format("Hint: Use \"%1$sdip\" instead", input); + mErrorLabel.setText(message); + } else { + mErrorLabel.setText(""); + } + } + } else if (event.type == SWT.Selection) { + // Button pressed - open resource chooser + if (event.widget instanceof Button) { + Button button = (Button) event.widget; + + // Open a resource chooser dialog for specified resource type. + IResourceRepository projectRepository = ResourceManager.getInstance() + .getProjectResources(mProject); + IResourceRepository systemRepository = mTargetData.getSystemResources(); + ResourceChooser dlg = new ResourceChooser(mProject, ResourceType.DIMEN, + projectRepository, systemRepository, getShell()); + Text text = (Text) button.getData(PROP_TEXTFIELD); + dlg.setCurrentResource(text.getText().trim()); + if (dlg.open() == Window.OK) { + text.setText(dlg.getCurrentResource()); + } + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LayoutTestBase.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LayoutTestBase.java index 21e4597..5b79ac3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LayoutTestBase.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/LayoutTestBase.java @@ -238,6 +238,12 @@ public abstract class LayoutTestBase extends TestCase { fail("Not supported in tests yet"); return null; } + + public String[] displayMarginInput(String all, String left, String right, String top, + String bottom) { + fail("Not supported in tests yet"); + return null; + } } public void testDummy() { |