diff options
12 files changed, 165 insertions, 36 deletions
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt index 853cb3c..86f5e2f 100644 --- a/eclipse/dictionary.txt +++ b/eclipse/dictionary.txt @@ -152,6 +152,7 @@ num ok os palette +param params pings placeholder 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 b696332..eb3f7e7 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 @@ -210,5 +210,18 @@ public interface IClientRulesEngine { */ String getAttribute(INode node, String namespace, String localName); } + + /** + * Given a UI root node and a potential XML node name, returns the first available id + * that matches the pattern "prefix%d". + * <p/> + * TabWidget is a special case and the method will always return "@android:id/tabs". + * + * @param fqcn The fully qualified class name of the view to generate a unique id for + * @return A suitable generated id in the attribute form needed by the XML id tag + * (e.g. "@+id/something") + */ + public String getUniqueId(String fqcn); + } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/Segment.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/Segment.java index 845f82d..0fb961a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/Segment.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/api/Segment.java @@ -45,7 +45,10 @@ public class Segment { /** The node that contains this edge */ public final INode node; - /** The id of the node */ + /** + * The id of the node. May be null (in which case id should be generated when + * move/resize is completed + */ public final String id; public Segment(int at, int from, int to, INode node, String id, SegmentType edgeType, 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 5b3334e..c332649 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 @@ -187,6 +187,7 @@ public class RelativeLayoutRule extends BaseLayoutRule { state.removeCycles(); // Now write the new elements. + INode previous = null; for (IDragElement element : elements) { String fqcn = element.getFqcn(); @@ -202,7 +203,25 @@ public class RelativeLayoutRule extends BaseLayoutRule { addAttributes(newChild, element, idMap, BaseLayoutRule.DEFAULT_ATTR_FILTER); addInnerElements(newChild, element, idMap); - state.applyConstraints(newChild); + 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; + } } } }); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DependencyGraph.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DependencyGraph.java index a0039fb..0b1d9e6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DependencyGraph.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DependencyGraph.java @@ -21,8 +21,8 @@ import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_PREFIX; import static com.android.ide.common.layout.LayoutConstants.VALUE_TRUE; import com.android.ide.common.api.IDragElement; -import com.android.ide.common.api.INode; import com.android.ide.common.api.IDragElement.IDragAttribute; +import com.android.ide.common.api.INode; import com.android.ide.common.api.INode.IAttribute; import com.android.ide.common.layout.BaseLayoutRule; @@ -150,8 +150,8 @@ class DependencyGraph { * * @param nodes the set of nodes that we want to compute the transitive dependencies * for - * @param vertical if true, look for vertical dependencies, otherwise look for - * horizontal dependencies + * @param vertical if true, look for vertical edge dependencies, otherwise look for + * horizontal edge dependencies * @return the set of nodes that directly or indirectly depend on the given nodes in * the given direction */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelineHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelineHandler.java index 8faf364..7c6e1d2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelineHandler.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelineHandler.java @@ -24,6 +24,7 @@ import static com.android.ide.common.api.SegmentType.RIGHT; import static com.android.ide.common.api.SegmentType.TOP; import static com.android.ide.common.layout.BaseLayoutRule.getMaxMatchDistance; 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_ABOVE; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_BASELINE; import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ALIGN_BOTTOM; @@ -109,13 +110,15 @@ public class GuidelineHandler { /** * The set of nodes which depend on the currently selected nodes, including - * transitively, through horizontal constraints. + * transitively, through horizontal constraints (a "horizontal constraint" + * is a constraint between two horizontal edges) */ protected Set<INode> mHorizontalDeps; /** * The set of nodes which depend on the currently selected nodes, including - * transitively, through vertical constraints. + * transitively, through vertical constraints (a "vertical constraint" + * is a constraint between two vertical edges) */ protected Set<INode> mVerticalDeps; @@ -468,7 +471,7 @@ public class GuidelineHandler { continue; } - Match match = new Match(edge, draggedEdge, type, delta); + Match match = new Match(this, edge, draggedEdge, type, delta); if (distance < closestDistance) { closest.clear(); @@ -492,6 +495,8 @@ public class GuidelineHandler { /** * Given a node, apply the suggestions by expressing them as relative layout param * values + * + * @param n the node to apply constraints to */ public void applyConstraints(INode n) { // Process each edge separately @@ -548,25 +553,25 @@ public class GuidelineHandler { } if (mMoveTop && mCurrentTopMatch != null) { - applyConstraint(n, mCurrentTopMatch.getConstraint()); + applyConstraint(n, mCurrentTopMatch.getConstraint(true /* generateId */)); if (mCurrentTopMatch.type == ALIGN_BASELINE) { // HACK! WORKAROUND! Baseline doesn't provide a new bottom edge for attachments - String c = mCurrentTopMatch.getConstraint(); + String c = mCurrentTopMatch.getConstraint(true); c = c.replace(ATTR_LAYOUT_ALIGN_BASELINE, ATTR_LAYOUT_ALIGN_BOTTOM); applyConstraint(n, c); } } if (mMoveBottom && mCurrentBottomMatch != null) { - applyConstraint(n, mCurrentBottomMatch.getConstraint()); + applyConstraint(n, mCurrentBottomMatch.getConstraint(true)); } if (mMoveLeft && mCurrentLeftMatch != null) { - applyConstraint(n, mCurrentLeftMatch.getConstraint()); + applyConstraint(n, mCurrentLeftMatch.getConstraint(true)); } if (mMoveRight && mCurrentRightMatch != null) { - applyConstraint(n, mCurrentRightMatch.getConstraint()); + applyConstraint(n, mCurrentRightMatch.getConstraint(true)); } if (mMoveLeft) { @@ -588,7 +593,6 @@ public class GuidelineHandler { String name = constraint.substring(0, constraint.indexOf('=')); String value = constraint.substring(constraint.indexOf('=') + 1); n.setAttribute(ANDROID_URI, name, value); - } private void applyMargin(INode n, String marginAttribute, int margin) { @@ -601,6 +605,58 @@ public class GuidelineHandler { } } + private void removeRelativeParams(INode node) { + for (ConstraintType type : ConstraintType.values()) { + node.setAttribute(ANDROID_URI, type.name, null); + } + node.setAttribute(ANDROID_URI,ATTR_LAYOUT_MARGIN_LEFT, null); + node.setAttribute(ANDROID_URI,ATTR_LAYOUT_MARGIN_RIGHT, null); + node.setAttribute(ANDROID_URI,ATTR_LAYOUT_MARGIN_TOP, null); + node.setAttribute(ANDROID_URI,ATTR_LAYOUT_MARGIN_BOTTOM, null); + } + + /** + * Attach the new child to the previous node + * @param previous the previous child + * @param node the new child to attach it to + */ + public void attachPrevious(INode previous, INode node) { + removeRelativeParams(node); + + String id = previous.getStringAttr(ANDROID_URI, ATTR_ID); + if (id == null) { + return; + } + + if (mCurrentTopMatch != null || mCurrentBottomMatch != null) { + // Attaching the top: arrange below, and for bottom arrange above + node.setAttribute(ANDROID_URI, + mCurrentTopMatch != null ? ATTR_LAYOUT_BELOW : ATTR_LAYOUT_ABOVE, id); + // Apply same left/right constraints as the parent + if (mCurrentLeftMatch != null) { + applyConstraint(node, mCurrentLeftMatch.getConstraint(true)); + applyMargin(node, ATTR_LAYOUT_MARGIN_LEFT, mLeftMargin); + } else if (mCurrentRightMatch != null) { + applyConstraint(node, mCurrentRightMatch.getConstraint(true)); + applyMargin(node, ATTR_LAYOUT_MARGIN_RIGHT, mRightMargin); + } + } else if (mCurrentLeftMatch != null || mCurrentRightMatch != null) { + node.setAttribute(ANDROID_URI, + mCurrentLeftMatch != null ? ATTR_LAYOUT_TO_RIGHT_OF : ATTR_LAYOUT_TO_LEFT_OF, + id); + // Apply same top/bottom constraints as the parent + if (mCurrentTopMatch != null) { + applyConstraint(node, mCurrentTopMatch.getConstraint(true)); + applyMargin(node, ATTR_LAYOUT_MARGIN_TOP, mTopMargin); + } else if (mCurrentBottomMatch != null) { + applyConstraint(node, mCurrentBottomMatch.getConstraint(true)); + applyMargin(node, ATTR_LAYOUT_MARGIN_BOTTOM, mBottomMargin); + } + } else { + return; + } + } + public void removeCycles() { if (mHorizontalCycle != null) { removeCycles(mHorizontalDeps); @@ -762,4 +818,13 @@ public class GuidelineHandler { return 0; } } + + /** + * Returns the {@link IClientRulesEngine} IDE callback + * + * @return the {@link IClientRulesEngine} IDE callback, never null + */ + public IClientRulesEngine getRulesEngine() { + return mRulesEngine; + } }
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelinePainter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelinePainter.java index 158a792..be0fb09 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelinePainter.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelinePainter.java @@ -130,7 +130,7 @@ public final class GuidelinePainter implements IFeedbackPainter { // Display the constraint. Remove the @id/ and @+id/ prefixes to make the text // shorter and easier to read. This doesn't use stripPrefix() because the id is // usually not a prefix of the value (for example, 'layout_alignBottom=@+id/foo'). - String constraint = m.getConstraint(); + String constraint = m.getConstraint(false /* generateId */); String description = constraint.replace(NEW_ID_PREFIX, "").replace(ID_PREFIX, ""); if (description.startsWith(ATTR_LAYOUT_PREFIX)) { description = description.substring(ATTR_LAYOUT_PREFIX.length()); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/Match.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/Match.java index a341113..98d02e8 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/Match.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/Match.java @@ -15,6 +15,8 @@ */ package com.android.ide.common.layout.relative; +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.VALUE_TRUE; import com.android.ide.common.api.Segment; @@ -36,15 +38,22 @@ class Match { /** whether this {@link Match} results in a cycle */ public boolean cycle; + /** The associated {@link GuidelineHander} which performed the match */ + private final GuidelineHandler mHandler; + /** * Create a new match. * + * @param handler the handler which performed the match * @param edge the "other" edge that the dragged edge is matched with * @param with the edge of the dragged node that is matched * @param type the type of constraint this is a match for * @param delta the signed distance between the matched edges */ - public Match(Segment edge, Segment with, ConstraintType type, int delta) { + public Match(GuidelineHandler handler, Segment edge, Segment with, + ConstraintType type, int delta) { + mHandler = handler; + this.edge = edge; this.with = with; this.type = type; @@ -54,13 +63,29 @@ class Match { /** * Returns the XML constraint attribute value for this match * + * @param generateId whether an id should be generated if one is missing * @return the XML constraint attribute value for this match */ - public String getConstraint() { + public String getConstraint(boolean generateId) { if (type.targetParent) { return type.name + '=' + VALUE_TRUE; } else { String id = edge.id; + if (id == null || id.length() == -1) { + if (!generateId) { + // Placeholder to display for the user during dragging + id = "<generated>"; + } else { + // Must generate an id on the fly! + // See if it's been set by a different constraint we've already applied + // to this same node + id = edge.node.getStringAttr(ANDROID_URI, ATTR_ID); + if (id == null || id.length() == 0) { + id = mHandler.getRulesEngine().getUniqueId(edge.node.getFqcn()); + edge.node.setAttribute(ANDROID_URI, ATTR_ID, id); + } + } + } return type.name + '=' + id; } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/MoveHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/MoveHandler.java index c7d25b4..a2f9ac2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/MoveHandler.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/MoveHandler.java @@ -63,8 +63,8 @@ public class MoveHandler extends GuidelineHandler { } mDraggedNodes = nodes; - mHorizontalDeps = mDependencyGraph.dependsOn(nodes, false /* vertical */); - mVerticalDeps = mDependencyGraph.dependsOn(nodes, true /* vertical */); + mHorizontalDeps = mDependencyGraph.dependsOn(nodes, false /* verticalEdge */); + mVerticalDeps = mDependencyGraph.dependsOn(nodes, true /* verticalEdge */); for (INode child : layout.getChildren()) { Rect bc = child.getBounds(); @@ -81,12 +81,9 @@ public class MoveHandler extends GuidelineHandler { } if (!isDragged) { - // Need an id to reference child in attachments to it, so skip - // nodes without ids String id = child.getStringAttr(ANDROID_URI, ATTR_ID); - if (id == null) { - continue; - } + // It's okay for id to be null; if you apply a constraint + // to a node with a missing id we will generate the id boolean addHorizontal = !mHorizontalDeps.contains(child); boolean addVertical = !mVerticalDeps.contains(child); @@ -233,7 +230,7 @@ public class MoveHandler extends GuidelineHandler { Match match = pickBestMatch(mHorizontalSuggestions); if (match != null) { - if (mVerticalDeps.contains(match.edge.node)) { + if (mHorizontalDeps.contains(match.edge.node)) { match.cycle = true; } @@ -259,7 +256,7 @@ public class MoveHandler extends GuidelineHandler { match = pickBestMatch(mVerticalSuggestions); if (match != null) { - if (mHorizontalDeps.contains(match.edge.node)) { + if (mVerticalDeps.contains(match.edge.node)) { match.cycle = true; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ResizeHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ResizeHandler.java index 870798d..6cf421a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ResizeHandler.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ResizeHandler.java @@ -84,14 +84,7 @@ public class ResizeHandler extends GuidelineHandler { for (INode child : layout.getChildren()) { if (child != resized) { - // Need an id to reference child in attachments to it, so skip nodes - // without ids - // TODO: Generate an id on the fly when needed (at commit time) instead! String id = child.getStringAttr(ANDROID_URI, ATTR_ID); - if (id == null) { - continue; - } - addBounds(child, id, !mHorizontalDeps.contains(child), !mVerticalDeps.contains(child)); @@ -208,7 +201,7 @@ public class ResizeHandler extends GuidelineHandler { Match match = pickBestMatch(mHorizontalSuggestions); if (match != null && (!mSnap || Math.abs(match.delta) < BaseLayoutRule.getMaxMatchDistance())) { - if (mVerticalDeps.contains(match.edge.node)) { + if (mHorizontalDeps.contains(match.edge.node)) { match.cycle = true; } @@ -232,7 +225,7 @@ public class ResizeHandler extends GuidelineHandler { Match match = pickBestMatch(mVerticalSuggestions); if (match != null && (!mSnap || Math.abs(match.delta) < BaseLayoutRule.getMaxMatchDistance())) { - if (mHorizontalDeps.contains(match.edge.node)) { + if (mVerticalDeps.contains(match.edge.node)) { match.cycle = true; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java index 5f60911..e23f05c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java @@ -29,21 +29,23 @@ import com.android.ide.common.resources.ResourceRepository; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.internal.actions.AddCompatibilityJarAction; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils; import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService; import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionManager; import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo; +import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; import com.android.ide.eclipse.adt.internal.resources.CyclicDependencyValidator; 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.Sdk; import com.android.ide.eclipse.adt.internal.ui.MarginChooser; -import com.android.ide.eclipse.adt.internal.ui.ResourcePreviewHelper; import com.android.ide.eclipse.adt.internal.ui.ReferenceChooserDialog; import com.android.ide.eclipse.adt.internal.ui.ResourceChooser; +import com.android.ide.eclipse.adt.internal.ui.ResourcePreviewHelper; import com.android.resources.ResourceType; import com.android.sdklib.IAndroidTarget; @@ -481,4 +483,10 @@ class ClientRulesEngine implements IClientRulesEngine { } } + public String getUniqueId(String fqcn) { + UiDocumentNode root = mRulesEngine.getEditor().getModel(); + String prefix = fqcn.substring(fqcn.lastIndexOf('.') + 1); + prefix = Character.toLowerCase(prefix.charAt(0)) + prefix.substring(1); + return DescriptorsUtils.getFreeWidgetId(root, prefix); + } }
\ No newline at end of file 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 b59f2f9..b29424e 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 @@ -276,6 +276,11 @@ public class LayoutTestBase extends TestCase { fail("Not supported in tests yet"); return px; } + + public String getUniqueId(String prefix) { + fail("Not supported in tests yet"); + return null; + } } public void testDummy() { |