aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java878
1 files changed, 0 insertions, 878 deletions
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
deleted file mode 100644
index df2c8f473..000000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseLayoutRule.java
+++ /dev/null
@@ -1,878 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.common.layout;
-
-import static com.android.SdkConstants.ANDROID_URI;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_LAYOUT_ABOVE;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BASELINE;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BOTTOM;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_BOTTOM;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_TOP;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_TOP;
-import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING;
-import static com.android.SdkConstants.ATTR_LAYOUT_BELOW;
-import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_HORIZONTAL;
-import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_IN_PARENT;
-import static com.android.SdkConstants.ATTR_LAYOUT_CENTER_VERTICAL;
-import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN;
-import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN_SPAN;
-import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY;
-import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_BOTTOM;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_LEFT;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_RIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN_TOP;
-import static com.android.SdkConstants.ATTR_LAYOUT_ROW;
-import static com.android.SdkConstants.ATTR_LAYOUT_ROW_SPAN;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF;
-import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
-import static com.android.SdkConstants.ATTR_LAYOUT_X;
-import static com.android.SdkConstants.ATTR_LAYOUT_Y;
-import static com.android.SdkConstants.VALUE_FILL_PARENT;
-import static com.android.SdkConstants.VALUE_MATCH_PARENT;
-import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
-
-import com.android.SdkConstants;
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.api.DrawingStyle;
-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.IDragElement.IDragAttribute;
-import com.android.ide.common.api.IFeedbackPainter;
-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.IViewRule;
-import com.android.ide.common.api.MarginType;
-import com.android.ide.common.api.Point;
-import com.android.ide.common.api.Rect;
-import com.android.ide.common.api.RuleAction;
-import com.android.ide.common.api.RuleAction.ChoiceProvider;
-import com.android.ide.common.api.Segment;
-import com.android.ide.common.api.SegmentType;
-import com.android.utils.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;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A {@link IViewRule} for all layouts.
- */
-public class BaseLayoutRule extends BaseViewRule {
- private static final String ACTION_FILL_WIDTH = "_fillW"; //$NON-NLS-1$
- private static final String ACTION_FILL_HEIGHT = "_fillH"; //$NON-NLS-1$
- 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 final RuleAction 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() {
- @Override
- public void action(@NonNull RuleAction action,
- @NonNull List<? extends INode> selectedNodes,
- final @Nullable String valueId,
- final @Nullable Boolean newValue) {
- parentNode.editXml("Change Margins", new INodeHandler() {
- @Override
- public void handle(@NonNull 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]);
- }
- }
- }
- });
- }
- };
-
- return RuleAction.createAction(ACTION_MARGIN, "Change Margins...", actionCallback,
- ICON_MARGINS, 40, false);
- }
-
- // Both LinearLayout and RelativeLayout have a gravity (but RelativeLayout applies it
- // to the parent whereas for LinearLayout it's on the children)
- protected final RuleAction createGravityAction(final List<? extends INode> targets, final
- String attributeName) {
- if (targets != null && targets.size() > 0) {
- final INode first = targets.get(0);
- ChoiceProvider provider = new ChoiceProvider() {
- @Override
- public void addChoices(@NonNull List<String> titles, @NonNull List<URL> iconUrls,
- @NonNull List<String> ids) {
- IAttributeInfo info = first.getAttributeInfo(ANDROID_URI, attributeName);
- if (info != null) {
- // Generate list of possible gravity value constants
- assert info.getFormats().contains(IAttributeInfo.Format.FLAG);
- for (String name : info.getFlagValues()) {
- titles.add(getAttributeDisplayName(name));
- ids.add(name);
- }
- }
- }
- };
-
- return RuleAction.createChoices("_gravity", "Change Gravity", //$NON-NLS-1$
- new PropertyCallback(targets, "Change Gravity", ANDROID_URI,
- attributeName),
- provider,
- first.getStringAttr(ANDROID_URI, attributeName), ICON_GRAVITY,
- 43, false);
- }
-
- return null;
- }
-
- @Override
- public void addLayoutActions(
- @NonNull List<RuleAction> actions,
- final @NonNull INode parentNode,
- final @NonNull 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() {
- @Override
- public void action(
- @NonNull RuleAction action,
- @NonNull List<? extends INode> selectedNodes,
- final @Nullable String valueId,
- final @Nullable 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() {
- @Override
- public void handle(@NonNull 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(RuleAction.createToggle(ACTION_FILL_WIDTH, "Toggle Fill Width",
- isFilled(first, ATTR_LAYOUT_WIDTH), actionCallback, ICON_FILL_WIDTH, 10, false));
- actions.add(RuleAction.createToggle(ACTION_FILL_HEIGHT, "Toggle Fill Height",
- isFilled(first, ATTR_LAYOUT_HEIGHT), actionCallback, ICON_FILL_HEIGHT, 20, false));
- }
-
- // ==== Paste support ====
-
- /**
- * The default behavior for pasting in a layout is to simulate a drop in the
- * top-left corner of the view.
- * <p/>
- * Note that we explicitly do not call super() here -- the BaseViewRule.onPaste handler
- * will call onPasteBeforeChild() instead.
- * <p/>
- * Derived layouts should override this behavior if not appropriate.
- */
- @Override
- public void onPaste(@NonNull INode targetNode, @Nullable Object targetView,
- @NonNull IDragElement[] elements) {
- DropFeedback feedback = onDropEnter(targetNode, targetView, elements);
- if (feedback != null) {
- Point p = targetNode.getBounds().getTopLeft();
- feedback = onDropMove(targetNode, elements, feedback, p);
- if (feedback != null) {
- onDropLeave(targetNode, elements, feedback);
- onDropped(targetNode, elements, feedback, p);
- }
- }
- }
-
- /**
- * The default behavior for pasting in a layout with a specific child target
- * is to simulate a drop right above the top left of the given child target.
- * <p/>
- * This method is invoked by BaseView when onPaste() is called --
- * views don't generally accept children and instead use the target node as
- * a hint to paste "before" it.
- *
- * @param parentNode the parent node we're pasting into
- * @param parentView the view object for the parent layout, or null
- * @param targetNode the first selected node
- * @param elements the elements being pasted
- */
- public void onPasteBeforeChild(INode parentNode, Object parentView, INode targetNode,
- IDragElement[] elements) {
- DropFeedback feedback = onDropEnter(parentNode, parentView, elements);
- if (feedback != null) {
- Point parentP = parentNode.getBounds().getTopLeft();
- Point targetP = targetNode.getBounds().getTopLeft();
- if (parentP.y < targetP.y) {
- targetP.y -= 1;
- }
-
- feedback = onDropMove(parentNode, elements, feedback, targetP);
- if (feedback != null) {
- onDropLeave(parentNode, elements, feedback);
- onDropped(parentNode, elements, feedback, targetP);
- }
- }
- }
-
- // ==== Utility methods used by derived layouts ====
-
- /**
- * Draws the bounds of the given elements and all its children elements in the canvas
- * with the specified offset.
- *
- * @param gc the graphics context
- * @param element the element to be drawn
- * @param offsetX a horizontal delta to add to the current bounds of the element when
- * drawing it
- * @param offsetY a vertical delta to add to the current bounds of the element when
- * drawing it
- */
- public void drawElement(IGraphics gc, IDragElement element, int offsetX, int offsetY) {
- Rect b = element.getBounds();
- if (b.isValid()) {
- gc.drawRect(b.x + offsetX, b.y + offsetY, b.x + offsetX + b.w, b.y + offsetY + b.h);
- }
-
- for (IDragElement inner : element.getInnerElements()) {
- drawElement(gc, inner, offsetX, offsetY);
- }
- }
-
- /**
- * Collect all the "android:id" IDs from the dropped elements. When moving
- * objects within the same canvas, that's all there is to do. However if the
- * objects are moved to a different canvas or are copied then set
- * createNewIds to true to find the existing IDs under targetNode and create
- * a map with new non-conflicting unique IDs as needed. Returns a map String
- * old-id => tuple (String new-id, String fqcn) where fqcn is the FQCN of
- * the element.
- */
- protected static Map<String, Pair<String, String>> getDropIdMap(INode targetNode,
- IDragElement[] elements, boolean createNewIds) {
- Map<String, Pair<String, String>> idMap = new HashMap<String, Pair<String, String>>();
-
- if (createNewIds) {
- collectIds(idMap, elements);
- // Need to remap ids if necessary
- idMap = remapIds(targetNode, idMap);
- }
-
- return idMap;
- }
-
- /**
- * Fills idMap with a map String id => tuple (String id, String fqcn) where
- * fqcn is the FQCN of the element (in case we want to generate new IDs
- * based on the element type.)
- *
- * @see #getDropIdMap
- */
- protected static Map<String, Pair<String, String>> collectIds(
- Map<String, Pair<String, String>> idMap,
- IDragElement[] elements) {
- for (IDragElement element : elements) {
- IDragAttribute attr = element.getAttribute(ANDROID_URI, ATTR_ID);
- if (attr != null) {
- String id = attr.getValue();
- if (id != null && id.length() > 0) {
- idMap.put(id, Pair.of(id, element.getFqcn()));
- }
- }
-
- collectIds(idMap, element.getInnerElements());
- }
-
- return idMap;
- }
-
- /**
- * Used by #getDropIdMap to find new IDs in case of conflict.
- */
- protected static Map<String, Pair<String, String>> remapIds(INode node,
- Map<String, Pair<String, String>> idMap) {
- // Visit the document to get a list of existing ids
- Set<String> existingIdSet = new HashSet<String>();
- collectExistingIds(node.getRoot(), existingIdSet);
-
- Map<String, Pair<String, String>> new_map = new HashMap<String, Pair<String, String>>();
- for (Map.Entry<String, Pair<String, String>> entry : idMap.entrySet()) {
- String key = entry.getKey();
- Pair<String, String> value = entry.getValue();
-
- String id = normalizeId(key);
-
- if (!existingIdSet.contains(id)) {
- // Not a conflict. Use as-is.
- new_map.put(key, value);
- if (!key.equals(id)) {
- new_map.put(id, value);
- }
- } else {
- // There is a conflict. Get a new id.
- String new_id = findNewId(value.getSecond(), existingIdSet);
- value = Pair.of(new_id, value.getSecond());
- new_map.put(id, value);
- new_map.put(id.replaceFirst("@\\+", "@"), value); //$NON-NLS-1$ //$NON-NLS-2$
- }
- }
-
- return new_map;
- }
-
- /**
- * Used by #remapIds to find a new ID for a conflicting element.
- */
- protected static String findNewId(String fqcn, Set<String> existingIdSet) {
- // Get the last component of the FQCN (e.g. "android.view.Button" =>
- // "Button")
- String name = fqcn.substring(fqcn.lastIndexOf('.') + 1);
-
- for (int i = 1; i < 1000000; i++) {
- String id = String.format("@+id/%s%02d", name, i); //$NON-NLS-1$
- if (!existingIdSet.contains(id)) {
- existingIdSet.add(id);
- return id;
- }
- }
-
- // We'll never reach here.
- return null;
- }
-
- /**
- * Used by #getDropIdMap to find existing IDs recursively.
- */
- protected static void collectExistingIds(INode root, Set<String> existingIdSet) {
- if (root == null) {
- return;
- }
-
- String id = root.getStringAttr(ANDROID_URI, ATTR_ID);
- if (id != null) {
- id = normalizeId(id);
-
- if (!existingIdSet.contains(id)) {
- existingIdSet.add(id);
- }
- }
-
- for (INode child : root.getChildren()) {
- collectExistingIds(child, existingIdSet);
- }
- }
-
- /**
- * Transforms @id/name into @+id/name to treat both forms the same way.
- */
- protected static String normalizeId(String id) {
- if (id.indexOf("@+") == -1) { //$NON-NLS-1$
- id = id.replaceFirst("@", "@+"); //$NON-NLS-1$ //$NON-NLS-2$
- }
- return id;
- }
-
- /**
- * For use by {@link BaseLayoutRule#addAttributes} A filter should return a
- * valid replacement string.
- */
- protected static interface AttributeFilter {
- String replace(String attributeUri, String attributeName, String attributeValue);
- }
-
- private static final String[] EXCLUDED_ATTRIBUTES = new String[] {
- // Common
- ATTR_LAYOUT_GRAVITY,
-
- // from AbsoluteLayout
- ATTR_LAYOUT_X,
- ATTR_LAYOUT_Y,
-
- // from RelativeLayout
- ATTR_LAYOUT_ABOVE,
- ATTR_LAYOUT_BELOW,
- ATTR_LAYOUT_TO_LEFT_OF,
- ATTR_LAYOUT_TO_RIGHT_OF,
- ATTR_LAYOUT_ALIGN_BASELINE,
- ATTR_LAYOUT_ALIGN_TOP,
- ATTR_LAYOUT_ALIGN_BOTTOM,
- ATTR_LAYOUT_ALIGN_LEFT,
- ATTR_LAYOUT_ALIGN_RIGHT,
- ATTR_LAYOUT_ALIGN_PARENT_TOP,
- ATTR_LAYOUT_ALIGN_PARENT_BOTTOM,
- ATTR_LAYOUT_ALIGN_PARENT_LEFT,
- ATTR_LAYOUT_ALIGN_PARENT_RIGHT,
- ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING,
- ATTR_LAYOUT_CENTER_HORIZONTAL,
- ATTR_LAYOUT_CENTER_IN_PARENT,
- ATTR_LAYOUT_CENTER_VERTICAL,
-
- // From GridLayout
- ATTR_LAYOUT_ROW,
- ATTR_LAYOUT_ROW_SPAN,
- ATTR_LAYOUT_COLUMN,
- ATTR_LAYOUT_COLUMN_SPAN
- };
-
- /**
- * Default attribute filter used by the various layouts to filter out some properties
- * we don't want to offer.
- */
- public static final AttributeFilter DEFAULT_ATTR_FILTER = new AttributeFilter() {
- Set<String> mExcludes;
-
- @Override
- public String replace(String uri, String name, String value) {
- if (!ANDROID_URI.equals(uri)) {
- return value;
- }
-
- if (mExcludes == null) {
- mExcludes = new HashSet<String>(EXCLUDED_ATTRIBUTES.length);
- mExcludes.addAll(Arrays.asList(EXCLUDED_ATTRIBUTES));
- }
-
- return mExcludes.contains(name) ? null : value;
- }
- };
-
- /**
- * Copies all the attributes from oldElement to newNode. Uses the idMap to
- * transform the value of all attributes of Format.REFERENCE. If filter is
- * non-null, it's a filter that can rewrite the attribute string.
- */
- protected static void addAttributes(INode newNode, IDragElement oldElement,
- Map<String, Pair<String, String>> idMap, AttributeFilter filter) {
-
- for (IDragAttribute attr : oldElement.getAttributes()) {
- String uri = attr.getUri();
- String name = attr.getName();
- String value = attr.getValue();
-
- IAttributeInfo attrInfo = newNode.getAttributeInfo(uri, name);
- if (attrInfo != null) {
- if (attrInfo.getFormats().contains(IAttributeInfo.Format.REFERENCE)) {
- if (idMap.containsKey(value)) {
- value = idMap.get(value).getFirst();
- }
- }
- }
-
- if (filter != null) {
- value = filter.replace(uri, name, value);
- }
- if (value != null && value.length() > 0) {
- newNode.setAttribute(uri, name, value);
- }
- }
- }
-
- /**
- * Adds all the children elements of oldElement to newNode, recursively.
- * Attributes are adjusted by calling addAttributes with idMap as necessary,
- * with no closure filter.
- */
- protected static void addInnerElements(INode newNode, IDragElement oldElement,
- Map<String, Pair<String, String>> idMap) {
-
- for (IDragElement element : oldElement.getInnerElements()) {
- String fqcn = element.getFqcn();
- INode childNode = newNode.appendChild(fqcn);
-
- addAttributes(childNode, element, idMap, null /* filter */);
- addInnerElements(childNode, element, idMap);
- }
- }
-
- /**
- * Insert the given elements into the given node at the given position
- *
- * @param targetNode the node to insert into
- * @param elements the elements to insert
- * @param createNewIds if true, generate new ids when there is a conflict
- * @param initialInsertPos index among targetnode's children which to insert the
- * children
- */
- public static void insertAt(final INode targetNode, final IDragElement[] elements,
- final boolean createNewIds, final int initialInsertPos) {
-
- // Collect IDs from dropped elements and remap them to new IDs
- // if this is a copy or from a different canvas.
- final Map<String, Pair<String, String>> idMap = getDropIdMap(targetNode, elements,
- createNewIds);
-
- targetNode.editXml("Insert Elements", new INodeHandler() {
-
- @Override
- public void handle(@NonNull INode node) {
- // Now write the new elements.
- int insertPos = initialInsertPos;
- for (IDragElement element : elements) {
- String fqcn = element.getFqcn();
-
- INode newChild = targetNode.insertChildAt(fqcn, insertPos);
-
- // insertPos==-1 means to insert at the end. Otherwise
- // increment the insertion position.
- if (insertPos >= 0) {
- insertPos++;
- }
-
- // Copy all the attributes, modifying them as needed.
- addAttributes(newChild, element, idMap, DEFAULT_ATTR_FILTER);
- addInnerElements(newChild, element, idMap);
- }
- }
- });
- }
-
- // ---- Resizing ----
-
- /** Creates a new {@link ResizeState} object to track resize state */
- protected ResizeState createResizeState(INode layout, Object layoutView, INode node) {
- return new ResizeState(this, layout, layoutView, node);
- }
-
- @Override
- public DropFeedback onResizeBegin(@NonNull INode child, @NonNull INode parent,
- @Nullable SegmentType horizontalEdge, @Nullable SegmentType verticalEdge,
- @Nullable Object childView, @Nullable Object parentView) {
- ResizeState state = createResizeState(parent, parentView, child);
- state.horizontalEdgeType = horizontalEdge;
- state.verticalEdgeType = verticalEdge;
-
- // Compute preferred (wrap_content) size such that we can offer guidelines to
- // snap to the preferred size
- Map<INode, Rect> sizes = mRulesEngine.measureChildren(parent,
- new IClientRulesEngine.AttributeFilter() {
- @Override
- public String getAttribute(@NonNull INode node, @Nullable String namespace,
- @NonNull String localName) {
- // Change attributes to wrap_content
- if (ATTR_LAYOUT_WIDTH.equals(localName)
- && SdkConstants.NS_RESOURCES.equals(namespace)) {
- return VALUE_WRAP_CONTENT;
- }
- if (ATTR_LAYOUT_HEIGHT.equals(localName)
- && SdkConstants.NS_RESOURCES.equals(namespace)) {
- return VALUE_WRAP_CONTENT;
- }
-
- return null;
- }
- });
- if (sizes != null) {
- state.wrapBounds = sizes.get(child);
- }
-
- return new DropFeedback(state, new IFeedbackPainter() {
- @Override
- public void paint(@NonNull IGraphics gc, @NonNull INode node,
- @NonNull DropFeedback feedback) {
- ResizeState resizeState = (ResizeState) feedback.userData;
- if (resizeState != null && resizeState.bounds != null) {
- paintResizeFeedback(gc, node, resizeState);
- }
- }
- });
- }
-
- protected void paintResizeFeedback(IGraphics gc, INode node, ResizeState resizeState) {
- gc.useStyle(DrawingStyle.RESIZE_PREVIEW);
- Rect b = resizeState.bounds;
- gc.drawRect(b);
-
- if (resizeState.horizontalFillSegment != null) {
- gc.useStyle(DrawingStyle.GUIDELINE);
- Segment s = resizeState.horizontalFillSegment;
- gc.drawLine(s.from, s.at, s.to, s.at);
- }
- if (resizeState.verticalFillSegment != null) {
- gc.useStyle(DrawingStyle.GUIDELINE);
- Segment s = resizeState.verticalFillSegment;
- gc.drawLine(s.at, s.from, s.at, s.to);
- }
-
- if (resizeState.wrapBounds != null) {
- gc.useStyle(DrawingStyle.GUIDELINE);
- int wrapWidth = resizeState.wrapBounds.w;
- int wrapHeight = resizeState.wrapBounds.h;
-
- // Show the "wrap_content" guideline.
- // If we are showing both the wrap_width and wrap_height lines
- // then we show at most the rectangle formed by the two lines;
- // otherwise we show the entire width of the line
- if (resizeState.horizontalEdgeType != null) {
- int y = -1;
- switch (resizeState.horizontalEdgeType) {
- case TOP:
- y = b.y + b.h - wrapHeight;
- break;
- case BOTTOM:
- y = b.y + wrapHeight;
- break;
- default: assert false : resizeState.horizontalEdgeType;
- }
- if (resizeState.verticalEdgeType != null) {
- switch (resizeState.verticalEdgeType) {
- case LEFT:
- gc.drawLine(b.x + b.w - wrapWidth, y, b.x + b.w, y);
- break;
- case RIGHT:
- gc.drawLine(b.x, y, b.x + wrapWidth, y);
- break;
- default: assert false : resizeState.verticalEdgeType;
- }
- } else {
- gc.drawLine(b.x, y, b.x + b.w, y);
- }
- }
- if (resizeState.verticalEdgeType != null) {
- int x = -1;
- switch (resizeState.verticalEdgeType) {
- case LEFT:
- x = b.x + b.w - wrapWidth;
- break;
- case RIGHT:
- x = b.x + wrapWidth;
- break;
- default: assert false : resizeState.verticalEdgeType;
- }
- if (resizeState.horizontalEdgeType != null) {
- switch (resizeState.horizontalEdgeType) {
- case TOP:
- gc.drawLine(x, b.y + b.h - wrapHeight, x, b.y + b.h);
- break;
- case BOTTOM:
- gc.drawLine(x, b.y, x, b.y + wrapHeight);
- break;
- default: assert false : resizeState.horizontalEdgeType;
- }
- } else {
- gc.drawLine(x, b.y, x, b.y + b.h);
- }
- }
- }
- }
-
- /**
- * Returns the maximum number of pixels will be considered a "match" when snapping
- * resize or move positions to edges or other constraints
- *
- * @return the maximum number of pixels to consider for snapping
- */
- public static final int getMaxMatchDistance() {
- // TODO - make constant once we're happy with the feel
- return 20;
- }
-
- @Override
- public void onResizeUpdate(@Nullable DropFeedback feedback, @NonNull INode child,
- @NonNull INode parent, @NonNull Rect newBounds, int modifierMask) {
- ResizeState state = (ResizeState) feedback.userData;
- state.bounds = newBounds;
- state.modifierMask = modifierMask;
-
- // Match on wrap bounds
- state.wrapWidth = state.wrapHeight = false;
- if (state.wrapBounds != null) {
- Rect b = state.wrapBounds;
- int maxMatchDistance = getMaxMatchDistance();
- if (state.horizontalEdgeType != null) {
- if (Math.abs(newBounds.h - b.h) < maxMatchDistance) {
- state.wrapHeight = true;
- if (state.horizontalEdgeType == SegmentType.TOP) {
- newBounds.y += newBounds.h - b.h;
- }
- newBounds.h = b.h;
- }
- }
- if (state.verticalEdgeType != null) {
- if (Math.abs(newBounds.w - b.w) < maxMatchDistance) {
- state.wrapWidth = true;
- if (state.verticalEdgeType == SegmentType.LEFT) {
- newBounds.x += newBounds.w - b.w;
- }
- newBounds.w = b.w;
- }
- }
- }
-
- // Match on fill bounds
- state.horizontalFillSegment = null;
- state.fillHeight = false;
- if (state.horizontalEdgeType == SegmentType.BOTTOM && !state.wrapHeight) {
- Rect parentBounds = parent.getBounds();
- state.horizontalFillSegment = new Segment(parentBounds.y2(), newBounds.x,
- newBounds.x2(),
- null /*node*/, null /*id*/, SegmentType.BOTTOM, MarginType.NO_MARGIN);
- if (Math.abs(newBounds.y2() - parentBounds.y2()) < getMaxMatchDistance()) {
- state.fillHeight = true;
- newBounds.h = parentBounds.y2() - newBounds.y;
- }
- }
- state.verticalFillSegment = null;
- state.fillWidth = false;
- if (state.verticalEdgeType == SegmentType.RIGHT && !state.wrapWidth) {
- Rect parentBounds = parent.getBounds();
- state.verticalFillSegment = new Segment(parentBounds.x2(), newBounds.y,
- newBounds.y2(),
- null /*node*/, null /*id*/, SegmentType.RIGHT, MarginType.NO_MARGIN);
- if (Math.abs(newBounds.x2() - parentBounds.x2()) < getMaxMatchDistance()) {
- state.fillWidth = true;
- newBounds.w = parentBounds.x2() - newBounds.x;
- }
- }
-
- feedback.tooltip = getResizeUpdateMessage(state, child, parent,
- newBounds, state.horizontalEdgeType, state.verticalEdgeType);
- }
-
- @Override
- public void onResizeEnd(@Nullable DropFeedback feedback, @NonNull INode child,
- final @NonNull INode parent, final @NonNull Rect newBounds) {
- final Rect oldBounds = child.getBounds();
- if (oldBounds.w != newBounds.w || oldBounds.h != newBounds.h) {
- final ResizeState state = (ResizeState) feedback.userData;
- child.editXml("Resize", new INodeHandler() {
- @Override
- public void handle(@NonNull INode n) {
- setNewSizeBounds(state, n, parent, oldBounds, newBounds,
- state.horizontalEdgeType, state.verticalEdgeType);
- }
- });
- }
- }
-
- /**
- * Returns the message to display to the user during the resize operation
- *
- * @param resizeState the current resize state
- * @param child the child node being resized
- * @param parent the parent of the resized node
- * @param newBounds the new bounds to resize the child to, in pixels
- * @param horizontalEdge the horizontal edge being resized
- * @param verticalEdge the vertical edge being resized
- * @return the message to display for the current resize bounds
- */
- protected String getResizeUpdateMessage(ResizeState resizeState, INode child, INode parent,
- Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) {
- String width = resizeState.getWidthAttribute();
- String height = resizeState.getHeightAttribute();
-
- if (horizontalEdge == null) {
- return width;
- } else if (verticalEdge == null) {
- return height;
- } else {
- // U+00D7: Unicode for multiplication sign
- return String.format("%s \u00D7 %s", width, height);
- }
- }
-
- /**
- * Performs the edit on the node to complete a resizing operation. The actual edit
- * part is pulled out such that subclasses can change/add to the edits and be part of
- * the same undo event
- *
- * @param resizeState the current resize state
- * @param node the child node being resized
- * @param layout the parent of the resized node
- * @param newBounds the new bounds to resize the child to, in pixels
- * @param horizontalEdge the horizontal edge being resized
- * @param verticalEdge the vertical edge being resized
- */
- protected void setNewSizeBounds(ResizeState resizeState, INode node, INode layout,
- Rect oldBounds, Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) {
- if (verticalEdge != null
- && (newBounds.w != oldBounds.w || resizeState.wrapWidth || resizeState.fillWidth)) {
- node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, resizeState.getWidthAttribute());
- }
- if (horizontalEdge != null
- && (newBounds.h != oldBounds.h || resizeState.wrapHeight || resizeState.fillHeight)) {
- node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, resizeState.getHeightAttribute());
- }
- }
-}