diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RelativeLayoutRule.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RelativeLayoutRule.java | 413 |
1 files changed, 413 insertions, 0 deletions
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 new file mode 100644 index 000000000..b4bc86978 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RelativeLayoutRule.java @@ -0,0 +1,413 @@ +/* + * 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_GRAVITY; +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_RESOURCE_PREFIX; +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.VALUE_TRUE; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.api.DropFeedback; +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.IViewRule; +import com.android.ide.common.api.InsertType; +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.SegmentType; +import com.android.ide.common.layout.relative.ConstraintPainter; +import com.android.ide.common.layout.relative.DeletionHandler; +import com.android.ide.common.layout.relative.GuidelinePainter; +import com.android.ide.common.layout.relative.MoveHandler; +import com.android.ide.common.layout.relative.ResizeHandler; +import com.android.utils.Pair; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * An {@link IViewRule} for android.widget.RelativeLayout and all its derived + * classes. + */ +public class RelativeLayoutRule extends BaseLayoutRule { + private static final String ACTION_SHOW_STRUCTURE = "_structure"; //$NON-NLS-1$ + private static final String ACTION_SHOW_CONSTRAINTS = "_constraints"; //$NON-NLS-1$ + private static final String ACTION_CENTER_VERTICAL = "_centerVert"; //$NON-NLS-1$ + private static final String ACTION_CENTER_HORIZONTAL = "_centerHoriz"; //$NON-NLS-1$ + private static final URL ICON_CENTER_VERTICALLY = + RelativeLayoutRule.class.getResource("centerVertically.png"); //$NON-NLS-1$ + private static final URL ICON_CENTER_HORIZONTALLY = + RelativeLayoutRule.class.getResource("centerHorizontally.png"); //$NON-NLS-1$ + private static final URL ICON_SHOW_STRUCTURE = + BaseLayoutRule.class.getResource("structure.png"); //$NON-NLS-1$ + private static final URL ICON_SHOW_CONSTRAINTS = + BaseLayoutRule.class.getResource("constraints.png"); //$NON-NLS-1$ + + public static boolean sShowStructure = false; + public static boolean sShowConstraints = true; + + // ==== Selection ==== + + @Override + public List<String> getSelectionHint(@NonNull INode parentNode, @NonNull INode childNode) { + List<String> infos = new ArrayList<String>(18); + addAttr(ATTR_LAYOUT_ABOVE, childNode, infos); + addAttr(ATTR_LAYOUT_BELOW, childNode, infos); + addAttr(ATTR_LAYOUT_TO_LEFT_OF, childNode, infos); + addAttr(ATTR_LAYOUT_TO_RIGHT_OF, childNode, infos); + addAttr(ATTR_LAYOUT_ALIGN_BASELINE, childNode, infos); + addAttr(ATTR_LAYOUT_ALIGN_TOP, childNode, infos); + addAttr(ATTR_LAYOUT_ALIGN_BOTTOM, childNode, infos); + addAttr(ATTR_LAYOUT_ALIGN_LEFT, childNode, infos); + addAttr(ATTR_LAYOUT_ALIGN_RIGHT, childNode, infos); + addAttr(ATTR_LAYOUT_ALIGN_PARENT_TOP, childNode, infos); + addAttr(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, childNode, infos); + addAttr(ATTR_LAYOUT_ALIGN_PARENT_LEFT, childNode, infos); + addAttr(ATTR_LAYOUT_ALIGN_PARENT_RIGHT, childNode, infos); + addAttr(ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING, childNode, infos); + addAttr(ATTR_LAYOUT_CENTER_HORIZONTAL, childNode, infos); + addAttr(ATTR_LAYOUT_CENTER_IN_PARENT, childNode, infos); + addAttr(ATTR_LAYOUT_CENTER_VERTICAL, childNode, infos); + + return infos; + } + + private void addAttr(String propertyName, INode childNode, List<String> infos) { + String a = childNode.getStringAttr(ANDROID_URI, propertyName); + if (a != null && a.length() > 0) { + // Display the layout parameters without the leading layout_ prefix + // and id references without the @+id/ prefix + if (propertyName.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) { + propertyName = propertyName.substring(ATTR_LAYOUT_RESOURCE_PREFIX.length()); + } + a = stripIdPrefix(a); + String s = propertyName + ": " + a; + infos.add(s); + } + } + + @Override + public void paintSelectionFeedback(@NonNull IGraphics graphics, @NonNull INode parentNode, + @NonNull List<? extends INode> childNodes, @Nullable Object view) { + super.paintSelectionFeedback(graphics, parentNode, childNodes, view); + + boolean showDependents = true; + if (sShowStructure) { + childNodes = Arrays.asList(parentNode.getChildren()); + // Avoid painting twice - both as incoming and outgoing + showDependents = false; + } else if (!sShowConstraints) { + return; + } + + ConstraintPainter.paintSelectionFeedback(graphics, parentNode, childNodes, showDependents); + } + + // ==== Drag'n'drop support ==== + + @Override + public DropFeedback onDropEnter(@NonNull INode targetNode, @Nullable Object targetView, + @Nullable IDragElement[] elements) { + return new DropFeedback(new MoveHandler(targetNode, elements, mRulesEngine), + new GuidelinePainter()); + } + + @Override + public DropFeedback onDropMove(@NonNull INode targetNode, @NonNull IDragElement[] elements, + @Nullable DropFeedback feedback, @NonNull Point p) { + if (elements == null || elements.length == 0 || feedback == null) { + return null; + } + + MoveHandler state = (MoveHandler) feedback.userData; + int offsetX = p.x + (feedback.dragBounds != null ? feedback.dragBounds.x : 0); + int offsetY = p.y + (feedback.dragBounds != null ? feedback.dragBounds.y : 0); + state.updateMove(feedback, elements, offsetX, offsetY, feedback.modifierMask); + + // Or maybe only do this if the results changed... + feedback.requestPaint = true; + + return feedback; + } + + @Override + public void onDropLeave(@NonNull INode targetNode, @NonNull IDragElement[] elements, + @Nullable DropFeedback feedback) { + } + + @Override + public void onDropped(final @NonNull INode targetNode, final @NonNull IDragElement[] elements, + final @Nullable DropFeedback feedback, final @NonNull Point p) { + if (feedback == null) { + return; + } + + final MoveHandler state = (MoveHandler) feedback.userData; + + final Map<String, Pair<String, String>> idMap = getDropIdMap(targetNode, elements, + feedback.isCopy || !feedback.sameCanvas); + + targetNode.editXml("Dropped", new INodeHandler() { + @Override + public void handle(@NonNull INode n) { + int index = -1; + + // Remove cycles + state.removeCycles(); + + // Now write the new elements. + INode previous = null; + for (IDragElement element : elements) { + String fqcn = element.getFqcn(); + + // index==-1 means to insert at the end. + // Otherwise increment the insertion position. + if (index >= 0) { + index++; + } + + INode newChild = targetNode.insertChildAt(fqcn, index); + + // Copy all the attributes, modifying them as needed. + addAttributes(newChild, element, idMap, BaseLayoutRule.DEFAULT_ATTR_FILTER); + addInnerElements(newChild, element, idMap); + + if (previous == null) { + state.applyConstraints(newChild); + previous = newChild; + } else { + // Arrange the nodes next to each other, depending on which + // edge we are attaching to. For example, if attaching to the + // top edge, arrange the subsequent nodes in a column below it. + // + // TODO: Try to do something smarter here where we detect + // constraints between the dragged edges, and we preserve these. + // We have to do this carefully though because if the + // constraints go through some other nodes not part of the + // selection, this doesn't work right, and you might be + // dragging several connected components, which we'd then + // need to stitch together such that they are all visible. + + state.attachPrevious(previous, newChild); + previous = newChild; + } + } + } + }); + } + + @Override + public void onChildInserted(@NonNull INode node, @NonNull INode parent, + @NonNull InsertType insertType) { + // TODO: Handle more generically some way to ensure that widgets with no + // intrinsic size get some minimum size until they are attached on multiple + // opposing sides. + //String fqcn = node.getFqcn(); + //if (fqcn.equals(FQCN_EDIT_TEXT)) { + // node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, "100dp"); //$NON-NLS-1$ + //} + } + + @Override + public void onRemovingChildren(@NonNull List<INode> deleted, @NonNull INode parent, + boolean moved) { + super.onRemovingChildren(deleted, parent, moved); + + if (!moved) { + DeletionHandler handler = new DeletionHandler(deleted, Collections.<INode>emptyList(), + parent); + handler.updateConstraints(); + } + } + + // ==== Resize Support ==== + + @Override + public DropFeedback onResizeBegin(@NonNull INode child, @NonNull INode parent, + @Nullable SegmentType horizontalEdgeType, @Nullable SegmentType verticalEdgeType, + @Nullable Object childView, @Nullable Object parentView) { + ResizeHandler state = new ResizeHandler(parent, child, mRulesEngine, + horizontalEdgeType, verticalEdgeType); + return new DropFeedback(state, new GuidelinePainter()); + } + + @Override + public void onResizeUpdate(@Nullable DropFeedback feedback, @NonNull INode child, + @NonNull INode parent, @NonNull Rect newBounds, + int modifierMask) { + if (feedback == null) { + return; + } + + ResizeHandler state = (ResizeHandler) feedback.userData; + state.updateResize(feedback, child, newBounds, modifierMask); + } + + @Override + public void onResizeEnd(@Nullable DropFeedback feedback, @NonNull INode child, + @NonNull INode parent, final @NonNull Rect newBounds) { + if (feedback == null) { + return; + } + final ResizeHandler state = (ResizeHandler) feedback.userData; + + child.editXml("Resize", new INodeHandler() { + @Override + public void handle(@NonNull INode n) { + state.removeCycles(); + state.applyConstraints(n); + } + }); + } + + // ==== Layout Actions Bar ==== + + @Override + public void addLayoutActions( + @NonNull List<RuleAction> actions, + final @NonNull INode parentNode, + final @NonNull List<? extends INode> children) { + super.addLayoutActions(actions, parentNode, children); + + actions.add(createGravityAction(Collections.<INode>singletonList(parentNode), + ATTR_GRAVITY)); + actions.add(RuleAction.createSeparator(25)); + actions.add(createMarginAction(parentNode, children)); + + IMenuCallback callback = new IMenuCallback() { + @Override + public void action(@NonNull RuleAction action, + @NonNull List<? extends INode> selectedNodes, + final @Nullable String valueId, + final @Nullable Boolean newValue) { + final String id = action.getId(); + if (id.equals(ACTION_CENTER_VERTICAL)|| id.equals(ACTION_CENTER_HORIZONTAL)) { + parentNode.editXml("Center", new INodeHandler() { + @Override + public void handle(@NonNull INode n) { + if (id.equals(ACTION_CENTER_VERTICAL)) { + for (INode child : children) { + centerVertically(child); + } + } else if (id.equals(ACTION_CENTER_HORIZONTAL)) { + for (INode child : children) { + centerHorizontally(child); + } + } + mRulesEngine.redraw(); + } + + }); + } else if (id.equals(ACTION_SHOW_CONSTRAINTS)) { + sShowConstraints = !sShowConstraints; + mRulesEngine.redraw(); + } else { + assert id.equals(ACTION_SHOW_STRUCTURE); + sShowStructure = !sShowStructure; + mRulesEngine.redraw(); + } + } + }; + + // Centering actions + if (children != null && children.size() > 0) { + actions.add(RuleAction.createSeparator(150)); + actions.add(RuleAction.createAction(ACTION_CENTER_VERTICAL, "Center Vertically", + callback, ICON_CENTER_VERTICALLY, 160, false)); + actions.add(RuleAction.createAction(ACTION_CENTER_HORIZONTAL, "Center Horizontally", + callback, ICON_CENTER_HORIZONTALLY, 170, false)); + } + + actions.add(RuleAction.createSeparator(80)); + actions.add(RuleAction.createToggle(ACTION_SHOW_CONSTRAINTS, "Show Constraints", + sShowConstraints, callback, ICON_SHOW_CONSTRAINTS, 180, false)); + actions.add(RuleAction.createToggle(ACTION_SHOW_STRUCTURE, "Show All Relationships", + sShowStructure, callback, ICON_SHOW_STRUCTURE, 190, false)); + } + + private void centerHorizontally(INode node) { + // Clear horizontal-oriented attributes from the node + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_PARENT_LEFT, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_LEFT, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_TO_RIGHT_OF, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_HORIZONTAL, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_PARENT_RIGHT, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_RIGHT, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_TO_LEFT_OF, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_HORIZONTAL, null); + + if (VALUE_TRUE.equals(node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_CENTER_IN_PARENT))) { + // Already done + } else if (VALUE_TRUE.equals(node.getStringAttr(ANDROID_URI, + ATTR_LAYOUT_CENTER_VERTICAL))) { + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_VERTICAL, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_IN_PARENT, VALUE_TRUE); + } else { + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_HORIZONTAL, VALUE_TRUE); + } + } + + private void centerVertically(INode node) { + // Clear vertical-oriented attributes from the node + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_PARENT_TOP, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_TOP, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_BELOW, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_BOTTOM, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ABOVE, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_VERTICAL, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_BASELINE, null); + + // Center vertically + if (VALUE_TRUE.equals(node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_CENTER_IN_PARENT))) { + // ALready done + } else if (VALUE_TRUE.equals(node.getStringAttr(ANDROID_URI, + ATTR_LAYOUT_CENTER_HORIZONTAL))) { + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_HORIZONTAL, null); + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_IN_PARENT, VALUE_TRUE); + } else { + node.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_VERTICAL, VALUE_TRUE); + } + } +} |