aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RelativeLayoutRule.java
diff options
context:
space:
mode:
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.java413
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);
+ }
+ }
+}