diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout')
77 files changed, 0 insertions, 13850 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AbsListViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AbsListViewRule.java deleted file mode 100644 index cd1b0fcae..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AbsListViewRule.java +++ /dev/null @@ -1,28 +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 com.android.ide.common.api.IViewRule; - -/** - * An {@link IViewRule} for android.widget.AbsListViewRule - */ -public class AbsListViewRule extends IgnoredLayoutRule { - - // GridViews and ListViews are not configurable via XML - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AbsoluteLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AbsoluteLayoutRule.java deleted file mode 100644 index 3ec3b5f1a..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AbsoluteLayoutRule.java +++ /dev/null @@ -1,254 +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_LAYOUT_X; -import static com.android.SdkConstants.ATTR_LAYOUT_Y; -import static com.android.SdkConstants.VALUE_N_DP; - -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.IDragElement; -import com.android.ide.common.api.IFeedbackPainter; -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.Point; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.SegmentType; -import com.android.utils.Pair; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * An {@link IViewRule} for android.widget.AbsoluteLayout and all its derived - * classes. - */ -public class AbsoluteLayoutRule extends BaseLayoutRule { - - @Override - public List<String> getSelectionHint(@NonNull INode parentNode, @NonNull INode childNode) { - List<String> infos = new ArrayList<String>(2); - infos.add("AbsoluteLayout is deprecated."); - infos.add("Use other layouts instead."); - return infos; - } - - // ==== Drag'n'drop support ==== - // The AbsoluteLayout accepts any drag'n'drop anywhere on its surface. - - @Override - public DropFeedback onDropEnter(@NonNull INode targetNode, @Nullable Object targetView, - final @Nullable IDragElement[] elements) { - - if (elements.length == 0) { - return null; - } - - DropFeedback df = new DropFeedback(null, new IFeedbackPainter() { - @Override - public void paint(@NonNull IGraphics gc, @NonNull INode node, - @NonNull DropFeedback feedback) { - // Paint callback for the AbsoluteLayout. - // This is called by the canvas when a draw is needed. - drawFeedback(gc, node, elements, feedback); - } - }); - df.errorMessage = "AbsoluteLayout is deprecated."; - return df; - } - - void drawFeedback( - IGraphics gc, - INode targetNode, - IDragElement[] elements, - DropFeedback feedback) { - Rect b = targetNode.getBounds(); - if (!b.isValid()) { - return; - } - - // Highlight the receiver - gc.useStyle(DrawingStyle.DROP_RECIPIENT); - gc.drawRect(b); - - // Get the drop point - Point p = (Point) feedback.userData; - - if (p == null) { - return; - } - - int x = p.x; - int y = p.y; - - Rect be = elements[0].getBounds(); - - if (be.isValid()) { - // At least the first element has a bound. Draw rectangles - // for all dropped elements with valid bounds, offset at - // the drop point. - int offsetX = x - be.x + (feedback.dragBounds != null ? feedback.dragBounds.x : 0); - int offsetY = y - be.y + (feedback.dragBounds != null ? feedback.dragBounds.y : 0); - gc.useStyle(DrawingStyle.DROP_PREVIEW); - for (IDragElement element : elements) { - drawElement(gc, element, offsetX, offsetY); - } - } else { - // We don't have bounds for new elements. In this case - // just draw cross hairs to the drop point. - gc.useStyle(DrawingStyle.GUIDELINE); - gc.drawLine(x, b.y, x, b.y + b.h); - gc.drawLine(b.x, y, b.x + b.w, y); - - // Use preview lines to indicate the bottom quadrant as well (to - // indicate that you are looking at the top left position of the - // drop, not the center for example) - gc.useStyle(DrawingStyle.DROP_PREVIEW); - gc.drawLine(x, y, b.x + b.w, y); - gc.drawLine(x, y, x, b.y + b.h); - } - } - - @Override - public DropFeedback onDropMove(@NonNull INode targetNode, @NonNull IDragElement[] elements, - @Nullable DropFeedback feedback, @NonNull Point p) { - // Update the data used by the DropFeedback.paintCallback above. - feedback.userData = p; - feedback.requestPaint = true; - - return feedback; - } - - @Override - public void onDropLeave(@NonNull INode targetNode, @NonNull IDragElement[] elements, - @Nullable DropFeedback feedback) { - // Nothing to do. - } - - @Override - public void onDropped(final @NonNull INode targetNode, final @NonNull IDragElement[] elements, - final @Nullable DropFeedback feedback, final @NonNull Point p) { - - final Rect b = targetNode.getBounds(); - if (!b.isValid()) { - return; - } - - // 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, - feedback.isCopy || !feedback.sameCanvas); - - targetNode.editXml("Add elements to AbsoluteLayout", new INodeHandler() { - @Override - public void handle(@NonNull INode node) { - boolean first = true; - Point offset = null; - - // Now write the new elements. - for (IDragElement element : elements) { - String fqcn = element.getFqcn(); - Rect be = element.getBounds(); - - INode newChild = targetNode.appendChild(fqcn); - - // Copy all the attributes, modifying them as needed. - addAttributes(newChild, element, idMap, DEFAULT_ATTR_FILTER); - - int deltaX = (feedback.dragBounds != null ? feedback.dragBounds.x : 0); - int deltaY = (feedback.dragBounds != null ? feedback.dragBounds.y : 0); - - int x = p.x - b.x + deltaX; - int y = p.y - b.y + deltaY; - - if (first) { - first = false; - if (be.isValid()) { - offset = new Point(x - be.x, y - be.y); - } - } else if (offset != null && be.isValid()) { - x = offset.x + be.x; - y = offset.y + be.y; - } else { - x += 10; - y += be.isValid() ? be.h : 10; - } - - double scale = feedback.dipScale; - if (scale != 1.0) { - x *= scale; - y *= scale; - } - - newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_X, - String.format(VALUE_N_DP, x)); - newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_Y, - String.format(VALUE_N_DP, y)); - - addInnerElements(newChild, element, idMap); - } - } - }); - } - - /** - * {@inheritDoc} - * <p> - * Overridden in this layout in order to let the top left coordinate be affected by - * the resize operation too. In other words, dragging the top left corner to resize a - * widget will not only change the size of the widget, it will also move it (though in - * this case, the bottom right corner will stay fixed). - */ - @Override - protected void setNewSizeBounds(ResizeState resizeState, INode node, INode layout, - Rect previousBounds, Rect newBounds, SegmentType horizontalEdge, - SegmentType verticalEdge) { - super.setNewSizeBounds(resizeState, node, layout, previousBounds, newBounds, - horizontalEdge, verticalEdge); - if (verticalEdge != null && newBounds.x != previousBounds.x) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_X, - String.format(VALUE_N_DP, - mRulesEngine.pxToDp(newBounds.x - node.getParent().getBounds().x))); - } - if (horizontalEdge != null && newBounds.y != previousBounds.y) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_Y, - String.format(VALUE_N_DP, - mRulesEngine.pxToDp(newBounds.y - node.getParent().getBounds().y))); - } - } - - @Override - protected String getResizeUpdateMessage(ResizeState resizeState, INode child, INode parent, - Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) { - Rect parentBounds = parent.getBounds(); - if (horizontalEdge == SegmentType.BOTTOM && verticalEdge == SegmentType.RIGHT) { - return super.getResizeUpdateMessage(resizeState, child, parent, newBounds, - horizontalEdge, verticalEdge); - } - return String.format("x=%d, y=%d\nwidth=%s, height=%s", - mRulesEngine.pxToDp(newBounds.x - parentBounds.x), - mRulesEngine.pxToDp(newBounds.y - parentBounds.y), - resizeState.getWidthAttribute(), resizeState.getHeightAttribute()); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AdapterViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AdapterViewRule.java deleted file mode 100644 index 28f5fc95e..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/AdapterViewRule.java +++ /dev/null @@ -1,62 +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 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.IDragElement; -import com.android.ide.common.api.IFeedbackPainter; -import com.android.ide.common.api.IGraphics; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Point; -import com.android.ide.common.api.Rect; - -/** Rule for AdapterView subclasses that don't have more specific rules */ -public class AdapterViewRule extends BaseLayoutRule { - @Override - public DropFeedback onDropEnter(@NonNull INode targetNode, @Nullable Object targetView, - @Nullable IDragElement[] elements) { - // You are not allowed to insert children into AdapterViews; you must - // use the dedicated addView methods etc dynamically - DropFeedback dropFeedback = new DropFeedback(null, new IFeedbackPainter() { - @Override - public void paint(@NonNull IGraphics gc, @NonNull INode node, - @NonNull DropFeedback feedback) { - Rect b = node.getBounds(); - if (b.isValid()) { - gc.useStyle(DrawingStyle.DROP_RECIPIENT); - gc.drawRect(b); - } - } - }); - String fqcn = targetNode.getFqcn(); - String name = fqcn.substring(fqcn.lastIndexOf('.') +1); - dropFeedback.errorMessage = String.format( - "%s cannot be configured via XML; add content to the AdapterView using Java code", - name); - dropFeedback.invalidTarget = true; - return dropFeedback; - } - - @Override - public DropFeedback onDropMove(@NonNull INode targetNode, @NonNull IDragElement[] elements, - @Nullable DropFeedback feedback, @NonNull Point p) { - feedback.invalidTarget = true; - return feedback; - } -} 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()); - } - } -} 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 deleted file mode 100644 index 83ce9ef8f..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java +++ /dev/null @@ -1,996 +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_CLASS; -import static com.android.SdkConstants.ATTR_HINT; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; -import static com.android.SdkConstants.ATTR_STYLE; -import static com.android.SdkConstants.ATTR_TEXT; -import static com.android.SdkConstants.DOT_LAYOUT_PARAMS; -import static com.android.SdkConstants.ID_PREFIX; -import static com.android.SdkConstants.NEW_ID_PREFIX; -import static com.android.SdkConstants.VALUE_FALSE; -import static com.android.SdkConstants.VALUE_FILL_PARENT; -import static com.android.SdkConstants.VALUE_MATCH_PARENT; -import static com.android.SdkConstants.VALUE_TRUE; -import static com.android.SdkConstants.VALUE_WRAP_CONTENT; -import static com.android.SdkConstants.VIEW_FRAGMENT; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.AbstractViewRule; -import com.android.ide.common.api.IAttributeInfo; -import com.android.ide.common.api.IAttributeInfo.Format; -import com.android.ide.common.api.IClientRulesEngine; -import com.android.ide.common.api.IDragElement; -import com.android.ide.common.api.IMenuCallback; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewMetadata; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.RuleAction; -import com.android.ide.common.api.RuleAction.ActionProvider; -import com.android.ide.common.api.RuleAction.ChoiceProvider; -import com.android.resources.ResourceType; -import com.android.utils.Pair; - -import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -/** - * Common IViewRule processing to all view and layout classes. - */ -public class BaseViewRule extends AbstractViewRule { - /** List of recently edited properties */ - private static List<String> sRecent = new LinkedList<String>(); - - /** Maximum number of recent properties to track and list */ - private final static int MAX_RECENT_COUNT = 12; - - // Strings used as internal ids, group ids and prefixes for actions - private static final String FALSE_ID = "false"; //$NON-NLS-1$ - private static final String TRUE_ID = "true"; //$NON-NLS-1$ - private static final String PROP_PREFIX = "@prop@"; //$NON-NLS-1$ - private static final String CLEAR_ID = "clear"; //$NON-NLS-1$ - private static final String ZCUSTOM = "zcustom"; //$NON-NLS-1$ - - protected IClientRulesEngine mRulesEngine; - - // Cache of attributes. Key is FQCN of a node mixed with its view hierarchy - // parent. Values are a custom map as needed by getContextMenu. - private Map<String, Map<String, Prop>> mAttributesMap = - new HashMap<String, Map<String, Prop>>(); - - @Override - public boolean onInitialize(@NonNull String fqcn, @NonNull IClientRulesEngine engine) { - mRulesEngine = engine; - - // This base rule can handle any class so we don't need to filter on - // FQCN. Derived classes should do so if they can handle some - // subclasses. - - // If onInitialize returns false, it means it can't handle the given - // FQCN and will be unloaded. - - return true; - } - - /** - * Returns the {@link IClientRulesEngine} associated with this {@link IViewRule} - * - * @return the {@link IClientRulesEngine} associated with this {@link IViewRule} - */ - public IClientRulesEngine getRulesEngine() { - return mRulesEngine; - } - - // === Context Menu === - - /** - * Generate custom actions for the context menu: <br/> - * - Explicit layout_width and layout_height attributes. - * - List of all other simple toggle attributes. - */ - @Override - public void addContextMenuActions(@NonNull List<RuleAction> actions, - final @NonNull INode selectedNode) { - String width = null; - String currentWidth = selectedNode.getStringAttr(ANDROID_URI, ATTR_LAYOUT_WIDTH); - - String fillParent = getFillParentValueName(); - boolean canMatchParent = supportsMatchParent(); - if (canMatchParent && VALUE_FILL_PARENT.equals(currentWidth)) { - currentWidth = VALUE_MATCH_PARENT; - } else if (!canMatchParent && VALUE_MATCH_PARENT.equals(currentWidth)) { - currentWidth = VALUE_FILL_PARENT; - } else if (!VALUE_WRAP_CONTENT.equals(currentWidth) && !fillParent.equals(currentWidth)) { - width = currentWidth; - } - - String height = null; - String currentHeight = selectedNode.getStringAttr(ANDROID_URI, ATTR_LAYOUT_HEIGHT); - - if (canMatchParent && VALUE_FILL_PARENT.equals(currentHeight)) { - currentHeight = VALUE_MATCH_PARENT; - } else if (!canMatchParent && VALUE_MATCH_PARENT.equals(currentHeight)) { - currentHeight = VALUE_FILL_PARENT; - } else if (!VALUE_WRAP_CONTENT.equals(currentHeight) - && !fillParent.equals(currentHeight)) { - height = currentHeight; - } - final String newWidth = width; - final String newHeight = height; - - final IMenuCallback onChange = new IMenuCallback() { - @Override - public void action( - final @NonNull RuleAction action, - final @NonNull List<? extends INode> selectedNodes, - final @Nullable String valueId, final @Nullable Boolean newValue) { - String fullActionId = action.getId(); - boolean isProp = fullActionId.startsWith(PROP_PREFIX); - final String actionId = isProp ? - fullActionId.substring(PROP_PREFIX.length()) : fullActionId; - - if (fullActionId.equals(ATTR_LAYOUT_WIDTH)) { - final String newAttrValue = getValue(valueId, newWidth); - if (newAttrValue != null) { - for (INode node : selectedNodes) { - node.editXml("Change Attribute " + ATTR_LAYOUT_WIDTH, - new PropertySettingNodeHandler(ANDROID_URI, - ATTR_LAYOUT_WIDTH, newAttrValue)); - } - editedProperty(ATTR_LAYOUT_WIDTH); - } - return; - } else if (fullActionId.equals(ATTR_LAYOUT_HEIGHT)) { - // Ask the user - final String newAttrValue = getValue(valueId, newHeight); - if (newAttrValue != null) { - for (INode node : selectedNodes) { - node.editXml("Change Attribute " + ATTR_LAYOUT_HEIGHT, - new PropertySettingNodeHandler(ANDROID_URI, - ATTR_LAYOUT_HEIGHT, newAttrValue)); - } - editedProperty(ATTR_LAYOUT_HEIGHT); - } - return; - } else if (fullActionId.equals(ATTR_ID)) { - // Ids must be set individually so open the id dialog for each - // selected node (though allow cancel to break the loop) - for (INode node : selectedNodes) { - if (!mRulesEngine.rename(node)) { - break; - } - } - editedProperty(ATTR_ID); - return; - } else if (isProp) { - INode firstNode = selectedNodes.get(0); - String key = getPropertyMapKey(selectedNode); - Map<String, Prop> props = mAttributesMap.get(key); - final Prop prop = (props != null) ? props.get(actionId) : null; - - if (prop != null) { - editedProperty(actionId); - - // For custom values (requiring an input dialog) input the - // value outside the undo-block. - // Input the value as a text, unless we know it's the "text" or - // "style" attributes (where we know we want to ask for specific - // resource types). - String uri = ANDROID_URI; - String v = null; - if (prop.isStringEdit()) { - boolean isStyle = actionId.equals(ATTR_STYLE); - boolean isText = actionId.equals(ATTR_TEXT); - boolean isHint = actionId.equals(ATTR_HINT); - if (isStyle || isText || isHint) { - String resourceTypeName = isStyle - ? ResourceType.STYLE.getName() - : ResourceType.STRING.getName(); - String oldValue = selectedNodes.size() == 1 - ? (isStyle ? firstNode.getStringAttr(ATTR_STYLE, actionId) - : firstNode.getStringAttr(ANDROID_URI, actionId)) - : ""; //$NON-NLS-1$ - oldValue = ensureValidString(oldValue); - v = mRulesEngine.displayResourceInput(resourceTypeName, oldValue); - if (isStyle) { - uri = null; - } - } else if (actionId.equals(ATTR_CLASS) && selectedNodes.size() >= 1 && - VIEW_FRAGMENT.equals(selectedNodes.get(0).getFqcn())) { - v = mRulesEngine.displayFragmentSourceInput(); - uri = null; - } else { - v = inputAttributeValue(firstNode, actionId); - } - } - final String customValue = v; - - for (INode n : selectedNodes) { - if (prop.isToggle()) { - // case of toggle - String value = ""; //$NON-NLS-1$ - if (valueId.equals(TRUE_ID)) { - value = newValue ? "true" : ""; //$NON-NLS-1$ //$NON-NLS-2$ - } else if (valueId.equals(FALSE_ID)) { - value = newValue ? "false" : "";//$NON-NLS-1$ //$NON-NLS-2$ - } - n.setAttribute(uri, actionId, value); - } else if (prop.isFlag()) { - // case of a flag - String values = ""; //$NON-NLS-1$ - if (!valueId.equals(CLEAR_ID)) { - values = n.getStringAttr(ANDROID_URI, actionId); - Set<String> newValues = new HashSet<String>(); - if (values != null) { - newValues.addAll(Arrays.asList( - values.split("\\|"))); //$NON-NLS-1$ - } - if (newValue) { - newValues.add(valueId); - } else { - newValues.remove(valueId); - } - - List<String> sorted = new ArrayList<String>(newValues); - Collections.sort(sorted); - values = join('|', sorted); - - // Special case - if (valueId.equals("normal")) { //$NON-NLS-1$ - // For textStyle for example, if you have "bold|italic" - // and you select the "normal" property, this should - // not behave in the normal flag way and "or" itself in; - // it should replace the other two. - // This also applies to imeOptions. - values = valueId; - } - } - n.setAttribute(uri, actionId, values); - } else if (prop.isEnum()) { - // case of an enum - String value = ""; //$NON-NLS-1$ - if (!valueId.equals(CLEAR_ID)) { - value = newValue ? valueId : ""; //$NON-NLS-1$ - } - n.setAttribute(uri, actionId, value); - } else { - assert prop.isStringEdit(); - // We've already received the value outside the undo block - if (customValue != null) { - n.setAttribute(uri, actionId, customValue); - } - } - } - } - } - } - - /** - * Input the custom value for the given attribute. This will use the Reference - * Chooser if it is a reference value, otherwise a plain text editor. - */ - private String inputAttributeValue(final INode node, final String attribute) { - String oldValue = node.getStringAttr(ANDROID_URI, attribute); - oldValue = ensureValidString(oldValue); - IAttributeInfo attributeInfo = node.getAttributeInfo(ANDROID_URI, attribute); - if (attributeInfo != null - && attributeInfo.getFormats().contains(Format.REFERENCE)) { - return mRulesEngine.displayReferenceInput(oldValue); - } else { - // A single resource type? If so use a resource chooser initialized - // to this specific type - /* This does not work well, because the metadata is a bit misleading: - * for example a Button's "text" property and a Button's "onClick" property - * both claim to be of type [string], but @string/ is NOT valid for - * onClick.. - if (attributeInfo != null && attributeInfo.getFormats().length == 1) { - // Resource chooser - Format format = attributeInfo.getFormats()[0]; - return mRulesEngine.displayResourceInput(format.name(), oldValue); - } - */ - - // Fallback: just edit the raw XML string - String message = String.format("New %1$s Value:", attribute); - return mRulesEngine.displayInput(message, oldValue, null); - } - } - - /** - * Returns the value (which will ask the user if the value is the special - * {@link #ZCUSTOM} marker - */ - private String getValue(String valueId, String defaultValue) { - if (valueId.equals(ZCUSTOM)) { - if (defaultValue == null) { - defaultValue = ""; - } - String value = mRulesEngine.displayInput( - "Set custom layout attribute value (example: 50dp)", - defaultValue, null); - if (value != null && value.trim().length() > 0) { - return value.trim(); - } else { - return null; - } - } - - return valueId; - } - }; - - IAttributeInfo textAttribute = selectedNode.getAttributeInfo(ANDROID_URI, ATTR_TEXT); - if (textAttribute != null) { - actions.add(RuleAction.createAction(PROP_PREFIX + ATTR_TEXT, "Edit Text...", onChange, - null, 10, true)); - } - - String editIdLabel = selectedNode.getStringAttr(ANDROID_URI, ATTR_ID) != null ? - "Edit ID..." : "Assign ID..."; - actions.add(RuleAction.createAction(ATTR_ID, editIdLabel, onChange, null, 20, true)); - - addCommonPropertyActions(actions, selectedNode, onChange, 21); - - // Create width choice submenu - actions.add(RuleAction.createSeparator(32)); - List<Pair<String, String>> widthChoices = new ArrayList<Pair<String,String>>(4); - widthChoices.add(Pair.of(VALUE_WRAP_CONTENT, "Wrap Content")); - if (canMatchParent) { - widthChoices.add(Pair.of(VALUE_MATCH_PARENT, "Match Parent")); - } else { - widthChoices.add(Pair.of(VALUE_FILL_PARENT, "Fill Parent")); - } - if (width != null) { - widthChoices.add(Pair.of(width, width)); - } - widthChoices.add(Pair.of(ZCUSTOM, "Other...")); - actions.add(RuleAction.createChoices( - ATTR_LAYOUT_WIDTH, "Layout Width", - onChange, - null /* iconUrls */, - currentWidth, - null, 35, - true, // supportsMultipleNodes - widthChoices)); - - // Create height choice submenu - List<Pair<String, String>> heightChoices = new ArrayList<Pair<String,String>>(4); - heightChoices.add(Pair.of(VALUE_WRAP_CONTENT, "Wrap Content")); - if (canMatchParent) { - heightChoices.add(Pair.of(VALUE_MATCH_PARENT, "Match Parent")); - } else { - heightChoices.add(Pair.of(VALUE_FILL_PARENT, "Fill Parent")); - } - if (height != null) { - heightChoices.add(Pair.of(height, height)); - } - heightChoices.add(Pair.of(ZCUSTOM, "Other...")); - actions.add(RuleAction.createChoices( - ATTR_LAYOUT_HEIGHT, "Layout Height", - onChange, - null /* iconUrls */, - currentHeight, - null, 40, - true, - heightChoices)); - - actions.add(RuleAction.createSeparator(45)); - RuleAction properties = RuleAction.createChoices("properties", "Other Properties", //$NON-NLS-1$ - onChange /*callback*/, null /*icon*/, 50, - true /*supportsMultipleNodes*/, new ActionProvider() { - @Override - public @NonNull List<RuleAction> getNestedActions(@NonNull INode node) { - List<RuleAction> propertyActionTypes = new ArrayList<RuleAction>(); - propertyActionTypes.add(RuleAction.createChoices( - "recent", "Recent", //$NON-NLS-1$ - onChange /*callback*/, null /*icon*/, 10, - true /*supportsMultipleNodes*/, new ActionProvider() { - @Override - public @NonNull List<RuleAction> getNestedActions(@NonNull INode n) { - List<RuleAction> propertyActions = new ArrayList<RuleAction>(); - addRecentPropertyActions(propertyActions, n, onChange); - return propertyActions; - } - })); - - propertyActionTypes.add(RuleAction.createSeparator(20)); - - addInheritedProperties(propertyActionTypes, node, onChange, 30); - - propertyActionTypes.add(RuleAction.createSeparator(50)); - propertyActionTypes.add(RuleAction.createChoices( - "layoutparams", "Layout Parameters", //$NON-NLS-1$ - onChange /*callback*/, null /*icon*/, 60, - true /*supportsMultipleNodes*/, new ActionProvider() { - @Override - public @NonNull List<RuleAction> getNestedActions(@NonNull INode n) { - List<RuleAction> propertyActions = new ArrayList<RuleAction>(); - addPropertyActions(propertyActions, n, onChange, null, true); - return propertyActions; - } - })); - - propertyActionTypes.add(RuleAction.createSeparator(70)); - - propertyActionTypes.add(RuleAction.createChoices( - "allprops", "All By Name", //$NON-NLS-1$ - onChange /*callback*/, null /*icon*/, 80, - true /*supportsMultipleNodes*/, new ActionProvider() { - @Override - public @NonNull List<RuleAction> getNestedActions(@NonNull INode n) { - List<RuleAction> propertyActions = new ArrayList<RuleAction>(); - addPropertyActions(propertyActions, n, onChange, null, false); - return propertyActions; - } - })); - - return propertyActionTypes; - } - }); - - actions.add(properties); - } - - @Override - @Nullable - public String getDefaultActionId(@NonNull final INode selectedNode) { - IAttributeInfo textAttribute = selectedNode.getAttributeInfo(ANDROID_URI, ATTR_TEXT); - if (textAttribute != null) { - return PROP_PREFIX + ATTR_TEXT; - } - - return null; - } - - private static String getPropertyMapKey(INode node) { - // Compute the key for mAttributesMap. This depends on the type of this - // node and its parent in the view hierarchy. - StringBuilder sb = new StringBuilder(); - sb.append(node.getFqcn()); - sb.append('_'); - INode parent = node.getParent(); - if (parent != null) { - sb.append(parent.getFqcn()); - } - return sb.toString(); - } - - /** - * Adds menu items for the inherited attributes, one pull-right menu for each super class - * that defines attributes. - * - * @param propertyActionTypes the actions list to add into - * @param node the node to apply the attributes to - * @param onChange the callback to use for setting attributes - * @param sortPriority the initial sort attribute for the first menu item - */ - private void addInheritedProperties(List<RuleAction> propertyActionTypes, INode node, - final IMenuCallback onChange, int sortPriority) { - List<String> attributeSources = node.getAttributeSources(); - for (final String definedBy : attributeSources) { - String sourceClass = definedBy; - - // Strip package prefixes when necessary - int index = sourceClass.length(); - if (sourceClass.endsWith(DOT_LAYOUT_PARAMS)) { - index = sourceClass.length() - DOT_LAYOUT_PARAMS.length() - 1; - } - int lastDot = sourceClass.lastIndexOf('.', index); - if (lastDot != -1) { - sourceClass = sourceClass.substring(lastDot + 1); - } - - String label; - if (definedBy.equals(node.getFqcn())) { - label = String.format("Defined by %1$s", sourceClass); - } else { - label = String.format("Inherited from %1$s", sourceClass); - } - - propertyActionTypes.add(RuleAction.createChoices("def_" + definedBy, - label, - onChange /*callback*/, null /*icon*/, sortPriority++, - true /*supportsMultipleNodes*/, new ActionProvider() { - @Override - public @NonNull List<RuleAction> getNestedActions(@NonNull INode n) { - List<RuleAction> propertyActions = new ArrayList<RuleAction>(); - addPropertyActions(propertyActions, n, onChange, definedBy, false); - return propertyActions; - } - })); - } - } - - /** - * Creates a list of properties that are commonly edited for views of the - * selected node's type - */ - private void addCommonPropertyActions(List<RuleAction> actions, INode selectedNode, - IMenuCallback onChange, int sortPriority) { - Map<String, Prop> properties = getPropertyMetadata(selectedNode); - IViewMetadata metadata = mRulesEngine.getMetadata(selectedNode.getFqcn()); - if (metadata != null) { - List<String> attributes = metadata.getTopAttributes(); - if (attributes.size() > 0) { - for (String attribute : attributes) { - // Text and ID are handled manually in the menu construction code because - // we want to place them consistently and customize the action label - if (ATTR_TEXT.equals(attribute) || ATTR_ID.equals(attribute)) { - continue; - } - - Prop property = properties.get(attribute); - if (property != null) { - String title = property.getTitle(); - if (title.endsWith("...")) { - title = String.format("Edit %1$s", property.getTitle()); - } - actions.add(createPropertyAction(property, attribute, title, - selectedNode, onChange, sortPriority)); - sortPriority++; - } - } - } - } - } - - /** - * Record that the given property was just edited; adds it to the front of - * the recently edited property list - * - * @param property the name of the property - */ - static void editedProperty(String property) { - if (sRecent.contains(property)) { - sRecent.remove(property); - } else if (sRecent.size() > MAX_RECENT_COUNT) { - sRecent.remove(sRecent.size() - 1); - } - sRecent.add(0, property); - } - - /** - * Creates a list of recently modified properties that apply to the given selected node - */ - private void addRecentPropertyActions(List<RuleAction> actions, INode selectedNode, - IMenuCallback onChange) { - int sortPriority = 10; - Map<String, Prop> properties = getPropertyMetadata(selectedNode); - for (String attribute : sRecent) { - Prop property = properties.get(attribute); - if (property != null) { - actions.add(createPropertyAction(property, attribute, property.getTitle(), - selectedNode, onChange, sortPriority)); - sortPriority += 10; - } - } - } - - /** - * Creates a list of nested actions representing the property-setting - * actions for the given selected node - */ - private void addPropertyActions(List<RuleAction> actions, INode selectedNode, - IMenuCallback onChange, String definedBy, boolean layoutParamsOnly) { - - Map<String, Prop> properties = getPropertyMetadata(selectedNode); - - int sortPriority = 10; - for (Map.Entry<String, Prop> entry : properties.entrySet()) { - String id = entry.getKey(); - Prop property = entry.getValue(); - if (layoutParamsOnly) { - // If we have definedBy information, that is most accurate; all layout - // params will be defined by a class whose name ends with - // .LayoutParams: - if (definedBy != null) { - if (!definedBy.endsWith(DOT_LAYOUT_PARAMS)) { - continue; - } - } else if (!id.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) { - continue; - } - } - if (definedBy != null && !definedBy.equals(property.getDefinedBy())) { - continue; - } - actions.add(createPropertyAction(property, id, property.getTitle(), - selectedNode, onChange, sortPriority)); - sortPriority += 10; - } - - // The properties are coming out of map key order which isn't right, so sort - // alphabetically instead - Collections.sort(actions, new Comparator<RuleAction>() { - @Override - public int compare(RuleAction action1, RuleAction action2) { - return action1.getTitle().compareTo(action2.getTitle()); - } - }); - } - - private RuleAction createPropertyAction(Prop p, String id, String title, INode selectedNode, - IMenuCallback onChange, int sortPriority) { - if (p.isToggle()) { - // Toggles are handled as a multiple-choice between true, false - // and nothing (clear) - String value = selectedNode.getStringAttr(ANDROID_URI, id); - if (value != null) { - value = value.toLowerCase(Locale.US); - } - if (VALUE_TRUE.equals(value)) { - value = TRUE_ID; - } else if (VALUE_FALSE.equals(value)) { - value = FALSE_ID; - } else { - value = CLEAR_ID; - } - return RuleAction.createChoices(PROP_PREFIX + id, title, - onChange, BOOLEAN_CHOICE_PROVIDER, - value, - null, sortPriority, - true); - } else if (p.getChoices() != null) { - // Enum or flags. Their possible values are the multiple-choice - // items, with an extra "clear" option to remove everything. - String current = selectedNode.getStringAttr(ANDROID_URI, id); - if (current == null || current.length() == 0) { - current = CLEAR_ID; - } - return RuleAction.createChoices(PROP_PREFIX + id, title, - onChange, new EnumPropertyChoiceProvider(p), - current, - null, sortPriority, - true); - } else { - return RuleAction.createAction( - PROP_PREFIX + id, - title, - onChange, - null, sortPriority, - true); - } - } - - private Map<String, Prop> getPropertyMetadata(final INode selectedNode) { - String key = getPropertyMapKey(selectedNode); - Map<String, Prop> props = mAttributesMap.get(key); - if (props == null) { - // Prepare the property map - props = new HashMap<String, Prop>(); - for (IAttributeInfo attrInfo : selectedNode.getDeclaredAttributes()) { - String id = attrInfo != null ? attrInfo.getName() : null; - if (id == null || id.equals(ATTR_LAYOUT_WIDTH) || id.equals(ATTR_LAYOUT_HEIGHT)) { - // Layout width/height are already handled at the root level - continue; - } - if (attrInfo == null) { - continue; - } - EnumSet<Format> formats = attrInfo.getFormats(); - - String title = getAttributeDisplayName(id); - - String definedBy = attrInfo != null ? attrInfo.getDefinedBy() : null; - if (formats.contains(IAttributeInfo.Format.BOOLEAN)) { - props.put(id, new Prop(title, true, definedBy)); - } else if (formats.contains(IAttributeInfo.Format.ENUM)) { - // Convert each enum into a map id=>title - Map<String, String> values = new HashMap<String, String>(); - if (attrInfo != null) { - for (String e : attrInfo.getEnumValues()) { - values.put(e, getAttributeDisplayName(e)); - } - } - - props.put(id, new Prop(title, false, false, values, definedBy)); - } else if (formats.contains(IAttributeInfo.Format.FLAG)) { - // Convert each flag into a map id=>title - Map<String, String> values = new HashMap<String, String>(); - if (attrInfo != null) { - for (String e : attrInfo.getFlagValues()) { - values.put(e, getAttributeDisplayName(e)); - } - } - - props.put(id, new Prop(title, false, true, values, definedBy)); - } else { - props.put(id, new Prop(title + "...", false, definedBy)); - } - } - mAttributesMap.put(key, props); - } - return props; - } - - /** - * A {@link ChoiceProvder} which provides alternatives suitable for choosing - * values for a boolean property: true, false, or "default". - */ - private static ChoiceProvider BOOLEAN_CHOICE_PROVIDER = new ChoiceProvider() { - @Override - public void addChoices(@NonNull List<String> titles, @NonNull List<URL> iconUrls, - @NonNull List<String> ids) { - titles.add("True"); - ids.add(TRUE_ID); - - titles.add("False"); - ids.add(FALSE_ID); - - titles.add(RuleAction.SEPARATOR); - ids.add(RuleAction.SEPARATOR); - - titles.add("Default"); - ids.add(CLEAR_ID); - } - }; - - /** - * A {@link ChoiceProvider} which provides the various available - * attribute values available for a given {@link Prop} property descriptor. - */ - private static class EnumPropertyChoiceProvider implements ChoiceProvider { - private Prop mProperty; - - public EnumPropertyChoiceProvider(Prop property) { - super(); - mProperty = property; - } - - @Override - public void addChoices(@NonNull List<String> titles, @NonNull List<URL> iconUrls, - @NonNull List<String> ids) { - for (Entry<String, String> entry : mProperty.getChoices().entrySet()) { - ids.add(entry.getKey()); - titles.add(entry.getValue()); - } - - titles.add(RuleAction.SEPARATOR); - ids.add(RuleAction.SEPARATOR); - - titles.add("Default"); - ids.add(CLEAR_ID); - } - } - - /** - * Returns true if the given node is "filled" (e.g. has layout width set to match - * parent or fill parent - */ - protected final 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 - * - * @return match_parent or fill_parent depending on which is supported by the project - */ - protected final String getFillParentValueName() { - return supportsMatchParent() ? VALUE_MATCH_PARENT : VALUE_FILL_PARENT; - } - - /** - * Returns true if the project supports match_parent instead of just fill_parent - * - * @return true if the project supports match_parent instead of just fill_parent - */ - protected final boolean supportsMatchParent() { - // fill_parent was renamed match_parent in API level 8 - return mRulesEngine.getMinApiLevel() >= 8; - } - - /** Join strings into a single string with the given delimiter */ - static String join(char delimiter, Collection<String> strings) { - StringBuilder sb = new StringBuilder(100); - for (String s : strings) { - if (sb.length() > 0) { - sb.append(delimiter); - } - sb.append(s); - } - return sb.toString(); - } - - static Map<String, String> concatenate(Map<String, String> pre, Map<String, String> post) { - Map<String, String> result = new HashMap<String, String>(pre.size() + post.size()); - result.putAll(pre); - result.putAll(post); - return result; - } - - // Quick utility for building up maps declaratively to minimize the diffs - static Map<String, String> mapify(String... values) { - Map<String, String> map = new HashMap<String, String>(values.length / 2); - for (int i = 0; i < values.length; i += 2) { - String key = values[i]; - if (key == null) { - continue; - } - String value = values[i + 1]; - map.put(key, value); - } - - return map; - } - - /** - * Produces a display name for an attribute, usually capitalizing the attribute name - * and splitting up underscores into new words - * - * @param name the attribute name to convert - * @return a display name for the attribute name - */ - public static String getAttributeDisplayName(String name) { - if (name != null && name.length() > 0) { - StringBuilder sb = new StringBuilder(); - boolean capitalizeNext = true; - for (int i = 0, n = name.length(); i < n; i++) { - char c = name.charAt(i); - if (capitalizeNext) { - c = Character.toUpperCase(c); - } - capitalizeNext = false; - if (c == '_') { - c = ' '; - capitalizeNext = true; - } - sb.append(c); - } - - return sb.toString(); - } - - return name; - } - - - // ==== Paste support ==== - - /** - * Most views can't accept children so there's nothing to paste on them. In - * this case, defer the call to the parent layout and use the target node as - * an indication of where to paste. - */ - @Override - public void onPaste(@NonNull INode targetNode, @Nullable Object targetView, - @NonNull IDragElement[] elements) { - // - INode parent = targetNode.getParent(); - if (parent != null) { - String parentFqcn = parent.getFqcn(); - IViewRule parentRule = mRulesEngine.loadRule(parentFqcn); - - if (parentRule instanceof BaseLayoutRule) { - ((BaseLayoutRule) parentRule).onPasteBeforeChild(parent, targetView, targetNode, - elements); - } - } - } - - /** - * Support class for the context menu code. Stores state about properties in - * the context menu. - */ - private static class Prop { - private final boolean mToggle; - private final boolean mFlag; - private final String mTitle; - private final Map<String, String> mChoices; - private String mDefinedBy; - - public Prop(String title, boolean isToggle, boolean isFlag, Map<String, String> choices, - String definedBy) { - mTitle = title; - mToggle = isToggle; - mFlag = isFlag; - mChoices = choices; - mDefinedBy = definedBy; - } - - public String getDefinedBy() { - return mDefinedBy; - } - - public Prop(String title, boolean isToggle, String definedBy) { - this(title, isToggle, false, null, definedBy); - } - - private boolean isToggle() { - return mToggle; - } - - private boolean isFlag() { - return mFlag && mChoices != null; - } - - private boolean isEnum() { - return !mFlag && mChoices != null; - } - - private String getTitle() { - return mTitle; - } - - private Map<String, String> getChoices() { - return mChoices; - } - - private boolean isStringEdit() { - return mChoices == null && !mToggle; - } - } - - /** - * Returns a source attribute value which points to a sample image. This is typically - * used to provide an initial image shown on ImageButtons, etc. There is no guarantee - * that the source pointed to by this method actually exists. - * - * @return a source attribute to use for sample images, never null - */ - protected final String getSampleImageSrc() { - // Builtin graphics available since v1: - return "@android:drawable/btn_star"; //$NON-NLS-1$ - } - - /** - * Strips the {@code @+id} or {@code @id} prefix off of the given id - * - * @param id attribute to be stripped - * @return the id name without the {@code @+id} or {@code @id} prefix - */ - @NonNull - public static String stripIdPrefix(@Nullable String id) { - if (id == null) { - return ""; //$NON-NLS-1$ - } else if (id.startsWith(NEW_ID_PREFIX)) { - return id.substring(NEW_ID_PREFIX.length()); - } else if (id.startsWith(ID_PREFIX)) { - return id.substring(ID_PREFIX.length()); - } - return id; - } - - private static String ensureValidString(String value) { - if (value == null) { - value = ""; //$NON-NLS-1$ - } - return value; - } - } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/CalendarViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/CalendarViewRule.java deleted file mode 100644 index 91684e2c5..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/CalendarViewRule.java +++ /dev/null @@ -1,44 +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_LAYOUT_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for android.widget.CalendarView. - */ -public class CalendarViewRule extends BaseViewRule { - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - // CalendarViews need a lot of space, and the wrapping doesn't seem to work - // well anyway; it reports a much-to-small size than actually accommodates its - // content. - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, getFillParentValueName()); - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, getFillParentValueName()); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/DatePickerRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/DatePickerRule.java deleted file mode 100644 index a635a9ad6..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/DatePickerRule.java +++ /dev/null @@ -1,22 +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; - -public class DatePickerRule extends IgnoredLayoutRule { - // A DatePicker inherits from FrameLayout but is not a general purpose - // FrameLayout -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/DialerFilterRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/DialerFilterRule.java deleted file mode 100644 index 606bbd86c..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/DialerFilterRule.java +++ /dev/null @@ -1,63 +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_BELOW; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; -import static com.android.SdkConstants.ATTR_TEXT; -import static com.android.SdkConstants.FQCN_EDIT_TEXT; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for android.widget.DialerFilterRule. - */ -public class DialerFilterRule extends BaseViewRule { - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - // A DialerFilter requires a couple of nested EditTexts with fixed ids: - if (insertType.isCreate()) { - String fillParent = getFillParentValueName(); - INode hint = node.appendChild(FQCN_EDIT_TEXT); - hint.setAttribute(ANDROID_URI, ATTR_TEXT, "Hint"); - hint.setAttribute(ANDROID_URI, ATTR_ID, "@android:id/hint"); //$NON-NLS-1$ - hint.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, fillParent); - - INode primary = node.appendChild(FQCN_EDIT_TEXT); - primary.setAttribute(ANDROID_URI, ATTR_TEXT, "Primary"); - primary.setAttribute(ANDROID_URI, ATTR_ID, "@android:id/primary"); //$NON-NLS-1$ - primary.setAttribute(ANDROID_URI, ATTR_LAYOUT_BELOW, - "@android:id/hint"); //$NON-NLS-1$ - primary.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, fillParent); - - - // What do we initialize the icon to? - //INode icon = node.appendChild("android.widget.ImageView"); //$NON-NLS-1$ - //icon.setAttribute(ANDROID_URI, ATTR_ID, "@android:id/icon"); //$NON-NLS-1$ - } - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java deleted file mode 100644 index 03a5bc04e..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/EditTextRule.java +++ /dev/null @@ -1,139 +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_EMS; -import static com.android.SdkConstants.REQUEST_FOCUS; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -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.RuleAction; - -import java.util.List; - -/** - * An {@link IViewRule} for android.widget.EditText. - */ -public class EditTextRule extends BaseViewRule { - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - if (parent != null) { - INode focus = findFocus(findRoot(parent)); - if (focus == null) { - // Add <requestFocus> - node.appendChild(REQUEST_FOCUS); - } - - if (parent.getBounds().w >= 320) { - node.setAttribute(ANDROID_URI, ATTR_EMS, "10"); //$NON-NLS-1$ - } - } - } - - /** - * {@inheritDoc} - * <p> - * Adds a "Request Focus" menu item. - */ - @Override - public void addContextMenuActions(@NonNull List<RuleAction> actions, - final @NonNull INode selectedNode) { - super.addContextMenuActions(actions, selectedNode); - - final boolean hasFocus = hasFocus(selectedNode); - final String label = hasFocus ? "Clear Focus" : "Request Focus"; - - IMenuCallback onChange = new IMenuCallback() { - @Override - public void action( - @NonNull RuleAction menuAction, - @NonNull List<? extends INode> selectedNodes, - @Nullable String valueId, - @Nullable Boolean newValue) { - selectedNode.editXml(label, new INodeHandler() { - @Override - public void handle(@NonNull INode node) { - INode focus = findFocus(findRoot(node)); - if (focus != null && focus.getParent() != null) { - focus.getParent().removeChild(focus); - } - if (!hasFocus) { - node.appendChild(REQUEST_FOCUS); - } - } - }); - } - }; - - actions.add(RuleAction.createAction("_setfocus", label, onChange, //$NON-NLS-1$ - null, 5, false /*supportsMultipleNodes*/)); - actions.add(RuleAction.createSeparator(7)); - } - - /** Returns true if the given node currently has focus */ - private static boolean hasFocus(INode node) { - INode focus = findFocus(node); - if (focus != null) { - return focus.getParent() == node; - } - - return false; - } - - /** Returns the root/top level node in the view hierarchy that contains the given node */ - private static INode findRoot(INode node) { - // First find the parent - INode root = node; - while (root != null) { - INode parent = root.getParent(); - if (parent == null) { - break; - } else { - root = parent; - } - } - - return root; - } - - /** Finds the focus node (not the node containing focus, but the actual request focus node - * under a given node */ - private static INode findFocus(INode node) { - if (node.getFqcn().equals(REQUEST_FOCUS)) { - return node; - } - - for (INode child : node.getChildren()) { - INode focus = findFocus(child); - if (focus != null) { - return focus; - } - } - return null; - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FragmentRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FragmentRule.java deleted file mode 100644 index f99cf0ceb..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FragmentRule.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_NAME; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for the special XML {@code <fragment>} tag. - */ -public class FragmentRule extends BaseViewRule { - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - // When dropping a fragment tag, ask the user which class to use. - if (insertType == InsertType.CREATE) { // NOT InsertType.CREATE_PREVIEW - String fqcn = mRulesEngine.displayFragmentSourceInput(); - if (fqcn != null) { - node.editXml("Add Fragment", - new PropertySettingNodeHandler(ANDROID_URI, ATTR_NAME, - fqcn.length() > 0 ? fqcn : null)); - } else { - // Remove the view; the insertion was canceled - parent.removeChild(node); - } - } - } -} 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 deleted file mode 100644 index 0f9096294..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FrameLayoutRule.java +++ /dev/null @@ -1,195 +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_LAYOUT_GRAVITY; -import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; - -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.IDragElement; -import com.android.ide.common.api.IFeedbackPainter; -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.IViewMetadata; -import com.android.ide.common.api.IViewMetadata.FillPreference; -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.utils.Pair; - -import java.util.List; -import java.util.Map; - -/** - * An {@link IViewRule} for android.widget.FrameLayout and all its derived - * classes. - */ -public class FrameLayoutRule extends BaseLayoutRule { - - // ==== Drag'n'drop support ==== - // The FrameLayout accepts any drag'n'drop anywhere on its surface. - - @Override - public DropFeedback onDropEnter(@NonNull INode targetNode, @Nullable Object targetView, - final @Nullable IDragElement[] elements) { - if (elements.length == 0) { - return null; - } - - return new DropFeedback(null, new IFeedbackPainter() { - @Override - public void paint(@NonNull IGraphics gc, @NonNull INode node, - @NonNull DropFeedback feedback) { - drawFeedback(gc, node, elements, feedback); - } - }); - } - - protected void drawFeedback( - IGraphics gc, - INode targetNode, - IDragElement[] elements, - DropFeedback feedback) { - Rect b = targetNode.getBounds(); - if (!b.isValid()) { - return; - } - - gc.useStyle(DrawingStyle.DROP_RECIPIENT); - gc.drawRect(b); - - // Get the drop point - Point p = (Point) feedback.userData; - - if (p == null) { - return; - } - - Rect be = elements[0].getBounds(); - - gc.useStyle(DrawingStyle.DROP_PREVIEW); - if (be.isValid()) { - // At least the first element has a bound. Draw rectangles - // for all dropped elements with valid bounds, offset at - // (0,0) - for (IDragElement it : elements) { - Rect currBounds = it.getBounds(); - if (currBounds.isValid()) { - int offsetX = b.x - currBounds.x; - int offsetY = b.y - currBounds.y; - drawElement(gc, it, offsetX, offsetY); - } - } - } else { - // We don't have bounds for new elements. In this case - // just draw insert lines indicating the top left corner where - // the item will be placed - - // +1: Place lines fully within the view (the stroke width is 2) to - // make - // it even more visually obvious - gc.drawLine(b.x + 1, b.y, b.x + 1, b.y + b.h); - gc.drawLine(b.x, b.y + 1, b.x + b.w, b.y + 1); - } - } - - @Override - public DropFeedback onDropMove(@NonNull INode targetNode, @NonNull IDragElement[] elements, - @Nullable DropFeedback feedback, @NonNull Point p) { - feedback.userData = p; - feedback.requestPaint = true; - return feedback; - } - - @Override - public void onDropLeave(@NonNull INode targetNode, @NonNull IDragElement[] elements, - @Nullable DropFeedback feedback) { - // ignore - } - - @Override - public void onDropped(final @NonNull INode targetNode, final @NonNull IDragElement[] elements, - final @Nullable DropFeedback feedback, final @NonNull Point p) { - Rect b = targetNode.getBounds(); - if (!b.isValid()) { - return; - } - - // 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, - feedback.isCopy || !feedback.sameCanvas); - - targetNode.editXml("Add elements to FrameLayout", new INodeHandler() { - - @Override - public void handle(@NonNull INode node) { - - // Now write the new elements. - for (IDragElement element : elements) { - String fqcn = element.getFqcn(); - - INode newChild = targetNode.appendChild(fqcn); - - // Copy all the attributes, modifying them as needed. - addAttributes(newChild, element, idMap, DEFAULT_ATTR_FILTER); - - addInnerElements(newChild, element, idMap); - } - } - }); - } - - @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(RuleAction.createSeparator(25)); - actions.add(createMarginAction(parentNode, children)); - if (children != null && children.size() > 0) { - actions.add(createGravityAction(children, ATTR_LAYOUT_GRAVITY)); - } - } - - @Override - public void onChildInserted(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - // Look at the fill preferences and fill embedded layouts etc - String fqcn = node.getFqcn(); - IViewMetadata metadata = mRulesEngine.getMetadata(fqcn); - if (metadata != null) { - FillPreference fill = metadata.getFillPreference(); - String fillParent = getFillParentValueName(); - if (fill.fillHorizontally(true)) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, fillParent); - } - if (fill.fillVertically(false)) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, fillParent); - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GravityHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GravityHelper.java deleted file mode 100644 index b9aabad3f..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GravityHelper.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * 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 static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY; -import static com.android.SdkConstants.GRAVITY_VALUE_BOTTOM; -import static com.android.SdkConstants.GRAVITY_VALUE_CENTER; -import static com.android.SdkConstants.GRAVITY_VALUE_CENTER_HORIZONTAL; -import static com.android.SdkConstants.GRAVITY_VALUE_CENTER_VERTICAL; -import static com.android.SdkConstants.GRAVITY_VALUE_FILL; -import static com.android.SdkConstants.GRAVITY_VALUE_FILL_HORIZONTAL; -import static com.android.SdkConstants.GRAVITY_VALUE_FILL_VERTICAL; -import static com.android.SdkConstants.GRAVITY_VALUE_LEFT; -import static com.android.SdkConstants.GRAVITY_VALUE_RIGHT; -import static com.android.SdkConstants.GRAVITY_VALUE_TOP; - -import org.w3c.dom.Element; - -/** Helper class for looking up the gravity masks of gravity attributes */ -public class GravityHelper { - // From SDK constants; temporary - public static final String GRAVITY_VALUE_START = "start"; //$NON-NLS-1$ - public static final String GRAVITY_VALUE_END = "end"; //$NON-NLS-1$ - - /** Bitmask for a gravity which includes left */ - @SuppressWarnings("PointlessBitwiseExpression") // for symmetry with other fields - public static final int GRAVITY_LEFT = 1 << 0; - - /** Bitmask for a gravity which includes right */ - public static final int GRAVITY_RIGHT = 1 << 1; - - /** Bitmask for a gravity which includes center horizontal */ - public static final int GRAVITY_CENTER_HORIZ = 1 << 2; - - /** Bitmask for a gravity which includes fill horizontal */ - public static final int GRAVITY_FILL_HORIZ = 1 << 3; - - /** Bitmask for a gravity which includes center vertical */ - public static final int GRAVITY_CENTER_VERT = 1 << 4; - - /** Bitmask for a gravity which includes fill vertical */ - public static final int GRAVITY_FILL_VERT = 1 << 5; - - /** Bitmask for a gravity which includes top */ - public static final int GRAVITY_TOP = 1 << 6; - - /** Bitmask for a gravity which includes bottom */ - public static final int GRAVITY_BOTTOM = 1 << 7; - - /** Bitmask for a gravity which includes start */ - public static final int GRAVITY_START = 1 << 8; - - /** Bitmask for a gravity which includes end */ - public static final int GRAVITY_END = 1 << 9; - - /** Bitmask for a gravity which includes any horizontal constraint */ - public static final int GRAVITY_HORIZ_MASK = GRAVITY_CENTER_HORIZ | GRAVITY_FILL_HORIZ - | GRAVITY_LEFT | GRAVITY_RIGHT | GRAVITY_START | GRAVITY_END; - - /** Bitmask for a gravity which any vertical constraint */ - public static final int GRAVITY_VERT_MASK = GRAVITY_CENTER_VERT | GRAVITY_FILL_VERT - | GRAVITY_TOP | GRAVITY_BOTTOM; - - /** - * Returns the gravity of the given element - * - * @param element the element to look up the gravity for - * @return a bit mask corresponding to the selected gravities - */ - public static int getGravity(Element element) { - String gravityString = element.getAttributeNS(ANDROID_URI, ATTR_LAYOUT_GRAVITY); - return getGravity(gravityString, GRAVITY_LEFT | GRAVITY_TOP); - } - - /** - * Returns the gravity bitmask for the given gravity string description - * - * @param gravityString the gravity string description - * @param defaultMask the default/initial bitmask to start with - * @return a bitmask corresponding to the gravity description - */ - public static int getGravity(String gravityString, int defaultMask) { - int gravity = defaultMask; - if (gravityString != null && !gravityString.isEmpty()) { - String[] anchors = gravityString.split("\\|"); //$NON-NLS-1$ - for (String anchor : anchors) { - if (GRAVITY_VALUE_CENTER.equals(anchor)) { - gravity = GRAVITY_CENTER_HORIZ | GRAVITY_CENTER_VERT; - } else if (GRAVITY_VALUE_FILL.equals(anchor)) { - gravity = GRAVITY_FILL_HORIZ | GRAVITY_FILL_VERT; - } else if (GRAVITY_VALUE_CENTER_VERTICAL.equals(anchor)) { - gravity = (gravity & GRAVITY_HORIZ_MASK) | GRAVITY_CENTER_VERT; - } else if (GRAVITY_VALUE_CENTER_HORIZONTAL.equals(anchor)) { - gravity = (gravity & GRAVITY_VERT_MASK) | GRAVITY_CENTER_HORIZ; - } else if (GRAVITY_VALUE_FILL_VERTICAL.equals(anchor)) { - gravity = (gravity & GRAVITY_HORIZ_MASK) | GRAVITY_FILL_VERT; - } else if (GRAVITY_VALUE_FILL_HORIZONTAL.equals(anchor)) { - gravity = (gravity & GRAVITY_VERT_MASK) | GRAVITY_FILL_HORIZ; - } else if (GRAVITY_VALUE_TOP.equals(anchor)) { - gravity = (gravity & GRAVITY_HORIZ_MASK) | GRAVITY_TOP; - } else if (GRAVITY_VALUE_BOTTOM.equals(anchor)) { - gravity = (gravity & GRAVITY_HORIZ_MASK) | GRAVITY_BOTTOM; - } else if (GRAVITY_VALUE_LEFT.equals(anchor)) { - gravity = (gravity & (GRAVITY_VERT_MASK|GRAVITY_START)) | GRAVITY_LEFT; - } else if (GRAVITY_VALUE_RIGHT.equals(anchor)) { - gravity = (gravity & (GRAVITY_VERT_MASK|GRAVITY_END)) | GRAVITY_RIGHT; - } else if (GRAVITY_VALUE_START.equals(anchor)) { - gravity = (gravity & (GRAVITY_VERT_MASK|GRAVITY_LEFT)) | GRAVITY_START; - } else if (GRAVITY_VALUE_END.equals(anchor)) { - gravity = (gravity & (GRAVITY_VERT_MASK|GRAVITY_RIGHT)) | GRAVITY_END; - } // else: "clip" not supported - } - } - - return gravity; - } - - /** - * Returns true if the given gravity bitmask is constrained horizontally - * - * @param gravity the gravity bitmask - * @return true if the given gravity bitmask is constrained horizontally - */ - public static boolean isConstrainedHorizontally(int gravity) { - return (gravity & GRAVITY_HORIZ_MASK) != 0; - } - - /** - * Returns true if the given gravity bitmask is constrained vertically - * - * @param gravity the gravity bitmask - * @return true if the given gravity bitmask is constrained vertically - */ - public static boolean isConstrainedVertically(int gravity) { - return (gravity & GRAVITY_VERT_MASK) != 0; - } - - /** - * Returns true if the given gravity bitmask is left aligned - * - * @param gravity the gravity bitmask - * @return true if the given gravity bitmask is left aligned - */ - public static boolean isLeftAligned(int gravity) { - return (gravity & (GRAVITY_LEFT|GRAVITY_START)) != 0; - } - - /** - * Returns true if the given gravity bitmask is top aligned - * - * @param gravity the gravity bitmask - * @return true if the given gravity bitmask is aligned - */ - public static boolean isTopAligned(int gravity) { - return (gravity & GRAVITY_TOP) != 0; - } - - /** Returns a gravity value string from the given gravity bitmask - * - * @param gravity the gravity bitmask - * @return the corresponding gravity string suitable as an XML attribute value - */ - public static String getGravity(int gravity) { - if (gravity == 0) { - return ""; - } - - if ((gravity & (GRAVITY_CENTER_HORIZ | GRAVITY_CENTER_VERT)) == - (GRAVITY_CENTER_HORIZ | GRAVITY_CENTER_VERT)) { - return GRAVITY_VALUE_CENTER; - } - - StringBuilder sb = new StringBuilder(30); - int horizontal = gravity & GRAVITY_HORIZ_MASK; - int vertical = gravity & GRAVITY_VERT_MASK; - - if ((horizontal & (GRAVITY_LEFT|GRAVITY_START)) != 0) { - if ((horizontal & GRAVITY_LEFT) != 0) { - sb.append(GRAVITY_VALUE_LEFT); - } - if ((horizontal & GRAVITY_START) != 0) { - if (sb.length() > 0) { - sb.append('|'); - } - sb.append(GRAVITY_VALUE_START); - } - } else if ((horizontal & (GRAVITY_RIGHT|GRAVITY_END)) != 0) { - if ((horizontal & GRAVITY_RIGHT) != 0) { - sb.append(GRAVITY_VALUE_RIGHT); - } - if ((horizontal & GRAVITY_END) != 0) { - if (sb.length() > 0) { - sb.append('|'); - } - sb.append(GRAVITY_VALUE_END); - } - } else if ((horizontal & GRAVITY_CENTER_HORIZ) != 0) { - sb.append(GRAVITY_VALUE_CENTER_HORIZONTAL); - } else if ((horizontal & GRAVITY_FILL_HORIZ) != 0) { - sb.append(GRAVITY_VALUE_FILL_HORIZONTAL); - } - - if (sb.length() > 0 && vertical != 0) { - sb.append('|'); - } - - if ((vertical & GRAVITY_TOP) != 0) { - sb.append(GRAVITY_VALUE_TOP); - } else if ((vertical & GRAVITY_BOTTOM) != 0) { - sb.append(GRAVITY_VALUE_BOTTOM); - } else if ((vertical & GRAVITY_CENTER_VERT) != 0) { - sb.append(GRAVITY_VALUE_CENTER_VERTICAL); - } else if ((vertical & GRAVITY_FILL_VERT) != 0) { - sb.append(GRAVITY_VALUE_FILL_VERTICAL); - } - - return sb.toString(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java deleted file mode 100644 index 80a23c6db..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java +++ /dev/null @@ -1,676 +0,0 @@ -/* - * 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 static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN; -import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY; -import static com.android.SdkConstants.ATTR_LAYOUT_ROW; -import static com.android.SdkConstants.ATTR_ORIENTATION; -import static com.android.SdkConstants.FQCN_GRID_LAYOUT; -import static com.android.SdkConstants.FQCN_SPACE; -import static com.android.SdkConstants.FQCN_SPACE_V7; -import static com.android.SdkConstants.GRAVITY_VALUE_FILL; -import static com.android.SdkConstants.GRAVITY_VALUE_FILL_HORIZONTAL; -import static com.android.SdkConstants.GRAVITY_VALUE_FILL_VERTICAL; -import static com.android.SdkConstants.GRAVITY_VALUE_LEFT; -import static com.android.SdkConstants.GRID_LAYOUT; -import static com.android.SdkConstants.VALUE_HORIZONTAL; -import static com.android.SdkConstants.VALUE_TRUE; - -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.IDragElement; -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.IViewMetadata; -import com.android.ide.common.api.IViewMetadata.FillPreference; -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.RuleAction.Choices; -import com.android.ide.common.api.SegmentType; -import com.android.ide.common.layout.grid.GridDropHandler; -import com.android.ide.common.layout.grid.GridLayoutPainter; -import com.android.ide.common.layout.grid.GridModel; -import com.android.ide.common.layout.grid.GridModel.ViewData; -import com.android.utils.Pair; - -import java.net.URL; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * An {@link IViewRule} for android.widget.GridLayout which provides designtime - * interaction with GridLayouts. - * <p> - * TODO: - * <ul> - * <li>Handle multi-drag: preserving relative positions and alignments among dragged - * views. - * <li>Handle GridLayouts that have been configured in a vertical orientation. - * <li>Handle free-form editing GridLayouts that have been manually edited rather than - * built up using free-form editing (e.g. they might not follow the same spacing - * convention, might use weights etc) - * <li>Avoid setting row and column numbers on the actual elements if they can be skipped - * to make the XML leaner. - * </ul> - */ -public class GridLayoutRule extends BaseLayoutRule { - /** - * The size of the visual regular grid that we snap to (if {@link #sSnapToGrid} is set - */ - public static final int GRID_SIZE = 16; - - /** Standard gap between views */ - public static final int SHORT_GAP_DP = 16; - - /** - * The preferred margin size, in pixels - */ - public static final int MARGIN_SIZE = 32; - - /** - * Size in screen pixels in the IDE of the gutter shown for new rows and columns (in - * grid mode) - */ - private static final int NEW_CELL_WIDTH = 10; - - /** - * Maximum size of a widget relative to a cell which is allowed to fit into a cell - * (and thereby enlarge it) before it is spread with row or column spans. - */ - public static final double MAX_CELL_DIFFERENCE = 1.2; - - /** Whether debugging diagnostics is available in the toolbar */ - private static final boolean CAN_DEBUG = - VALUE_TRUE.equals(System.getenv("ADT_DEBUG_GRIDLAYOUT")); //$NON-NLS-1$ - - private static final String ACTION_ADD_ROW = "_addrow"; //$NON-NLS-1$ - private static final String ACTION_REMOVE_ROW = "_removerow"; //$NON-NLS-1$ - private static final String ACTION_ADD_COL = "_addcol"; //$NON-NLS-1$ - private static final String ACTION_REMOVE_COL = "_removecol"; //$NON-NLS-1$ - private static final String ACTION_ORIENTATION = "_orientation"; //$NON-NLS-1$ - private static final String ACTION_SHOW_STRUCTURE = "_structure"; //$NON-NLS-1$ - private static final String ACTION_GRID_MODE = "_gridmode"; //$NON-NLS-1$ - private static final String ACTION_SNAP = "_snap"; //$NON-NLS-1$ - private static final String ACTION_DEBUG = "_debug"; //$NON-NLS-1$ - - private static final URL ICON_HORIZONTAL = GridLayoutRule.class.getResource("hlinear.png"); //$NON-NLS-1$ - private static final URL ICON_VERTICAL = GridLayoutRule.class.getResource("vlinear.png"); //$NON-NLS-1$ - private static final URL ICON_ADD_ROW = GridLayoutRule.class.getResource("addrow.png"); //$NON-NLS-1$ - private static final URL ICON_REMOVE_ROW = GridLayoutRule.class.getResource("removerow.png"); //$NON-NLS-1$ - private static final URL ICON_ADD_COL = GridLayoutRule.class.getResource("addcol.png"); //$NON-NLS-1$ - private static final URL ICON_REMOVE_COL = GridLayoutRule.class.getResource("removecol.png"); //$NON-NLS-1$ - private static final URL ICON_SHOW_STRUCT = GridLayoutRule.class.getResource("showgrid.png"); //$NON-NLS-1$ - private static final URL ICON_GRID_MODE = GridLayoutRule.class.getResource("gridmode.png"); //$NON-NLS-1$ - private static final URL ICON_SNAP = GridLayoutRule.class.getResource("snap.png"); //$NON-NLS-1$ - - /** - * Whether the IDE should show diagnostics for debugging the grid layout - including - * spacers visibly in the outline, showing row and column numbers, and so on - */ - public static boolean sDebugGridLayout = CAN_DEBUG; - - /** Whether the structure (grid model) should be displayed persistently to the user */ - public static boolean sShowStructure = false; - - /** Whether the drop positions should snap to a regular grid */ - public static boolean sSnapToGrid = false; - - /** - * Whether the grid is edited in "grid mode" where the operations are row/column based - * rather than free-form - */ - public static boolean sGridMode = true; - - /** Constructs a new {@link GridLayoutRule} */ - public GridLayoutRule() { - } - - @Override - public void addLayoutActions( - @NonNull List<RuleAction> actions, - final @NonNull INode parentNode, - final @NonNull List<? extends INode> children) { - super.addLayoutActions(actions, parentNode, children); - - String namespace = getNamespace(parentNode); - Choices orientationAction = RuleAction.createChoices( - ACTION_ORIENTATION, - "Orientation", //$NON-NLS-1$ - new PropertyCallback(Collections.singletonList(parentNode), - "Change LinearLayout Orientation", namespace, 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, false); - orientationAction.setRadio(true); - actions.add(orientationAction); - - // Gravity and margins - if (children != null && children.size() > 0) { - actions.add(RuleAction.createSeparator(35)); - actions.add(createMarginAction(parentNode, children)); - actions.add(createGravityAction(children, ATTR_LAYOUT_GRAVITY)); - } - - IMenuCallback actionCallback = new IMenuCallback() { - @Override - public void action( - final @NonNull RuleAction action, - @NonNull List<? extends INode> selectedNodes, - final @Nullable String valueId, - final @Nullable Boolean newValue) { - parentNode.editXml("Add/Remove Row/Column", new INodeHandler() { - @Override - public void handle(@NonNull INode n) { - String id = action.getId(); - if (id.equals(ACTION_SHOW_STRUCTURE)) { - sShowStructure = !sShowStructure; - mRulesEngine.redraw(); - return; - } else if (id.equals(ACTION_GRID_MODE)) { - sGridMode = !sGridMode; - mRulesEngine.redraw(); - return; - } else if (id.equals(ACTION_SNAP)) { - sSnapToGrid = !sSnapToGrid; - mRulesEngine.redraw(); - return; - } else if (id.equals(ACTION_DEBUG)) { - sDebugGridLayout = !sDebugGridLayout; - mRulesEngine.layout(); - return; - } - - GridModel grid = GridModel.get(mRulesEngine, parentNode, null); - if (id.equals(ACTION_ADD_ROW)) { - grid.addRow(children); - } else if (id.equals(ACTION_REMOVE_ROW)) { - grid.removeRows(children); - } else if (id.equals(ACTION_ADD_COL)) { - grid.addColumn(children); - } else if (id.equals(ACTION_REMOVE_COL)) { - grid.removeColumns(children); - } - } - - }); - } - }; - - actions.add(RuleAction.createSeparator(142)); - - actions.add(RuleAction.createToggle(ACTION_GRID_MODE, "Grid Model Mode", - sGridMode, actionCallback, ICON_GRID_MODE, 145, false)); - - // Add and Remove Column actions only apply in Grid Mode - if (sGridMode) { - actions.add(RuleAction.createToggle(ACTION_SHOW_STRUCTURE, "Show Structure", - sShowStructure, actionCallback, ICON_SHOW_STRUCT, 147, false)); - - // Add Row and Add Column - actions.add(RuleAction.createSeparator(150)); - actions.add(RuleAction.createAction(ACTION_ADD_COL, "Add Column", actionCallback, - ICON_ADD_COL, 160, false /* supportsMultipleNodes */)); - actions.add(RuleAction.createAction(ACTION_ADD_ROW, "Add Row", actionCallback, - ICON_ADD_ROW, 165, false)); - - // Remove Row and Remove Column (if something is selected) - if (children != null && children.size() > 0) { - // TODO: Add "Merge Columns" and "Merge Rows" ? - - actions.add(RuleAction.createAction(ACTION_REMOVE_COL, "Remove Column", - actionCallback, ICON_REMOVE_COL, 170, false)); - actions.add(RuleAction.createAction(ACTION_REMOVE_ROW, "Remove Row", - actionCallback, ICON_REMOVE_ROW, 175, false)); - } - - actions.add(RuleAction.createSeparator(185)); - } else { - actions.add(RuleAction.createToggle(ACTION_SHOW_STRUCTURE, "Show Structure", - sShowStructure, actionCallback, ICON_SHOW_STRUCT, 190, false)); - - // Snap to Grid and Show Structure are only relevant in free form mode - actions.add(RuleAction.createToggle(ACTION_SNAP, "Snap to Grid", - sSnapToGrid, actionCallback, ICON_SNAP, 200, false)); - } - - // Temporary: Diagnostics for GridLayout - if (CAN_DEBUG) { - actions.add(RuleAction.createToggle(ACTION_DEBUG, "Debug", - sDebugGridLayout, actionCallback, null, 210, false)); - } - } - - /** - * Returns the orientation attribute value currently used by the node (even if not - * defined, in which case the default horizontal value is returned) - */ - private String getCurrentOrientation(final INode node) { - String orientation = node.getStringAttr(getNamespace(node), ATTR_ORIENTATION); - if (orientation == null || orientation.length() == 0) { - orientation = VALUE_HORIZONTAL; - } - return orientation; - } - - @Override - public DropFeedback onDropEnter(@NonNull INode targetNode, @Nullable Object targetView, - @Nullable IDragElement[] elements) { - GridDropHandler userData = new GridDropHandler(this, targetNode, targetView); - IFeedbackPainter painter = GridLayoutPainter.createDropFeedbackPainter(this, elements); - return new DropFeedback(userData, painter); - } - - @Override - public DropFeedback onDropMove(@NonNull INode targetNode, @NonNull IDragElement[] elements, - @Nullable DropFeedback feedback, @NonNull Point p) { - if (feedback == null) { - return null; - } - feedback.requestPaint = true; - - GridDropHandler handler = (GridDropHandler) feedback.userData; - handler.computeMatches(feedback, p); - - return feedback; - } - - @Override - public void onDropped(final @NonNull INode targetNode, final @NonNull IDragElement[] elements, - @Nullable DropFeedback feedback, @NonNull Point p) { - if (feedback == null) { - return; - } - - Rect b = targetNode.getBounds(); - if (!b.isValid()) { - return; - } - - GridDropHandler dropHandler = (GridDropHandler) feedback.userData; - if (dropHandler.getRowMatch() == null || dropHandler.getColumnMatch() == null) { - return; - } - - // Collect IDs from dropped elements and remap them to new IDs - // if this is a copy or from a different canvas. - Map<String, Pair<String, String>> idMap = getDropIdMap(targetNode, elements, - feedback.isCopy || !feedback.sameCanvas); - - for (IDragElement element : elements) { - INode newChild; - if (!sGridMode) { - newChild = dropHandler.handleFreeFormDrop(targetNode, element); - } else { - newChild = dropHandler.handleGridModeDrop(targetNode, element); - } - - // Copy all the attributes, modifying them as needed. - addAttributes(newChild, element, idMap, DEFAULT_ATTR_FILTER); - - addInnerElements(newChild, element, idMap); - } - } - - @Override - public void onChildInserted(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - if (insertType == InsertType.MOVE_WITHIN) { - // Don't adjust widths/heights/weights when just moving within a single layout - return; - } - - if (GridModel.isSpace(node.getFqcn())) { - return; - } - - // Attempt to set "fill" properties on newly added views such that for example - // a text field will stretch horizontally. - String fqcn = node.getFqcn(); - IViewMetadata metadata = mRulesEngine.getMetadata(fqcn); - FillPreference fill = metadata.getFillPreference(); - String gravity = computeDefaultGravity(fill); - if (gravity != null) { - node.setAttribute(getNamespace(parent), ATTR_LAYOUT_GRAVITY, gravity); - } - } - - /** - * Returns the namespace URI to use for GridLayout-specific attributes, such - * as columnCount, layout_column, layout_column_span, layout_gravity etc. - * - * @param layout the GridLayout instance to look up the namespace for - * @return the namespace, never null - */ - public String getNamespace(INode layout) { - String namespace = ANDROID_URI; - - String fqcn = layout.getFqcn(); - if (!fqcn.equals(GRID_LAYOUT) && !fqcn.equals(FQCN_GRID_LAYOUT)) { - namespace = mRulesEngine.getAppNameSpace(); - } - - return namespace; - } - - /** - * Computes the default gravity to be used for a widget of the given fill - * preference when added to a grid layout - * - * @param fill the fill preference for the widget - * @return the gravity value, or null, to be set on the widget - */ - public static String computeDefaultGravity(FillPreference fill) { - String horizontal = GRAVITY_VALUE_LEFT; - String vertical = null; - if (fill.fillHorizontally(true /*verticalContext*/)) { - horizontal = GRAVITY_VALUE_FILL_HORIZONTAL; - } - if (fill.fillVertically(true /*verticalContext*/)) { - vertical = GRAVITY_VALUE_FILL_VERTICAL; - } - String gravity; - if (horizontal == GRAVITY_VALUE_FILL_HORIZONTAL - && vertical == GRAVITY_VALUE_FILL_VERTICAL) { - gravity = GRAVITY_VALUE_FILL; - } else if (vertical != null) { - gravity = horizontal + '|' + vertical; - } else { - gravity = horizontal; - } - - return gravity; - } - - @Override - public void onRemovingChildren(@NonNull List<INode> deleted, @NonNull INode parent, - boolean moved) { - super.onRemovingChildren(deleted, parent, moved); - - if (!sGridMode) { - // Attempt to clean up spacer objects for any newly-empty rows or columns - // as the result of this deletion - GridModel grid = GridModel.get(mRulesEngine, parent, null); - grid.onDeleted(deleted); - } - } - - @Override - protected void paintResizeFeedback(IGraphics gc, INode node, ResizeState state) { - if (!sGridMode) { - GridModel grid = getGrid(state); - GridLayoutPainter.paintResizeFeedback(gc, state.layout, grid); - } - - if (resizingWidget(state)) { - super.paintResizeFeedback(gc, node, state); - } else { - GridModel grid = getGrid(state); - int startColumn = grid.getColumn(state.bounds.x); - int endColumn = grid.getColumn(state.bounds.x2()); - int columnSpan = endColumn - startColumn + 1; - - int startRow = grid.getRow(state.bounds.y); - int endRow = grid.getRow(state.bounds.y2()); - int rowSpan = endRow - startRow + 1; - - Rect cellBounds = grid.getCellBounds(startRow, startColumn, rowSpan, columnSpan); - gc.useStyle(DrawingStyle.RESIZE_PREVIEW); - gc.drawRect(cellBounds); - } - } - - /** Returns the grid size cached on the given {@link ResizeState} object */ - private GridModel getGrid(ResizeState resizeState) { - GridModel grid = (GridModel) resizeState.clientData; - if (grid == null) { - grid = GridModel.get(mRulesEngine, resizeState.layout, resizeState.layoutView); - resizeState.clientData = grid; - } - - return grid; - } - - @Override - protected void setNewSizeBounds(ResizeState state, INode node, INode layout, - Rect oldBounds, Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) { - - if (resizingWidget(state)) { - if (state.fillWidth || state.fillHeight || state.wrapWidth || state.wrapHeight) { - GridModel grid = getGrid(state); - ViewData view = grid.getView(node); - if (view != null) { - String gravityString = grid.getGridAttribute(view.node, ATTR_LAYOUT_GRAVITY); - int gravity = GravityHelper.getGravity(gravityString, 0); - if (view.column > 0 && verticalEdge != null && state.fillWidth) { - state.fillWidth = false; - state.wrapWidth = true; - gravity &= ~GravityHelper.GRAVITY_HORIZ_MASK; - gravity |= GravityHelper.GRAVITY_FILL_HORIZ; - } else if (verticalEdge != null && state.wrapWidth) { - gravity &= ~GravityHelper.GRAVITY_HORIZ_MASK; - gravity |= GravityHelper.GRAVITY_LEFT; - } - if (view.row > 0 && horizontalEdge != null && state.fillHeight) { - state.fillHeight = false; - state.wrapHeight = true; - gravity &= ~GravityHelper.GRAVITY_VERT_MASK; - gravity |= GravityHelper.GRAVITY_FILL_VERT; - } else if (horizontalEdge != null && state.wrapHeight) { - gravity &= ~GravityHelper.GRAVITY_VERT_MASK; - gravity |= GravityHelper.GRAVITY_TOP; - } - gravityString = GravityHelper.getGravity(gravity); - grid.setGridAttribute(view.node, ATTR_LAYOUT_GRAVITY, gravityString); - // Fall through and set layout_width and/or layout_height to wrap_content - } - } - super.setNewSizeBounds(state, node, layout, oldBounds, newBounds, horizontalEdge, - verticalEdge); - } else { - Pair<Integer, Integer> spans = computeResizeSpans(state); - int rowSpan = spans.getFirst(); - int columnSpan = spans.getSecond(); - GridModel grid = getGrid(state); - grid.setColumnSpanAttribute(node, columnSpan); - grid.setRowSpanAttribute(node, rowSpan); - - ViewData view = grid.getView(node); - if (view != null) { - String gravityString = grid.getGridAttribute(view.node, ATTR_LAYOUT_GRAVITY); - int gravity = GravityHelper.getGravity(gravityString, 0); - if (verticalEdge != null && columnSpan > 1) { - gravity &= ~GravityHelper.GRAVITY_HORIZ_MASK; - gravity |= GravityHelper.GRAVITY_FILL_HORIZ; - } - if (horizontalEdge != null && rowSpan > 1) { - gravity &= ~GravityHelper.GRAVITY_VERT_MASK; - gravity |= GravityHelper.GRAVITY_FILL_VERT; - } - gravityString = GravityHelper.getGravity(gravity); - grid.setGridAttribute(view.node, ATTR_LAYOUT_GRAVITY, gravityString); - } - } - } - - @Override - protected String getResizeUpdateMessage(ResizeState state, INode child, INode parent, - Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) { - Pair<Integer, Integer> spans = computeResizeSpans(state); - if (resizingWidget(state)) { - String width = state.getWidthAttribute(); - String height = state.getHeightAttribute(); - - String message; - if (horizontalEdge == null) { - message = width; - } else if (verticalEdge == null) { - message = height; - } else { - // U+00D7: Unicode for multiplication sign - message = String.format("%s \u00D7 %s", width, height); - } - - // Tack on a tip about using the Shift modifier key - return String.format("%s\n(Press Shift to resize row/column spans)", message); - } else { - int rowSpan = spans.getFirst(); - int columnSpan = spans.getSecond(); - return String.format("ColumnSpan=%d, RowSpan=%d\n(Release Shift to resize widget itself)", - columnSpan, rowSpan); - } - } - - /** - * Returns true if we're resizing the widget, and false if we're resizing the cell - * spans - */ - private static boolean resizingWidget(ResizeState state) { - return (state.modifierMask & DropFeedback.MODIFIER2) == 0; - } - - /** - * Computes the new column and row spans as the result of the current resizing - * operation - */ - private Pair<Integer, Integer> computeResizeSpans(ResizeState state) { - GridModel grid = getGrid(state); - - int startColumn = grid.getColumn(state.bounds.x); - int endColumn = grid.getColumn(state.bounds.x2()); - int columnSpan = endColumn - startColumn + 1; - - int startRow = grid.getRow(state.bounds.y); - int endRow = grid.getRow(state.bounds.y2()); - int rowSpan = endRow - startRow + 1; - - return Pair.of(rowSpan, columnSpan); - } - - /** - * Returns the size of the new cell gutter in layout coordinates - * - * @return the size of the new cell gutter in layout coordinates - */ - public int getNewCellSize() { - return mRulesEngine.screenToLayout(NEW_CELL_WIDTH / 2); - } - - @Override - public void paintSelectionFeedback(@NonNull IGraphics graphics, @NonNull INode parentNode, - @NonNull List<? extends INode> childNodes, @Nullable Object view) { - super.paintSelectionFeedback(graphics, parentNode, childNodes, view); - - if (sShowStructure) { - // TODO: Cache the grid - if (view != null) { - if (GridLayoutPainter.paintStructure(view, DrawingStyle.GUIDELINE_DASHED, - parentNode, graphics)) { - return; - } - } - GridLayoutPainter.paintStructure(DrawingStyle.GUIDELINE_DASHED, - parentNode, graphics, GridModel.get(mRulesEngine, parentNode, view)); - } else if (sDebugGridLayout) { - GridLayoutPainter.paintStructure(DrawingStyle.GRID, - parentNode, graphics, GridModel.get(mRulesEngine, parentNode, view)); - } - - // TBD: Highlight the cells around the selection, and display easy controls - // for for example tweaking the rowspan/colspan of a cell? (but only in grid mode) - } - - /** - * Paste into a GridLayout. We have several possible behaviors (and many - * more than are listed here): - * <ol> - * <li> Preserve the current positions of the elements (if pasted from another - * canvas, not just XML markup copied from say a web site) and apply those - * into the current grid. This might mean "overwriting" (sitting on top of) - * existing elements. - * <li> Fill available "holes" in the grid. - * <li> Lay them out consecutively, row by row, like text. - * <li> Some hybrid approach, where I attempt to preserve the <b>relative</b> - * relationships (columns/wrapping, spacing between the pasted views etc) - * but I append them to the bottom of the layout on one or more new rows. - * <li> Try to paste at the current mouse position, if known, preserving the - * relative distances between the existing elements there. - * </ol> - * Attempting to preserve the current position isn't possible right now, - * because the clipboard data contains only the textual representation of - * the markup. (We'd need to stash position information from a previous - * layout render along with the clipboard data). - * <p> - * Currently, this implementation simply lays out the elements row by row, - * approach #3 above. - */ - @Override - public void onPaste( - @NonNull INode targetNode, - @Nullable Object targetView, - @NonNull IDragElement[] elements) { - DropFeedback feedback = onDropEnter(targetNode, targetView, elements); - if (feedback != null) { - Rect b = targetNode.getBounds(); - if (!b.isValid()) { - return; - } - - Map<String, Pair<String, String>> idMap = getDropIdMap(targetNode, elements, - true /* remap id's */); - - for (IDragElement element : elements) { - // Skip <Space> elements and only insert the real elements being - // copied - if (elements.length > 1 && (FQCN_SPACE.equals(element.getFqcn()) - || FQCN_SPACE_V7.equals(element.getFqcn()))) { - continue; - } - - String fqcn = element.getFqcn(); - INode newChild = targetNode.appendChild(fqcn); - addAttributes(newChild, element, idMap, DEFAULT_ATTR_FILTER); - - // Ensure that we reset any potential row/column attributes from a different - // grid layout being copied from - GridDropHandler handler = (GridDropHandler) feedback.userData; - GridModel grid = handler.getGrid(); - grid.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN, null); - grid.setGridAttribute(newChild, ATTR_LAYOUT_ROW, null); - - // TODO: Set columnSpans to avoid making these widgets completely - // break the layout - // Alternatively, I could just lay them all out on subsequent lines - // with a column span of columnSpan5 - - addInnerElements(newChild, element, idMap); - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridViewRule.java deleted file mode 100644 index b82f391b4..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridViewRule.java +++ /dev/null @@ -1,41 +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_LAYOUT_WIDTH; -import static com.android.SdkConstants.ATTR_NUM_COLUMNS; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for android.widget.GridView - */ -public class GridViewRule extends BaseViewRule { - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, getFillParentValueName()); - node.setAttribute(ANDROID_URI, ATTR_NUM_COLUMNS, "3"); //$NON-NLS-1$ - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/HorizontalScrollViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/HorizontalScrollViewRule.java deleted file mode 100644 index 722949051..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/HorizontalScrollViewRule.java +++ /dev/null @@ -1,96 +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_LAYOUT_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; -import static com.android.SdkConstants.ATTR_ORIENTATION; -import static com.android.SdkConstants.FQCN_LINEAR_LAYOUT; -import static com.android.SdkConstants.VALUE_HORIZONTAL; - -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.IDragElement; -import com.android.ide.common.api.IGraphics; -import com.android.ide.common.api.INode; -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; - -/** - * An {@link IViewRule} for android.widget.HorizontalScrollView. - */ -public class HorizontalScrollViewRule extends FrameLayoutRule { - - @Override - public void onChildInserted(@NonNull INode child, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onChildInserted(child, parent, insertType); - - // The child of the ScrollView should fill in both directions - String fillParent = getFillParentValueName(); - child.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, fillParent); - child.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, fillParent); - } - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - if (insertType.isCreate()) { - // Insert a horizontal linear layout which is commonly used with horizontal scrollbars - // as described by the documentation for HorizontalScrollbars. - INode linearLayout = node.appendChild(FQCN_LINEAR_LAYOUT); - linearLayout.setAttribute(ANDROID_URI, ATTR_ORIENTATION, - VALUE_HORIZONTAL); - } - } - - @Override - public DropFeedback onDropMove(@NonNull INode targetNode, @NonNull IDragElement[] elements, - @Nullable DropFeedback feedback, @NonNull Point p) { - DropFeedback f = super.onDropMove(targetNode, elements, feedback, p); - - // HorizontalScrollViews only allow a single child - if (targetNode.getChildren().length > 0) { - f.invalidTarget = true; - } - return f; - } - - @Override - protected void drawFeedback( - IGraphics gc, - INode targetNode, - IDragElement[] elements, - DropFeedback feedback) { - if (targetNode.getChildren().length > 0) { - Rect b = targetNode.getBounds(); - if (b.isValid()) { - gc.useStyle(DrawingStyle.DROP_RECIPIENT); - gc.drawRect(b); - } - } else { - super.drawFeedback(gc, targetNode, elements, feedback); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/IgnoredLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/IgnoredLayoutRule.java deleted file mode 100644 index 3a65a8601..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/IgnoredLayoutRule.java +++ /dev/null @@ -1,46 +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 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.INode; - -/** - * An ignored layout is a layout that should not be treated as a layout by the - * visual editor (usually because the widget extends a layout class we recognize - * and support, but where the widget is more restrictive in how it manages its - * children so we don't want to expose the normal configuration options). - * <p> - * For example, the ZoomControls widget is not user-configurable as a - * LinearLayout even though it extends it. Our ZoomControls rule is therefore a - * subclass of this {@link IgnoredLayoutRule} class. - */ -public abstract class IgnoredLayoutRule extends BaseLayoutRule { - @Override - public DropFeedback onDropEnter(@NonNull INode targetNode, @Nullable Object targetView, - @Nullable IDragElement[] elements) { - // Do nothing; this layout rule corresponds to a layout that - // should not be handled as a layout by the visual editor - usually - // because some widget is extending a layout for implementation purposes - // but does not want to expose configurability of the base layout in the - // editor. - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageButtonRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageButtonRule.java deleted file mode 100644 index 990795091..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageButtonRule.java +++ /dev/null @@ -1,54 +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_SRC; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for android.widget.ImageButton. - */ -public class ImageButtonRule extends BaseViewRule { - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - // When dropping an include tag, ask the user which layout to include. - if (insertType == InsertType.CREATE) { // NOT InsertType.CREATE_PREVIEW - String src = mRulesEngine.displayResourceInput("drawable", ""); //$NON-NLS-1$ //$NON-NLS-2$ - if (src != null) { - node.editXml("Set Image", - new PropertySettingNodeHandler(ANDROID_URI, ATTR_SRC, - src.length() > 0 ? src : null)); - return; - } - } - - // Fallback if dismissed or during previews etc - if (insertType.isCreate()) { - node.setAttribute(ANDROID_URI, ATTR_SRC, getSampleImageSrc()); - } - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageViewRule.java deleted file mode 100644 index bc0184c4f..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ImageViewRule.java +++ /dev/null @@ -1,57 +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_SRC; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for android.widget.ImageViewRule. - */ -public class ImageViewRule extends BaseViewRule { - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - // When dropping an include tag, ask the user which layout to include. - if (insertType == InsertType.CREATE) { // NOT InsertType.CREATE_PREVIEW - String src = mRulesEngine.displayResourceInput("drawable", ""); //$NON-NLS-1$ //$NON-NLS-2$ - if (src != null) { - node.editXml("Set Image", - new PropertySettingNodeHandler(ANDROID_URI, ATTR_SRC, - src.length() > 0 ? src : null)); - return; - } else { - // Remove the view; the insertion was canceled - parent.removeChild(node); - } - } - - // Fallback if dismissed or during previews etc - if (insertType.isCreate()) { - node.setAttribute(ANDROID_URI, ATTR_SRC, getSampleImageSrc()); - } - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/IncludeRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/IncludeRule.java deleted file mode 100644 index fcb1a6dac..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/IncludeRule.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 static com.android.SdkConstants.ATTR_LAYOUT; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for the special XML {@code <include>} tag. - */ -public class IncludeRule extends BaseViewRule { - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - // When dropping an include tag, ask the user which layout to include. - if (insertType == InsertType.CREATE) { // NOT InsertType.CREATE_PREVIEW - String include = mRulesEngine.displayIncludeSourceInput(); - if (include != null) { - node.editXml("Include Layout", - // Note -- the layout attribute is NOT in the Android namespace! - new PropertySettingNodeHandler(null, ATTR_LAYOUT, - include.length() > 0 ? include : null)); - } else { - // Remove the view; the insertion was canceled - parent.removeChild(node); - } - } - } -} 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 deleted file mode 100644 index 610fe5d8b..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java +++ /dev/null @@ -1,1092 +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_BASELINE_ALIGNED; -import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY; -import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_WEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; -import static com.android.SdkConstants.ATTR_ORIENTATION; -import static com.android.SdkConstants.ATTR_WEIGHT_SUM; -import static com.android.SdkConstants.VALUE_1; -import static com.android.SdkConstants.VALUE_HORIZONTAL; -import static com.android.SdkConstants.VALUE_VERTICAL; -import static com.android.SdkConstants.VALUE_WRAP_CONTENT; -import static com.android.SdkConstants.VALUE_ZERO_DP; -import static com.android.ide.eclipse.adt.AdtUtils.formatFloatAttribute; - -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.IClientRulesEngine; -import com.android.ide.common.api.IDragElement; -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.IViewMetadata; -import com.android.ide.common.api.IViewMetadata.FillPreference; -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.RuleAction.Choices; -import com.android.ide.common.api.SegmentType; -import com.android.ide.eclipse.adt.AdtPlugin; - -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.LinearLayout and all its derived - * 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 String ACTION_CLEAR = "_clear"; //$NON-NLS-1$ - private static final String ACTION_DOMINATE = "_dominate"; //$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$ - private static final URL ICON_CLEAR_WEIGHTS = - LinearLayoutRule.class.getResource("clearweights.png"); //$NON-NLS-1$ - private static final URL ICON_DOMINATE = - LinearLayoutRule.class.getResource("allweight.png"); //$NON-NLS-1$ - - /** - * 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 - */ - protected boolean isVertical(INode node) { - // Horizontal is the default, so if no value is specified it is horizontal. - return VALUE_VERTICAL.equals(node.getStringAttr(ANDROID_URI, - ATTR_ORIENTATION)); - } - - /** - * Returns true if this LinearLayout supports switching orientation. - * - * @return true if this layout supports orientations - */ - protected boolean supportsOrientation() { - return true; - } - - @Override - public void addLayoutActions( - @NonNull List<RuleAction> actions, - final @NonNull INode parentNode, - final @NonNull List<? extends INode> children) { - super.addLayoutActions(actions, parentNode, children); - if (supportsOrientation()) { - Choices action = RuleAction.createChoices( - ACTION_ORIENTATION, "Orientation", //$NON-NLS-1$ - 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, - false /* supportsMultipleNodes */ - ); - action.setRadio(true); - actions.add(action); - } - if (!isVertical(parentNode)) { - String current = parentNode.getStringAttr(ANDROID_URI, ATTR_BASELINE_ALIGNED); - boolean isAligned = current == null || Boolean.valueOf(current); - actions.add(RuleAction.createToggle(ACTION_BASELINE, "Toggle Baseline Alignment", - isAligned, - new PropertyCallback(Collections.singletonList(parentNode), - "Change Baseline Alignment", - ANDROID_URI, ATTR_BASELINE_ALIGNED), // TODO: Also set index? - ICON_BASELINE, 38, false)); - } - - // Gravity - if (children != null && children.size() > 0) { - actions.add(RuleAction.createSeparator(35)); - - // Margins - actions.add(createMarginAction(parentNode, children)); - - // Gravity - actions.add(createGravityAction(children, ATTR_LAYOUT_GRAVITY)); - - // Weights - IMenuCallback actionCallback = new IMenuCallback() { - @Override - public void action( - final @NonNull RuleAction action, - @NonNull List<? extends INode> selectedNodes, - final @Nullable String valueId, - final @Nullable Boolean newValue) { - parentNode.editXml("Change Weight", new INodeHandler() { - @Override - public void handle(@NonNull INode n) { - String id = action.getId(); - if (id.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) { - if (weight.isEmpty()) { - weight = null; // remove attribute - } - for (INode child : children) { - child.setAttribute(ANDROID_URI, - ATTR_LAYOUT_WEIGHT, weight); - } - } - } else if (id.equals(ACTION_DISTRIBUTE)) { - distributeWeights(parentNode, parentNode.getChildren()); - } else if (id.equals(ACTION_CLEAR)) { - clearWeights(parentNode); - } else if (id.equals(ACTION_CLEAR) || id.equals(ACTION_DOMINATE)) { - clearWeights(parentNode); - distributeWeights(parentNode, - children.toArray(new INode[children.size()])); - } else { - assert id.equals(ACTION_BASELINE); - } - } - }); - } - }; - actions.add(RuleAction.createSeparator(50)); - actions.add(RuleAction.createAction(ACTION_DISTRIBUTE, "Distribute Weights Evenly", - actionCallback, ICON_DISTRIBUTE, 60, false /*supportsMultipleNodes*/)); - actions.add(RuleAction.createAction(ACTION_DOMINATE, "Assign All Weight", - actionCallback, ICON_DOMINATE, 70, false)); - actions.add(RuleAction.createAction(ACTION_WEIGHT, "Change Layout Weight", - actionCallback, ICON_WEIGHTS, 80, false)); - actions.add(RuleAction.createAction(ACTION_CLEAR, "Clear All Weights", - actionCallback, ICON_CLEAR_WEIGHTS, 90, false)); - } - } - - private void distributeWeights(INode parentNode, INode[] targets) { - // 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 - } - } - 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 = formatFloatAttribute((float) share); - String sizeAttribute = isVertical(parentNode) ? - ATTR_LAYOUT_HEIGHT : ATTR_LAYOUT_WIDTH; - for (INode target : targets) { - target.setAttribute(ANDROID_URI, ATTR_LAYOUT_WEIGHT, value); - // Also set the width/height to 0dp to ensure actual equal - // size (without this, only the remaining space is - // distributed) - if (VALUE_WRAP_CONTENT.equals(target.getStringAttr(ANDROID_URI, sizeAttribute))) { - target.setAttribute(ANDROID_URI, sizeAttribute, VALUE_ZERO_DP); - } - } - } - - private void clearWeights(INode parentNode) { - // Clear attributes - String sizeAttribute = isVertical(parentNode) - ? ATTR_LAYOUT_HEIGHT : ATTR_LAYOUT_WIDTH; - for (INode target : parentNode.getChildren()) { - target.setAttribute(ANDROID_URI, ATTR_LAYOUT_WEIGHT, null); - String size = target.getStringAttr(ANDROID_URI, sizeAttribute); - if (size != null && size.startsWith("0")) { //$NON-NLS-1$ - target.setAttribute(ANDROID_URI, sizeAttribute, VALUE_WRAP_CONTENT); - } - } - } - - // ==== Drag'n'drop support ==== - - @Override - public DropFeedback onDropEnter(final @NonNull INode targetNode, @Nullable Object targetView, - final @Nullable IDragElement[] elements) { - - if (elements.length == 0) { - return null; - } - - Rect bn = targetNode.getBounds(); - if (!bn.isValid()) { - return null; - } - - boolean isVertical = isVertical(targetNode); - - // Prepare a list of insertion points: X coords for horizontal, Y for - // vertical. - List<MatchPos> indexes = new ArrayList<MatchPos>(); - - int last = isVertical ? bn.y : bn.x; - int pos = 0; - boolean lastDragged = false; - int selfPos = -1; - for (INode it : targetNode.getChildren()) { - Rect bc = it.getBounds(); - if (bc.isValid()) { - // First see if this node looks like it's the same as one of the - // *dragged* bounds - boolean isDragged = false; - for (IDragElement element : elements) { - // This tries to determine if an INode corresponds to an - // IDragElement, by comparing their bounds. - if (element.isSame(it)) { - isDragged = true; - break; - } - } - - // We don't want to insert drag positions before or after the - // element that is itself being dragged. However, we -do- want - // to insert a match position here, at the center, such that - // when you drag near its current position we show a match right - // where it's already positioned. - if (isDragged) { - int v = isVertical ? bc.y + (bc.h / 2) : bc.x + (bc.w / 2); - selfPos = pos; - indexes.add(new MatchPos(v, pos++)); - } else if (lastDragged) { - // Even though we don't want to insert a match below, we - // need to increment the index counter such that subsequent - // lines know their correct index in the child list. - pos++; - } else { - // Add an insertion point between the last point and the - // start of this child - int v = isVertical ? bc.y : bc.x; - v = (last + v) / 2; - indexes.add(new MatchPos(v, pos++)); - } - - last = isVertical ? (bc.y + bc.h) : (bc.x + bc.w); - lastDragged = isDragged; - } else { - // We still have to count this position even if it has no bounds, or - // subsequent children will be inserted at the wrong place - pos++; - } - } - - // Finally add an insert position after all the children - unless of - // course we happened to be dragging the last element - if (!lastDragged) { - int v = last + 1; - indexes.add(new MatchPos(v, pos)); - } - - int posCount = targetNode.getChildren().length + 1; - return new DropFeedback(new LinearDropData(indexes, posCount, isVertical, selfPos), - new IFeedbackPainter() { - - @Override - public void paint(@NonNull IGraphics gc, @NonNull INode node, - @NonNull DropFeedback feedback) { - // Paint callback for the LinearLayout. This is called - // by the canvas when a draw is needed. - drawFeedback(gc, node, elements, feedback); - } - }); - } - - void drawFeedback(IGraphics gc, INode node, IDragElement[] elements, DropFeedback feedback) { - Rect b = node.getBounds(); - if (!b.isValid()) { - return; - } - - // Highlight the receiver - gc.useStyle(DrawingStyle.DROP_RECIPIENT); - gc.drawRect(b); - - gc.useStyle(DrawingStyle.DROP_ZONE); - - LinearDropData data = (LinearDropData) feedback.userData; - boolean isVertical = data.isVertical(); - int selfPos = data.getSelfPos(); - - for (MatchPos it : data.getIndexes()) { - int i = it.getDistance(); - int pos = it.getPosition(); - // Don't show insert drop zones for "self"-index since that one goes - // right through the center of the widget rather than in a sibling - // position - if (pos != selfPos) { - if (isVertical) { - // draw horizontal lines - gc.drawLine(b.x, i, b.x + b.w, i); - } else { - // draw vertical lines - gc.drawLine(i, b.y, i, b.y + b.h); - } - } - } - - Integer currX = data.getCurrX(); - Integer currY = data.getCurrY(); - - if (currX != null && currY != null) { - gc.useStyle(DrawingStyle.DROP_ZONE_ACTIVE); - - int x = currX; - int y = currY; - - Rect be = elements[0].getBounds(); - - // Draw a clear line at the closest drop zone (unless we're over the - // dragged element itself) - if (data.getInsertPos() != selfPos || selfPos == -1) { - gc.useStyle(DrawingStyle.DROP_PREVIEW); - if (data.getWidth() != null) { - int width = data.getWidth(); - int fromX = x - width / 2; - int toX = x + width / 2; - gc.drawLine(fromX, y, toX, y); - } else if (data.getHeight() != null) { - int height = data.getHeight(); - int fromY = y - height / 2; - int toY = y + height / 2; - gc.drawLine(x, fromY, x, toY); - } - } - - if (be.isValid()) { - boolean isLast = data.isLastPosition(); - - // At least the first element has a bound. Draw rectangles for - // all dropped elements with valid bounds, offset at the drop - // point. - int offsetX; - int offsetY; - if (isVertical) { - offsetX = b.x - be.x; - offsetY = currY - be.y - (isLast ? 0 : (be.h / 2)); - - } else { - offsetX = currX - be.x - (isLast ? 0 : (be.w / 2)); - offsetY = b.y - be.y; - } - - gc.useStyle(DrawingStyle.DROP_PREVIEW); - for (IDragElement element : elements) { - Rect bounds = element.getBounds(); - if (bounds.isValid() && (bounds.w > b.w || bounds.h > b.h) && - node.getChildren().length == 0) { - // The bounds of the child does not fully fit inside the target. - // Limit the bounds to the layout bounds (but only when there - // are no children, since otherwise positioning around the existing - // children gets difficult) - final int px, py, pw, ph; - if (bounds.w > b.w) { - px = b.x; - pw = b.w; - } else { - px = bounds.x + offsetX; - pw = bounds.w; - } - if (bounds.h > b.h) { - py = b.y; - ph = b.h; - } else { - py = bounds.y + offsetY; - ph = bounds.h; - } - Rect within = new Rect(px, py, pw, ph); - gc.drawRect(within); - } else { - drawElement(gc, element, offsetX, offsetY); - } - } - } - } - } - - @Override - public DropFeedback onDropMove(@NonNull INode targetNode, @NonNull IDragElement[] elements, - @Nullable DropFeedback feedback, @NonNull Point p) { - Rect b = targetNode.getBounds(); - if (!b.isValid()) { - return feedback; - } - - LinearDropData data = (LinearDropData) feedback.userData; - boolean isVertical = data.isVertical(); - - int bestDist = Integer.MAX_VALUE; - int bestIndex = Integer.MIN_VALUE; - Integer bestPos = null; - - for (MatchPos index : data.getIndexes()) { - int i = index.getDistance(); - int pos = index.getPosition(); - int dist = (isVertical ? p.y : p.x) - i; - if (dist < 0) - dist = -dist; - if (dist < bestDist) { - bestDist = dist; - bestIndex = i; - bestPos = pos; - if (bestDist <= 0) - break; - } - } - - if (bestIndex != Integer.MIN_VALUE) { - Integer oldX = data.getCurrX(); - Integer oldY = data.getCurrY(); - - if (isVertical) { - data.setCurrX(b.x + b.w / 2); - data.setCurrY(bestIndex); - data.setWidth(b.w); - data.setHeight(null); - } else { - data.setCurrX(bestIndex); - data.setCurrY(b.y + b.h / 2); - data.setWidth(null); - data.setHeight(b.h); - } - - data.setInsertPos(bestPos); - - feedback.requestPaint = !equals(oldX, data.getCurrX()) - || !equals(oldY, data.getCurrY()); - } - - return feedback; - } - - private static boolean equals(Integer i1, Integer i2) { - if (i1 == i2) { - return true; - } else if (i1 != null) { - return i1.equals(i2); - } else { - // We know i2 != null - return i2.equals(i1); - } - } - - @Override - public void onDropLeave(@NonNull INode targetNode, @NonNull IDragElement[] elements, - @Nullable DropFeedback feedback) { - // ignore - } - - @Override - public void onDropped(final @NonNull INode targetNode, final @NonNull IDragElement[] elements, - final @Nullable DropFeedback feedback, final @NonNull Point p) { - - LinearDropData data = (LinearDropData) feedback.userData; - final int initialInsertPos = data.getInsertPos(); - insertAt(targetNode, elements, feedback.isCopy || !feedback.sameCanvas, initialInsertPos); - } - - @Override - public void onChildInserted(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - if (insertType == InsertType.MOVE_WITHIN) { - // Don't adjust widths/heights/weights when just moving within a single - // LinearLayout - return; - } - - // Attempt to set fill-properties on newly added views such that for example, - // in a vertical layout, a text field defaults to filling horizontally, but not - // vertically. - String fqcn = node.getFqcn(); - IViewMetadata metadata = mRulesEngine.getMetadata(fqcn); - if (metadata != null) { - boolean vertical = isVertical(parent); - FillPreference fill = metadata.getFillPreference(); - String fillParent = getFillParentValueName(); - if (fill.fillHorizontally(vertical)) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, fillParent); - } else if (!vertical && fill == FillPreference.WIDTH_IN_VERTICAL) { - // In a horizontal layout, make views that would fill horizontally in a - // vertical layout have a non-zero weight instead. This will make the item - // fill but only enough to allow other views to be shown as well. - // (However, for drags within the same layout we do not touch - // the weight, since it might already have been tweaked to a particular - // value) - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WEIGHT, VALUE_1); - } - if (fill.fillVertically(vertical)) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, fillParent); - } - } - - // If you insert into a layout that already is using layout weights, - // and all the layout weights are the same (nonzero) value, then use - // the same weight for this new layout as well. Also duplicate the 0dip/0px/0dp - // sizes, if used. - boolean duplicateWeight = true; - boolean duplicate0dip = true; - String sameWeight = null; - String sizeAttribute = isVertical(parent) ? ATTR_LAYOUT_HEIGHT : ATTR_LAYOUT_WIDTH; - for (INode target : parent.getChildren()) { - if (target == node) { - continue; - } - String weight = target.getStringAttr(ANDROID_URI, ATTR_LAYOUT_WEIGHT); - if (weight == null || weight.length() == 0) { - duplicateWeight = false; - break; - } else if (sameWeight != null && !sameWeight.equals(weight)) { - duplicateWeight = false; - } else { - sameWeight = weight; - } - String size = target.getStringAttr(ANDROID_URI, sizeAttribute); - if (size != null && !size.startsWith("0")) { //$NON-NLS-1$ - duplicate0dip = false; - break; - } - } - if (duplicateWeight && sameWeight != null) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WEIGHT, sameWeight); - if (duplicate0dip) { - node.setAttribute(ANDROID_URI, sizeAttribute, VALUE_ZERO_DP); - } - } - } - - /** A possible match position */ - private static class MatchPos { - /** The pixel distance */ - private int mDistance; - /** The position among siblings */ - private int mPosition; - - public MatchPos(int distance, int position) { - mDistance = distance; - mPosition = position; - } - - @Override - public String toString() { - return "MatchPos [distance=" + mDistance //$NON-NLS-1$ - + ", position=" + mPosition //$NON-NLS-1$ - + "]"; //$NON-NLS-1$ - } - - private int getDistance() { - return mDistance; - } - - private int getPosition() { - return mPosition; - } - } - - private static class LinearDropData { - /** Vertical layout? */ - private final boolean mVertical; - - /** Insert points (pixels + index) */ - private final List<MatchPos> mIndexes; - - /** Number of insert positions in the target node */ - private final int mNumPositions; - - /** Current marker X position */ - private Integer mCurrX; - - /** Current marker Y position */ - private Integer mCurrY; - - /** Position of the dragged element in this layout (or - -1 if the dragged element is from elsewhere) */ - private final int mSelfPos; - - /** Current drop insert index (-1 for "at the end") */ - private int mInsertPos = -1; - - /** width of match line if it's a horizontal one */ - private Integer mWidth; - - /** height of match line if it's a vertical one */ - private Integer mHeight; - - public LinearDropData(List<MatchPos> indexes, int numPositions, - boolean isVertical, int selfPos) { - mIndexes = indexes; - mNumPositions = numPositions; - mVertical = isVertical; - mSelfPos = selfPos; - } - - @Override - public String toString() { - return "LinearDropData [currX=" + mCurrX //$NON-NLS-1$ - + ", currY=" + mCurrY //$NON-NLS-1$ - + ", height=" + mHeight //$NON-NLS-1$ - + ", indexes=" + mIndexes //$NON-NLS-1$ - + ", insertPos=" + mInsertPos //$NON-NLS-1$ - + ", isVertical=" + mVertical //$NON-NLS-1$ - + ", selfPos=" + mSelfPos //$NON-NLS-1$ - + ", width=" + mWidth //$NON-NLS-1$ - + "]"; //$NON-NLS-1$ - } - - private boolean isVertical() { - return mVertical; - } - - private void setCurrX(Integer currX) { - mCurrX = currX; - } - - private Integer getCurrX() { - return mCurrX; - } - - private void setCurrY(Integer currY) { - mCurrY = currY; - } - - private Integer getCurrY() { - return mCurrY; - } - - private int getSelfPos() { - return mSelfPos; - } - - private void setInsertPos(int insertPos) { - mInsertPos = insertPos; - } - - private int getInsertPos() { - return mInsertPos; - } - - private List<MatchPos> getIndexes() { - return mIndexes; - } - - private void setWidth(Integer width) { - mWidth = width; - } - - private Integer getWidth() { - return mWidth; - } - - private void setHeight(Integer height) { - mHeight = height; - } - - private Integer getHeight() { - return mHeight; - } - - /** - * Returns true if we are inserting into the last position - * - * @return true if we are inserting into the last position - */ - public boolean isLastPosition() { - return mInsertPos == mNumPositions - 1; - } - } - - /** Custom resize state used during linear layout resizing */ - private class LinearResizeState extends ResizeState { - /** Whether the node should be assigned a new weight */ - public boolean useWeight; - /** Weight sum to be applied to the parent */ - private float mNewWeightSum; - /** The weight to be set on the node (provided {@link #useWeight} is true) */ - private float mWeight; - /** Map from nodes to preferred bounds of nodes where the weights have been cleared */ - public final Map<INode, Rect> unweightedSizes; - /** Total required size required by the siblings <b>without</b> weights */ - public int totalLength; - /** List of nodes which should have their weights cleared */ - public List<INode> mClearWeights; - - private LinearResizeState(BaseLayoutRule rule, INode layout, Object layoutView, - INode node) { - super(rule, layout, layoutView, node); - - unweightedSizes = mRulesEngine.measureChildren(layout, - new IClientRulesEngine.AttributeFilter() { - @Override - public String getAttribute(@NonNull INode n, @Nullable String namespace, - @NonNull String localName) { - // Clear out layout weights; we need to measure the unweighted sizes - // of the children - if (ATTR_LAYOUT_WEIGHT.equals(localName) - && SdkConstants.NS_RESOURCES.equals(namespace)) { - return ""; //$NON-NLS-1$ - } - - return null; - } - }); - - // Compute total required size required by the siblings *without* weights - totalLength = 0; - final boolean isVertical = isVertical(layout); - for (Map.Entry<INode, Rect> entry : unweightedSizes.entrySet()) { - Rect preferredSize = entry.getValue(); - if (isVertical) { - totalLength += preferredSize.h; - } else { - totalLength += preferredSize.w; - } - } - } - - /** Resets the computed state */ - void reset() { - mNewWeightSum = -1; - useWeight = false; - mClearWeights = null; - } - - /** Sets a weight to be applied to the node */ - void setWeight(float weight) { - useWeight = true; - mWeight = weight; - } - - /** Sets a weight sum to be applied to the parent layout */ - void setWeightSum(float weightSum) { - mNewWeightSum = weightSum; - } - - /** Marks that the given node should be cleared when applying the new size */ - void clearWeight(INode n) { - if (mClearWeights == null) { - mClearWeights = new ArrayList<INode>(); - } - mClearWeights.add(n); - } - - /** Applies the state to the nodes */ - public void apply() { - assert useWeight; - - String value = mWeight > 0 ? formatFloatAttribute(mWeight) : null; - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WEIGHT, value); - - if (mClearWeights != null) { - for (INode n : mClearWeights) { - if (getWeight(n) > 0.0f) { - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_WEIGHT, null); - } - } - } - - if (mNewWeightSum > 0.0) { - layout.setAttribute(ANDROID_URI, ATTR_WEIGHT_SUM, - formatFloatAttribute(mNewWeightSum)); - } - } - } - - @Override - protected ResizeState createResizeState(INode layout, Object layoutView, INode node) { - return new LinearResizeState(this, layout, layoutView, node); - } - - protected void updateResizeState(LinearResizeState resizeState, final INode node, INode layout, - Rect oldBounds, Rect newBounds, SegmentType horizontalEdge, - SegmentType verticalEdge) { - // Update the resize state. - // This method attempts to compute a new layout weight to be used in the direction - // of the linear layout. If the superclass has already determined that we can snap to - // a wrap_content or match_parent boundary, we prefer that. Otherwise, we attempt to - // compute a layout weight - which can fail if the size is too big (not enough room), - // or if the size is too small (smaller than the natural width of the node), and so on. - // In that case this method just aborts, which will leave the resize state object - // in such a state that it will call the superclass to resize instead, which will fall - // back to device independent pixel sizing. - resizeState.reset(); - - if (oldBounds.equals(newBounds)) { - return; - } - - // If we're setting the width/height to wrap_content/match_parent in the dimension of the - // linear layout, then just apply wrap_content and clear weights. - boolean isVertical = isVertical(layout); - if (!isVertical && verticalEdge != null) { - if (resizeState.wrapWidth || resizeState.fillWidth) { - resizeState.clearWeight(node); - return; - } - if (newBounds.w == oldBounds.w) { - return; - } - } - - if (isVertical && horizontalEdge != null) { - if (resizeState.wrapHeight || resizeState.fillHeight) { - resizeState.clearWeight(node); - return; - } - if (newBounds.h == oldBounds.h) { - return; - } - } - - // Compute weight sum - float sum = getWeightSum(layout); - if (sum <= 0.0f) { - sum = 1.0f; - resizeState.setWeightSum(sum); - } - - // If the new size of the node is smaller than its preferred/wrap_content size, - // then we cannot use weights to size it; switch to pixel-based sizing instead - Map<INode, Rect> sizes = resizeState.unweightedSizes; - Rect nodePreferredSize = sizes.get(node); - if (nodePreferredSize != null) { - if (horizontalEdge != null && newBounds.h < nodePreferredSize.h || - verticalEdge != null && newBounds.w < nodePreferredSize.w) { - return; - } - } - - Rect layoutBounds = layout.getBounds(); - int remaining = (isVertical ? layoutBounds.h : layoutBounds.w) - resizeState.totalLength; - Rect nodeBounds = sizes.get(node); - if (nodeBounds == null) { - return; - } - - if (remaining > 0) { - int missing = 0; - if (isVertical) { - if (newBounds.h > nodeBounds.h) { - missing = newBounds.h - nodeBounds.h; - } else if (newBounds.h > resizeState.wrapBounds.h) { - // The weights concern how much space to ADD to the view. - // What if we have resized it to a size *smaller* than its current - // size without the weight delta? This can happen if you for example - // have set a hardcoded size, such as 500dp, and then size it to some - // smaller size. - missing = newBounds.h - resizeState.wrapBounds.h; - remaining += nodeBounds.h - resizeState.wrapBounds.h; - resizeState.wrapHeight = true; - } - } else { - if (newBounds.w > nodeBounds.w) { - missing = newBounds.w - nodeBounds.w; - } else if (newBounds.w > resizeState.wrapBounds.w) { - missing = newBounds.w - resizeState.wrapBounds.w; - remaining += nodeBounds.w - resizeState.wrapBounds.w; - resizeState.wrapWidth = true; - } - } - if (missing > 0) { - // (weight / weightSum) * remaining = missing, so - // weight = missing * weightSum / remaining - float weight = missing * sum / remaining; - resizeState.setWeight(weight); - } - } - } - - /** - * {@inheritDoc} - * <p> - * Overridden in this layout in order to make resizing affect the layout_weight - * attribute instead of the layout_width (for horizontal LinearLayouts) or - * layout_height (for vertical LinearLayouts). - */ - @Override - protected void setNewSizeBounds(ResizeState state, final INode node, INode layout, - Rect oldBounds, Rect newBounds, SegmentType horizontalEdge, - SegmentType verticalEdge) { - LinearResizeState resizeState = (LinearResizeState) state; - updateResizeState(resizeState, node, layout, oldBounds, newBounds, - horizontalEdge, verticalEdge); - - if (resizeState.useWeight) { - resizeState.apply(); - - // Handle resizing in the opposite dimension of the layout - final boolean isVertical = isVertical(layout); - if (!isVertical && horizontalEdge != null) { - if (newBounds.h != oldBounds.h || resizeState.wrapHeight - || resizeState.fillHeight) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, - resizeState.getHeightAttribute()); - } - } - if (isVertical && verticalEdge != null) { - if (newBounds.w != oldBounds.w || resizeState.wrapWidth || resizeState.fillWidth) { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, - resizeState.getWidthAttribute()); - } - } - } else { - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WEIGHT, null); - super.setNewSizeBounds(resizeState, node, layout, oldBounds, newBounds, - horizontalEdge, verticalEdge); - } - } - - @Override - protected String getResizeUpdateMessage(ResizeState state, INode child, INode parent, - Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) { - LinearResizeState resizeState = (LinearResizeState) state; - updateResizeState(resizeState, child, parent, child.getBounds(), newBounds, - horizontalEdge, verticalEdge); - - if (resizeState.useWeight) { - String weight = formatFloatAttribute(resizeState.mWeight); - String dimension = String.format("weight %1$s", weight); - - String width; - String height; - if (isVertical(parent)) { - width = resizeState.getWidthAttribute(); - height = dimension; - } else { - width = dimension; - 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); - } - } else { - return super.getResizeUpdateMessage(state, child, parent, newBounds, - horizontalEdge, verticalEdge); - } - } - - /** - * Returns the layout weight of of the given child of a LinearLayout, or 0.0 if it - * does not define a weight - */ - private static float getWeight(INode linearLayoutChild) { - String weight = linearLayoutChild.getStringAttr(ANDROID_URI, ATTR_LAYOUT_WEIGHT); - if (weight != null && weight.length() > 0) { - try { - return Float.parseFloat(weight); - } catch (NumberFormatException nfe) { - AdtPlugin.log(nfe, "Invalid weight %1$s", weight); - } - } - - return 0.0f; - } - - /** - * Returns the sum of all the layout weights of the children in the given LinearLayout - * - * @param linearLayout the layout to compute the total sum for - * @return the total sum of all the layout weights in the given layout - */ - private static float getWeightSum(INode linearLayout) { - String weightSum = linearLayout.getStringAttr(ANDROID_URI, - ATTR_WEIGHT_SUM); - float sum = -1.0f; - if (weightSum != null) { - // Distribute - try { - sum = Float.parseFloat(weightSum); - return sum; - } catch (NumberFormatException nfe) { - // Just keep using the default - } - } - - return getSumOfWeights(linearLayout); - } - - private static float getSumOfWeights(INode linearLayout) { - float sum = 0.0f; - for (INode child : linearLayout.getChildren()) { - sum += getWeight(child); - } - - return sum; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ListViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ListViewRule.java deleted file mode 100644 index 70728c81d..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ListViewRule.java +++ /dev/null @@ -1,42 +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_LAYOUT_WIDTH; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for android.widget.ListView and all its derived classes such - * as ExpandableListView. - * This is the "root" rule, that is used whenever there is not more specific - * rule to apply. - */ -public class ListViewRule extends AdapterViewRule { - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, getFillParentValueName()); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MapViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MapViewRule.java deleted file mode 100644 index 006661e57..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MapViewRule.java +++ /dev/null @@ -1,46 +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 com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for com.google.android.maps.MapView. - * <p> - * TODO: This class should be pulled out of the ADT and bundled with the add ons - * (not the core jar but an optional tool jar) - */ -public class MapViewRule extends BaseViewRule { - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - if (insertType.isCreate()) { - node.setAttribute(ANDROID_URI, "android:apiKey", //$NON-NLS-1$ - "Your API key: see " + //$NON-NLS-1$ - "http://code.google.com/android/add-ons/google-apis/mapkey.html"); //$NON-NLS-1$ - } - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MergeRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MergeRule.java deleted file mode 100644 index 9cef9c4b3..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/MergeRule.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.RuleAction; - -import java.util.List; - -/** - * Drop handler for the {@code <merge>} tag - */ -public class MergeRule extends FrameLayoutRule { - // The <merge> tag behaves a lot like the FrameLayout; all children are added - // on top of each other at (0,0) - - @Override - public void addContextMenuActions(@NonNull List<RuleAction> actions, - final @NonNull INode selectedNode) { - // Deliberately ignore super.getContextMenu(); we don't want to attempt to list - // properties for the <merge> tag - } -} 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 deleted file mode 100644 index da2614eef..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertyCallback.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.annotations.NonNull; -import com.android.annotations.Nullable; -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.RuleAction; - -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; - - /** - * Creates a new property callback. - * - * @param targetNodes the nodes to apply the property to, or null to use the - * nodes pass into the - * {@link #action(RuleAction, List, String, Boolean)} method. - * @param undoLabel the label to use for the undo action - * @param uri the attribute URI to apply - * @param attribute the attribute name to apply - */ - public PropertyCallback(List<? extends INode> targetNodes, String undoLabel, - String uri, String attribute) { - super(); - mTargetNodes = targetNodes; - mUndoLabel = undoLabel; - mUri = uri; - mAttribute = attribute; - } - - // ---- Implements IMenuCallback ---- - @Override - public void action(@NonNull RuleAction action, @NonNull List<? extends INode> selectedNodes, - final @Nullable String valueId, final @Nullable Boolean newValue) { - if (mTargetNodes != null && mTargetNodes.size() > 0) { - selectedNodes = mTargetNodes; - } - if (selectedNodes == null || selectedNodes.size() == 0) { - return; - } - final List<? extends INode> nodes = selectedNodes; - selectedNodes.get(0).editXml(mUndoLabel, new INodeHandler() { - @Override - public void handle(@NonNull INode n) { - for (INode targetNode : nodes) { - 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/PropertySettingNodeHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertySettingNodeHandler.java deleted file mode 100644 index 13c8842ed..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/PropertySettingNodeHandler.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.INodeHandler; - -/** - * A convenience implementation of {@link INodeHandler} for setting a given attribute to a - * given value on a particular node. - */ -class PropertySettingNodeHandler implements INodeHandler { - private final String mNamespaceUri; - private final String mAttribute; - private final String mValue; - - PropertySettingNodeHandler(String namespaceUri, String attribute, String value) { - super(); - mNamespaceUri = namespaceUri; - mAttribute = attribute; - mValue = value; - } - - @Override - public void handle(@NonNull INode node) { - node.setAttribute(mNamespaceUri, mAttribute, mValue); - } -}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/QuickContactBadgeRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/QuickContactBadgeRule.java deleted file mode 100644 index 0164794d3..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/QuickContactBadgeRule.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for android.widget.QuickContactBadgeRule. - */ -public class QuickContactBadgeRule extends ImageViewRule { - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - // Deliberately override onCreate such that we don't populate a default - // image; at design time layoutlib will supply the system default contacts - // image. - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RadioGroupRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RadioGroupRule.java deleted file mode 100644 index c9aa20768..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RadioGroupRule.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 static com.android.SdkConstants.ATTR_CHECKED; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.VALUE_TRUE; - - -import com.android.SdkConstants; -import static com.android.SdkConstants.ANDROID_URI; -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for android.widget.RadioGroup which initializes the radio group - * with some radio buttons - */ -public class RadioGroupRule extends LinearLayoutRule { - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - if (insertType.isCreate()) { - for (int i = 0; i < 3; i++) { - INode handle = node.appendChild(SdkConstants.FQCN_RADIO_BUTTON); - handle.setAttribute(ANDROID_URI, ATTR_ID, String.format("@+id/radio%d", i)); - if (i == 0) { - handle.setAttribute(ANDROID_URI, ATTR_CHECKED, VALUE_TRUE); - } - } - } - } -} 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 deleted file mode 100644 index b4bc86978..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/RelativeLayoutRule.java +++ /dev/null @@ -1,413 +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_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); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ResizeState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ResizeState.java deleted file mode 100644 index 42b9083ad..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ResizeState.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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 static com.android.SdkConstants.VALUE_N_DP; -import static com.android.SdkConstants.VALUE_WRAP_CONTENT; - -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.Segment; -import com.android.ide.common.api.SegmentType; - -/** State held during resizing operations */ -class ResizeState { - /** - * The associated rule - */ - private final BaseLayoutRule mRule; - - /** - * The node being resized - */ - public final INode node; - - /** - * The layout containing the resized node - */ - public final INode layout; - - /** The proposed resized bounds of the node */ - public Rect bounds; - - /** The preferred wrap_content bounds of the node */ - public Rect wrapBounds; - - /** The suggested horizontal fill_parent guideline position */ - public Segment horizontalFillSegment; - - /** The suggested vertical fill_parent guideline position */ - public Segment verticalFillSegment; - - /** The type of horizontal edge being resized, or null */ - public SegmentType horizontalEdgeType; - - /** The type of vertical edge being resized, or null */ - public SegmentType verticalEdgeType; - - /** Whether the user has snapped to the wrap_content width */ - public boolean wrapWidth; - - /** Whether the user has snapped to the wrap_content height */ - public boolean wrapHeight; - - /** Whether the user has snapped to the match_parent width */ - public boolean fillWidth; - - /** Whether the user has snapped to the match_parent height */ - public boolean fillHeight; - - /** Custom field for use by subclasses */ - public Object clientData; - - /** Keyboard mask */ - public int modifierMask; - - /** - * The actual view object for the layout containing the resizing operation, - * or null if not known - */ - public Object layoutView; - - /** - * Constructs a new {@link ResizeState} - * - * @param rule the associated rule - * @param layout the parent layout containing the resized node - * @param layoutView the actual View instance for the layout, or null if not known - * @param node the node being resized - */ - ResizeState(BaseLayoutRule rule, INode layout, Object layoutView, INode node) { - mRule = rule; - - this.layout = layout; - this.node = node; - this.layoutView = layoutView; - } - - /** - * Returns the width attribute to be set to match the new bounds - * - * @return the width string, never null - */ - public String getWidthAttribute() { - if (wrapWidth) { - return VALUE_WRAP_CONTENT; - } else if (fillWidth) { - return mRule.getFillParentValueName(); - } else { - return String.format(VALUE_N_DP, mRule.mRulesEngine.pxToDp(bounds.w)); - } - } - - /** - * Returns the height attribute to be set to match the new bounds - * - * @return the height string, never null - */ - public String getHeightAttribute() { - if (wrapHeight) { - return VALUE_WRAP_CONTENT; - } else if (fillHeight) { - return mRule.getFillParentValueName(); - } else { - return String.format(VALUE_N_DP, mRule.mRulesEngine.pxToDp(bounds.h)); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ScrollViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ScrollViewRule.java deleted file mode 100644 index 9f2b4ae6f..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ScrollViewRule.java +++ /dev/null @@ -1,96 +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_LAYOUT_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; -import static com.android.SdkConstants.ATTR_ORIENTATION; -import static com.android.SdkConstants.FQCN_LINEAR_LAYOUT; -import static com.android.SdkConstants.VALUE_VERTICAL; - -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.IDragElement; -import com.android.ide.common.api.IGraphics; -import com.android.ide.common.api.INode; -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; - -/** - * An {@link IViewRule} for android.widget.ScrollView. - */ -public class ScrollViewRule extends FrameLayoutRule { - - @Override - public void onChildInserted(@NonNull INode child, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onChildInserted(child, parent, insertType); - - // The child of the ScrollView should fill in both directions - String fillParent = getFillParentValueName(); - child.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, fillParent); - child.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, fillParent); - } - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - if (insertType.isCreate()) { - // Insert a default linear layout (which will in turn be registered as - // a child of this node and the create child method above will set its - // fill parent attributes, its id, etc. - INode linear = node.appendChild(FQCN_LINEAR_LAYOUT); - linear.setAttribute(ANDROID_URI, ATTR_ORIENTATION, VALUE_VERTICAL); - } - } - - @Override - public DropFeedback onDropMove(@NonNull INode targetNode, @NonNull IDragElement[] elements, - @Nullable DropFeedback feedback, @NonNull Point p) { - DropFeedback f = super.onDropMove(targetNode, elements, feedback, p); - - // ScrollViews only allow a single child - if (targetNode.getChildren().length > 0) { - f.invalidTarget = true; - } - return f; - } - - @Override - protected void drawFeedback( - IGraphics gc, - INode targetNode, - IDragElement[] elements, - DropFeedback feedback) { - if (targetNode.getChildren().length > 0) { - Rect b = targetNode.getBounds(); - if (b.isValid()) { - gc.useStyle(DrawingStyle.DROP_RECIPIENT); - gc.drawRect(b); - } - } else { - super.drawFeedback(gc, targetNode, elements, feedback); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/SeekBarRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/SeekBarRule.java deleted file mode 100644 index b88f8ab25..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/SeekBarRule.java +++ /dev/null @@ -1,42 +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_LAYOUT_WIDTH; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for android.widget.SeekBar - */ -public class SeekBarRule extends BaseViewRule { - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - // A SeekBar isn't useful with wrap_content because it packs itself down to - // almost no usable width -- so just make it grow in all layouts - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, getFillParentValueName()); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/SlidingDrawerRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/SlidingDrawerRule.java deleted file mode 100644 index e4267bb10..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/SlidingDrawerRule.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 static com.android.SdkConstants.ATTR_CONTENT; -import static com.android.SdkConstants.ATTR_HANDLE; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; -import static com.android.SdkConstants.ATTR_TEXT; - - -import com.android.SdkConstants; -import static com.android.SdkConstants.ANDROID_URI; -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for android.widget.SlidingDrawerRule which initializes new sliding - * drawers with their mandatory children and default sizing attributes - */ -public class SlidingDrawerRule extends BaseLayoutRule { - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - if (insertType.isCreate()) { - String matchParent = getFillParentValueName(); - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, matchParent); - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, matchParent); - - // Create mandatory children and reference them from the handle and content - // attributes of the sliding drawer - String handleId = "@+id/handle"; //$NON-NLS-1$ - String contentId = "@+id/content"; //$NON-NLS-1$ - node.setAttribute(ANDROID_URI, ATTR_HANDLE, handleId); - node.setAttribute(ANDROID_URI, ATTR_CONTENT, contentId); - - // Handle - INode handle = node.appendChild(SdkConstants.FQCN_BUTTON); - handle.setAttribute(ANDROID_URI, ATTR_TEXT, "Handle"); - handle.setAttribute(ANDROID_URI, ATTR_ID, handleId); - - // Content - INode content = node.appendChild(SdkConstants.FQCN_LINEAR_LAYOUT); - content.setAttribute(ANDROID_URI, ATTR_ID, contentId); - content.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, matchParent); - content.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, matchParent); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TabHostRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TabHostRule.java deleted file mode 100644 index cb2153b50..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TabHostRule.java +++ /dev/null @@ -1,82 +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_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; -import static com.android.SdkConstants.ATTR_ORIENTATION; -import static com.android.SdkConstants.FQCN_FRAME_LAYOUT; -import static com.android.SdkConstants.FQCN_LINEAR_LAYOUT; -import static com.android.SdkConstants.FQCN_TAB_WIDGET; -import static com.android.SdkConstants.VALUE_VERTICAL; -import static com.android.SdkConstants.VALUE_WRAP_CONTENT; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for android.widget.TabHost. - */ -public class TabHostRule extends IgnoredLayoutRule { - // The TabHost layout states in its documentation that you typically - // manipulate its children via the TabHost rather than directly manipulating - // the child elements yourself, e.g. via addTab() etc. - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - if (insertType.isCreate()) { - String fillParent = getFillParentValueName(); - - // Configure default Table setup as described in the Table tutorial - node.setAttribute(ANDROID_URI, ATTR_ID, "@android:id/tabhost"); //$NON-NLS-1$ - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, fillParent); - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, fillParent); - - INode linear = node.appendChild(FQCN_LINEAR_LAYOUT); - linear.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, fillParent); - linear.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, fillParent); - linear.setAttribute(ANDROID_URI, ATTR_ORIENTATION, - VALUE_VERTICAL); - - INode tab = linear.appendChild(FQCN_TAB_WIDGET); - tab.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, fillParent); - tab.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, VALUE_WRAP_CONTENT); - tab.setAttribute(ANDROID_URI, ATTR_ID, "@android:id/tabs"); //$NON-NLS-1$ - - INode frame = linear.appendChild(FQCN_FRAME_LAYOUT); - frame.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, fillParent); - frame.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, fillParent); - frame.setAttribute(ANDROID_URI, ATTR_ID, "@android:id/tabcontent"); //$NON-NLS-1$ - - for (int i = 0; i < 3; i++) { - INode child = frame.appendChild(FQCN_LINEAR_LAYOUT); - child.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, fillParent); - child.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, fillParent); - child.setAttribute(ANDROID_URI, ATTR_ID, - String.format("@+id/tab%d", i + 1)); //$NON-NLS-1$ - } - } - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TabWidgetRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TabWidgetRule.java deleted file mode 100755 index 7ebaea54d..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TabWidgetRule.java +++ /dev/null @@ -1,27 +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 com.android.ide.common.api.IViewRule; - -/** - * An {@link IViewRule} for android.widget.TabWidget. - */ -public class TabWidgetRule extends IgnoredLayoutRule { - // TabWidgets aren't configurable as plain LinearLayout since they - // are supposed to be manipulated by their parent TabHost. -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java deleted file mode 100644 index b6aeeb486..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableLayoutRule.java +++ /dev/null @@ -1,218 +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.FQCN_TABLE_ROW; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.DropFeedback; -import com.android.ide.common.api.IClientRulesEngine; -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.RuleAction; -import com.android.ide.common.api.SegmentType; - -import java.net.URL; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * An {@link IViewRule} for android.widget.TableLayout. - */ -public class TableLayoutRule extends LinearLayoutRule { - // A table is a linear layout, but with a few differences: - // the default is vertical, not horizontal - // The fill of all children should be wrap_content - - private static final String ACTION_ADD_ROW = "_addrow"; //$NON-NLS-1$ - private static final String ACTION_REMOVE_ROW = "_removerow"; //$NON-NLS-1$ - private static final URL ICON_ADD_ROW = - TableLayoutRule.class.getResource("addrow.png"); //$NON-NLS-1$ - private static final URL ICON_REMOVE_ROW = - TableLayoutRule.class.getResource("removerow.png"); //$NON-NLS-1$ - - @Override - protected boolean isVertical(INode node) { - // Tables are always vertical - return true; - } - - @Override - protected boolean supportsOrientation() { - return false; - } - - @Override - public void onChildInserted(@NonNull INode child, @NonNull INode parent, - @NonNull InsertType insertType) { - // Overridden to inhibit the setting of layout_width/layout_height since - // it should always be match_parent - } - - /** - * Add an explicit "Add Row" action to the context menu - */ - @Override - public void addContextMenuActions(@NonNull List<RuleAction> actions, - final @NonNull INode selectedNode) { - super.addContextMenuActions(actions, selectedNode); - - IMenuCallback addTab = new IMenuCallback() { - @Override - public void action( - @NonNull RuleAction action, - @NonNull List<? extends INode> selectedNodes, - final @Nullable String valueId, - @Nullable Boolean newValue) { - final INode node = selectedNode; - INode newRow = node.appendChild(FQCN_TABLE_ROW); - mRulesEngine.select(Collections.singletonList(newRow)); - } - }; - actions.add(RuleAction.createAction("_addrow", "Add Row", addTab, null, 5, false)); //$NON-NLS-1$ - } - - @Override - public void addLayoutActions( - @NonNull List<RuleAction> actions, - final @NonNull INode parentNode, - final @NonNull List<? extends INode> children) { - super.addLayoutActions(actions, parentNode, children); - addTableLayoutActions(mRulesEngine, actions, parentNode, children); - } - - /** - * Adds layout actions to add and remove toolbar items - */ - static void addTableLayoutActions(final IClientRulesEngine rulesEngine, - List<RuleAction> actions, final INode parentNode, - final List<? extends INode> children) { - IMenuCallback actionCallback = new IMenuCallback() { - @Override - public void action( - final @NonNull RuleAction action, - @NonNull List<? extends INode> selectedNodes, - final @Nullable String valueId, - final @Nullable Boolean newValue) { - parentNode.editXml("Add/Remove Table Row", new INodeHandler() { - @Override - public void handle(@NonNull INode n) { - if (action.getId().equals(ACTION_ADD_ROW)) { - // Determine the index of the selection, if any; if there is - // a selection, insert the row before the current row, otherwise - // append it to the table. - int index = -1; - INode[] rows = parentNode.getChildren(); - if (children != null) { - findTableIndex: - for (INode child : children) { - // Find direct child of table layout - while (child != null && child.getParent() != parentNode) { - child = child.getParent(); - } - if (child != null) { - // Compute index of direct child of table layout - for (int i = 0; i < rows.length; i++) { - if (rows[i] == child) { - index = i; - break findTableIndex; - } - } - } - } - } - INode newRow; - if (index == -1) { - newRow = parentNode.appendChild(FQCN_TABLE_ROW); - } else { - newRow = parentNode.insertChildAt(FQCN_TABLE_ROW, index); - } - rulesEngine.select(Collections.singletonList(newRow)); - } else if (action.getId().equals(ACTION_REMOVE_ROW)) { - // Find the direct children of the TableLayout to delete; - // this is necessary since TableRow might also use - // this implementation, so the parentNode is the true - // TableLayout but the children might be grand children. - Set<INode> targets = new HashSet<INode>(); - for (INode child : children) { - while (child != null && child.getParent() != parentNode) { - child = child.getParent(); - } - if (child != null) { - targets.add(child); - } - } - for (INode target : targets) { - parentNode.removeChild(target); - } - } - } - }); - } - }; - - // Add Row - actions.add(RuleAction.createSeparator(150)); - actions.add(RuleAction.createAction(ACTION_ADD_ROW, "Add Table Row", actionCallback, - ICON_ADD_ROW, 160, false)); - - // Remove Row (if something is selected) - if (children != null && children.size() > 0) { - actions.add(RuleAction.createAction(ACTION_REMOVE_ROW, "Remove Table Row", - actionCallback, ICON_REMOVE_ROW, 170, false)); - } - } - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - if (insertType.isCreate()) { - // Start the table with 4 rows - for (int i = 0; i < 4; i++) { - node.appendChild(FQCN_TABLE_ROW); - } - } - } - - @Override - public DropFeedback onResizeBegin(@NonNull INode child, @NonNull INode parent, - @Nullable SegmentType horizontalEdge, @Nullable SegmentType verticalEdge, - @Nullable Object childView, @Nullable Object parentView) { - // Children of a table layout cannot set their widths (it is controlled by column - // settings on the table). They can set their heights (though for TableRow, the - // height is always wrap_content). - if (horizontalEdge == null) { // Widths are edited by vertical edges. - // The user is not editing a vertical height so don't allow resizing at all - return null; - } - if (child.getFqcn().equals(FQCN_TABLE_ROW)) { - // TableRows are always WRAP_CONTENT - return null; - } - - // Allow resizing heights only - return super.onResizeBegin(child, parent, horizontalEdge, null /*verticalEdge*/, - childView, parentView); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableRowRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableRowRule.java deleted file mode 100644 index 6e3f202ee..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TableRowRule.java +++ /dev/null @@ -1,80 +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.FQCN_TABLE_LAYOUT; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.DropFeedback; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; -import com.android.ide.common.api.RuleAction; -import com.android.ide.common.api.SegmentType; - -import java.util.List; - -/** - * An {@link IViewRule} for android.widget.TableRow. - */ -public class TableRowRule extends LinearLayoutRule { - @Override - protected boolean isVertical(INode node) { - return false; - } - - @Override - protected boolean supportsOrientation() { - return false; - } - - @Override - public void onChildInserted(@NonNull INode child, @NonNull INode parent, - @NonNull InsertType insertType) { - // Overridden to inhibit the setting of layout_width/layout_height since - // the table row will enforce match_parent and wrap_content for width and height - // respectively. - } - - @Override - public void addLayoutActions( - @NonNull List<RuleAction> actions, - final @NonNull INode parentNode, - final @NonNull List<? extends INode> children) { - super.addLayoutActions(actions, parentNode, children); - - // Also apply table-specific actions on the table row such that you can - // select something in a table row and still get offered actions on the surrounding - // table. - if (children != null) { - INode grandParent = parentNode.getParent(); - if (grandParent != null && grandParent.getFqcn().equals(FQCN_TABLE_LAYOUT)) { - TableLayoutRule.addTableLayoutActions(mRulesEngine, actions, grandParent, - children); - } - } - } - - @Override - public DropFeedback onResizeBegin(@NonNull INode child, @NonNull INode parent, - @Nullable SegmentType horizontalEdge, @Nullable SegmentType verticalEdge, - @Nullable Object childView, @Nullable Object parentView) { - // No resizing in TableRows; the width is *always* match_parent and the height is - // *always* wrap_content. - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TimePickerRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TimePickerRule.java deleted file mode 100755 index 1eb603d43..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/TimePickerRule.java +++ /dev/null @@ -1,27 +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 com.android.ide.common.api.IViewRule; - -/** - * An {@link IViewRule} for android.widget.TimePicker. - */ -public class TimePickerRule extends IgnoredLayoutRule { - // A TimePicker inherits from FrameLayout but is not a general purpose - // FrameLayout -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ViewRule.java deleted file mode 100755 index a7b23ab75..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ViewRule.java +++ /dev/null @@ -1,31 +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 com.android.ide.common.api.IViewRule; - -/** - * An {@link IViewRule} for android.view.View and all its derived classes. This - * is the "root" rule, that is used whenever there is not more specific rule to - * apply. - * <p/> - * There is no customization here, everything that is common to all views is - * simply implemented in BaseViewRule. - */ -public class ViewRule extends BaseViewRule { - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ViewTagRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ViewTagRule.java deleted file mode 100644 index a89a3d851..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ViewTagRule.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2012 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.ATTR_CLASS; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; -import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor; - -/** - * An {@link IViewRule} for the special XML {@code <view>} tag. - */ -public class ViewTagRule extends BaseViewRule { - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - // When dropping a view tag, ask the user which custom view class to use - if (insertType == InsertType.CREATE) { // NOT InsertType.CREATE_PREVIEW - String fqcn = mRulesEngine.displayCustomViewClassInput(); - if (fqcn != null) { - if (!ViewElementDescriptor.viewNeedsPackage(fqcn)) { - fqcn = fqcn.substring(fqcn.lastIndexOf('.') + 1); - } - node.editXml("Set Custom View Class", - new PropertySettingNodeHandler(null, ATTR_CLASS, - fqcn.length() > 0 ? fqcn : null)); - } else { - // Remove the view; the insertion was canceled - parent.removeChild(node); - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/WebViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/WebViewRule.java deleted file mode 100644 index 42b06e65b..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/WebViewRule.java +++ /dev/null @@ -1,46 +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_LAYOUT_HEIGHT; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewRule; -import com.android.ide.common.api.InsertType; - -/** - * An {@link IViewRule} for android.widget.ZoomControls. - */ -public class WebViewRule extends IgnoredLayoutRule { - // A WebView is not a general purpose AbsoluteLayout you should drop stuff - // into; it's an AbsoluteLayout for implementation purposes. - - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - if (insertType.isCreate()) { - String matchParent = getFillParentValueName(); - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, matchParent); - node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, matchParent); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ZoomButtonRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ZoomButtonRule.java deleted file mode 100644 index 66cbd45f0..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ZoomButtonRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_SRC; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.InsertType; - -public class ZoomButtonRule extends BaseViewRule { - @Override - public void onCreate(@NonNull INode node, @NonNull INode parent, - @NonNull InsertType insertType) { - super.onCreate(node, parent, insertType); - - if (insertType.isCreate()) { - node.setAttribute(ANDROID_URI, ATTR_SRC, "@android:drawable/btn_plus"); //$NON-NLS-1$ - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ZoomControlsRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ZoomControlsRule.java deleted file mode 100755 index 226bf4eb5..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ZoomControlsRule.java +++ /dev/null @@ -1,27 +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 com.android.ide.common.api.IViewRule; - -/** - * An {@link IViewRule} for android.widget.ZoomControls. - */ -public class ZoomControlsRule extends IgnoredLayoutRule { - // A ZoomControl is only a LinearLayout in terms of borrowing - // implementation; it does not behave like one in terms of configurability. -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/addcol.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/addcol.png Binary files differdeleted file mode 100644 index 21391ef53..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/addcol.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/addrow.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/addrow.png Binary files differdeleted file mode 100644 index 0faa3e607..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/addrow.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/allweight.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/allweight.png Binary files differdeleted file mode 100644 index 506c66320..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/allweight.png +++ /dev/null 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 differdeleted file mode 100644 index acb187ca0..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/baseline.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/centerHorizontally.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/centerHorizontally.png Binary files differdeleted file mode 100644 index 5053cdadd..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/centerHorizontally.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/centerVertically.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/centerVertically.png Binary files differdeleted file mode 100644 index ebba8e812..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/centerVertically.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/clearweights.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/clearweights.png Binary files differdeleted file mode 100644 index ad27c174d..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/clearweights.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/constraints.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/constraints.png Binary files differdeleted file mode 100644 index 7247d5a09..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/constraints.png +++ /dev/null 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 differdeleted file mode 100644 index eac2340f9..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/distribute.png +++ /dev/null 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 differdeleted file mode 100644 index 38e137deb..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/fillheight.png +++ /dev/null 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 differdeleted file mode 100644 index f272aab68..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/fillwidth.png +++ /dev/null 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 differdeleted file mode 100644 index 4f20928ad..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/gravity.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java deleted file mode 100644 index 8bdb56bfe..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java +++ /dev/null @@ -1,840 +0,0 @@ -/* - * 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.grid; - -import static com.android.SdkConstants.ATTR_COLUMN_COUNT; -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_ROW; -import static com.android.SdkConstants.ATTR_LAYOUT_ROW_SPAN; -import static com.android.ide.common.layout.GravityHelper.getGravity; -import static com.android.ide.common.layout.GridLayoutRule.GRID_SIZE; -import static com.android.ide.common.layout.GridLayoutRule.MARGIN_SIZE; -import static com.android.ide.common.layout.GridLayoutRule.MAX_CELL_DIFFERENCE; -import static com.android.ide.common.layout.GridLayoutRule.SHORT_GAP_DP; -import static com.android.ide.common.layout.grid.GridModel.UNDEFINED; -import static java.lang.Math.abs; - -import com.android.ide.common.api.DropFeedback; -import com.android.ide.common.api.IDragElement; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewMetadata; -import com.android.ide.common.api.Margins; -import com.android.ide.common.api.Point; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.SegmentType; -import com.android.ide.common.layout.BaseLayoutRule; -import com.android.ide.common.layout.GravityHelper; -import com.android.ide.common.layout.GridLayoutRule; -import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -/** - * The {@link GridDropHandler} handles drag and drop operations into and within a - * GridLayout, computing guidelines, handling drops to edit the grid model, and so on. - */ -public class GridDropHandler { - private final GridModel mGrid; - private final GridLayoutRule mRule; - private GridMatch mColumnMatch; - private GridMatch mRowMatch; - - /** - * Creates a new {@link GridDropHandler} for - * @param gridLayoutRule the corresponding {@link GridLayoutRule} - * @param layout the GridLayout node - * @param view the view instance of the grid layout receiving the drop - */ - public GridDropHandler(GridLayoutRule gridLayoutRule, INode layout, Object view) { - mRule = gridLayoutRule; - mGrid = GridModel.get(mRule.getRulesEngine(), layout, view); - } - - /** - * Computes the best horizontal and vertical matches for a drag to the given position. - * - * @param feedback a {@link DropFeedback} object containing drag state like the drag - * bounds and the drag baseline - * @param p the mouse position - */ - public void computeMatches(DropFeedback feedback, Point p) { - mRowMatch = mColumnMatch = null; - feedback.tooltip = null; - - Rect bounds = mGrid.layout.getBounds(); - int x1 = p.x; - int y1 = p.y; - - Rect dragBounds = feedback.dragBounds; - int w = dragBounds != null ? dragBounds.w : 0; - int h = dragBounds != null ? dragBounds.h : 0; - if (!GridLayoutRule.sGridMode) { - if (dragBounds != null) { - // Sometimes the items are centered under the mouse so - // offset by the top left corner distance - x1 += dragBounds.x; - y1 += dragBounds.y; - } - - int x2 = x1 + w; - int y2 = y1 + h; - - if (x2 < bounds.x || y2 < bounds.y || x1 > bounds.x2() || y1 > bounds.y2()) { - return; - } - - List<GridMatch> columnMatches = new ArrayList<GridMatch>(); - List<GridMatch> rowMatches = new ArrayList<GridMatch>(); - int max = BaseLayoutRule.getMaxMatchDistance(); - - // Column matches: - addLeftSideMatch(x1, columnMatches, max); - addRightSideMatch(x2, columnMatches, max); - addCenterColumnMatch(bounds, x1, y1, x2, y2, columnMatches, max); - - // Row matches: - int row = (mGrid.getViewCount() == 0) ? 0 : mGrid.getClosestRow(y1); - int rowY = mGrid.getRowY(row); - addTopMatch(y1, rowMatches, max, row, rowY); - addBaselineMatch(feedback.dragBaseline, y1, rowMatches, max, row, rowY); - addBottomMatch(y2, rowMatches, max); - - // Look for gap-matches: Predefined spacing between widgets. - // TODO: Make this use metadata for predefined spacing between - // pairs of types of components. For example, buttons have certain - // inserts in their 9-patch files (depending on the theme) that should - // be considered and subtracted from the overall proposed distance! - addColumnGapMatch(bounds, x1, x2, columnMatches, max); - addRowGapMatch(bounds, y1, y2, rowMatches, max); - - // Fallback: Split existing cell. Also do snap-to-grid. - if (GridLayoutRule.sSnapToGrid) { - x1 = ((x1 - MARGIN_SIZE - bounds.x) / GRID_SIZE) * GRID_SIZE - + MARGIN_SIZE + bounds.x; - y1 = ((y1 - MARGIN_SIZE - bounds.y) / GRID_SIZE) * GRID_SIZE - + MARGIN_SIZE + bounds.y; - x2 = x1 + w; - y2 = y1 + h; - } - - - if (columnMatches.size() == 0 && x1 >= bounds.x) { - // Split the current cell since we have no matches - // TODO: Decide whether it should be gravity left or right... - columnMatches.add(new GridMatch(SegmentType.LEFT, 0, x1, mGrid.getColumn(x1), - true /* createCell */, UNDEFINED)); - } - if (rowMatches.size() == 0 && y1 >= bounds.y) { - rowMatches.add(new GridMatch(SegmentType.TOP, 0, y1, mGrid.getRow(y1), - true /* createCell */, UNDEFINED)); - } - - // Pick best matches - Collections.sort(rowMatches); - Collections.sort(columnMatches); - - mColumnMatch = null; - mRowMatch = null; - String columnDescription = null; - String rowDescription = null; - if (columnMatches.size() > 0) { - mColumnMatch = columnMatches.get(0); - columnDescription = mColumnMatch.getDisplayName(mGrid.layout); - } - if (rowMatches.size() > 0) { - mRowMatch = rowMatches.get(0); - rowDescription = mRowMatch.getDisplayName(mGrid.layout); - } - - if (columnDescription != null && rowDescription != null) { - feedback.tooltip = columnDescription + '\n' + rowDescription; - } - - feedback.invalidTarget = mColumnMatch == null || mRowMatch == null; - } else { - // Find which cell we're inside. - - // TODO: Find out where within the cell we are, and offer to tweak the gravity - // based on the position. - int column = mGrid.getColumn(x1); - int row = mGrid.getRow(y1); - - int leftDistance = mGrid.getColumnDistance(column, x1); - int rightDistance = mGrid.getColumnDistance(column + 1, x1); - int topDistance = mGrid.getRowDistance(row, y1); - int bottomDistance = mGrid.getRowDistance(row + 1, y1); - - int SLOP = 2; - int radius = mRule.getNewCellSize(); - if (rightDistance < radius + SLOP) { - column = Math.min(column + 1, mGrid.actualColumnCount); - leftDistance = rightDistance; - } - if (bottomDistance < radius + SLOP) { - row = Math.min(row + 1, mGrid.actualRowCount); - topDistance = bottomDistance; - } - - boolean createColumn = leftDistance < radius + SLOP; - boolean createRow = topDistance < radius + SLOP; - if (x1 >= bounds.x2()) { - createColumn = true; - } - if (y1 >= bounds.y2()) { - createRow = true; - } - - int cellWidth = leftDistance + rightDistance; - int cellHeight = topDistance + bottomDistance; - SegmentType horizontalType = SegmentType.LEFT; - SegmentType verticalType = SegmentType.TOP; - int minDistance = 10; // Don't center or right/bottom align in tiny cells - if (!createColumn && leftDistance > minDistance - && dragBounds != null && dragBounds.w < cellWidth - 10) { - if (rightDistance < leftDistance) { - horizontalType = SegmentType.RIGHT; - } - - int centerDistance = Math.abs(cellWidth / 2 - leftDistance); - if (centerDistance < leftDistance / 2 && centerDistance < rightDistance / 2) { - horizontalType = SegmentType.CENTER_HORIZONTAL; - } - } - if (!createRow && topDistance > minDistance - && dragBounds != null && dragBounds.h < cellHeight - 10) { - if (bottomDistance < topDistance) { - verticalType = SegmentType.BOTTOM; - } - int centerDistance = Math.abs(cellHeight / 2 - topDistance); - if (centerDistance < topDistance / 2 && centerDistance < bottomDistance / 2) { - verticalType = SegmentType.CENTER_VERTICAL; - } - } - - mColumnMatch = new GridMatch(horizontalType, 0, x1, column, createColumn, 0); - mRowMatch = new GridMatch(verticalType, 0, y1, row, createRow, 0); - - StringBuilder description = new StringBuilder(50); - String rowString = Integer.toString(mColumnMatch.cellIndex + 1); - String columnString = Integer.toString(mRowMatch.cellIndex + 1); - if (mRowMatch.createCell && mRowMatch.cellIndex < mGrid.actualRowCount) { - description.append(String.format("Shift row %1$d down", mRowMatch.cellIndex + 1)); - description.append('\n'); - } - if (mColumnMatch.createCell && mColumnMatch.cellIndex < mGrid.actualColumnCount) { - description.append(String.format("Shift column %1$d right", - mColumnMatch.cellIndex + 1)); - description.append('\n'); - } - description.append(String.format("Insert into cell (%1$s,%2$s)", - rowString, columnString)); - description.append('\n'); - description.append(String.format("Align %1$s, %2$s", - horizontalType.name().toLowerCase(Locale.US), - verticalType.name().toLowerCase(Locale.US))); - feedback.tooltip = description.toString(); - } - } - - /** - * Adds a match to align the left edge with some other edge. - */ - private void addLeftSideMatch(int x1, List<GridMatch> columnMatches, int max) { - int column = (mGrid.getViewCount() == 0) ? 0 : mGrid.getClosestColumn(x1); - int columnX = mGrid.getColumnX(column); - int distance = abs(columnX - x1); - if (distance <= max) { - columnMatches.add(new GridMatch(SegmentType.LEFT, distance, columnX, column, - false, UNDEFINED)); - } - } - - /** - * Adds a match to align the right edge with some other edge. - */ - private void addRightSideMatch(int x2, List<GridMatch> columnMatches, int max) { - // TODO: Only match the right hand side if the drag bounds fit fully within the - // cell! Ditto for match below. - int columnRight = (mGrid.getViewCount() == 0) ? 0 : mGrid.getClosestColumn(x2); - int rightDistance = mGrid.getColumnDistance(columnRight, x2); - if (rightDistance < max) { - int columnX = mGrid.getColumnX(columnRight); - if (columnX > mGrid.layout.getBounds().x) { - columnMatches.add(new GridMatch(SegmentType.RIGHT, rightDistance, columnX, - columnRight, false, UNDEFINED)); - } - } - } - - /** - * Adds a horizontal match with the center axis of the GridLayout - */ - private void addCenterColumnMatch(Rect bounds, int x1, int y1, int x2, int y2, - List<GridMatch> columnMatches, int max) { - Collection<INode> intersectsRow = mGrid.getIntersectsRow(y1, y2); - if (intersectsRow.size() == 0) { - // Offer centering on this row since there isn't anything there - int matchedLine = bounds.centerX(); - int distance = abs((x1 + x2) / 2 - matchedLine); - if (distance <= 2 * max) { - boolean createCell = false; // always just put in column 0 - columnMatches.add(new GridMatch(SegmentType.CENTER_HORIZONTAL, distance, - matchedLine, 0 /* column */, createCell, UNDEFINED)); - } - } - } - - /** - * Adds a match to align the top edge with some other edge. - */ - private void addTopMatch(int y1, List<GridMatch> rowMatches, int max, int row, int rowY) { - int distance = mGrid.getRowDistance(row, y1); - if (distance <= max) { - rowMatches.add(new GridMatch(SegmentType.TOP, distance, rowY, row, false, - UNDEFINED)); - } - } - - /** - * Adds a match to align the bottom edge with some other edge. - */ - private void addBottomMatch(int y2, List<GridMatch> rowMatches, int max) { - int rowBottom = (mGrid.getViewCount() == 0) ? 0 : mGrid.getClosestRow(y2); - int distance = mGrid.getRowDistance(rowBottom, y2); - if (distance < max) { - int rowY = mGrid.getRowY(rowBottom); - if (rowY > mGrid.layout.getBounds().y) { - rowMatches.add(new GridMatch(SegmentType.BOTTOM, distance, rowY, - rowBottom, false, UNDEFINED)); - } - } - } - - /** - * Adds a baseline match, if applicable. - */ - private void addBaselineMatch(int dragBaseline, int y1, List<GridMatch> rowMatches, int max, - int row, int rowY) { - int dragBaselineY = y1 + dragBaseline; - int rowBaseline = mGrid.getBaseline(row); - if (rowBaseline != -1) { - int rowBaselineY = rowY + rowBaseline; - int distance = abs(dragBaselineY - rowBaselineY); - if (distance < max) { - rowMatches.add(new GridMatch(SegmentType.BASELINE, distance, rowBaselineY, row, - false, UNDEFINED)); - } - } - } - - /** - * Computes a horizontal "gap" match - a preferred distance from the nearest edge, - * including margin edges - */ - private void addColumnGapMatch(Rect bounds, int x1, int x2, List<GridMatch> columnMatches, - int max) { - if (x1 < bounds.x + MARGIN_SIZE + max) { - int matchedLine = bounds.x + MARGIN_SIZE; - int distance = abs(matchedLine - x1); - if (distance <= max) { - boolean createCell = mGrid.getColumnX(mGrid.getColumn(matchedLine)) != matchedLine; - columnMatches.add(new GridMatch(SegmentType.LEFT, distance, matchedLine, - 0, createCell, MARGIN_SIZE)); - } - } else if (x2 > bounds.x2() - MARGIN_SIZE - max) { - int matchedLine = bounds.x2() - MARGIN_SIZE; - int distance = abs(matchedLine - x2); - if (distance <= max) { - // This does not yet work properly; we need to use columnWeights to achieve this - //boolean createCell = mGrid.getColumnX(mGrid.getColumn(matchedLine)) != matchedLine; - //columnMatches.add(new GridMatch(SegmentType.RIGHT, distance, matchedLine, - // mGrid.actualColumnCount - 1, createCell, MARGIN_SIZE)); - } - } else { - int columnRight = mGrid.getColumn(x1 - SHORT_GAP_DP); - int columnX = mGrid.getColumnMaxX(columnRight); - int matchedLine = columnX + SHORT_GAP_DP; - int distance = abs(matchedLine - x1); - if (distance <= max) { - boolean createCell = mGrid.getColumnX(mGrid.getColumn(matchedLine)) != matchedLine; - columnMatches.add(new GridMatch(SegmentType.LEFT, distance, matchedLine, - columnRight, createCell, SHORT_GAP_DP)); - } - - // Add a column directly adjacent (no gap) - columnRight = mGrid.getColumn(x1); - columnX = mGrid.getColumnMaxX(columnRight); - matchedLine = columnX; - distance = abs(matchedLine - x1); - - // Let's say you have this arrangement: - // [button1][button2] - // This is two columns, where the right hand side edge of column 1 is - // flush with the left side edge of column 2, because in fact the width of - // button1 is what defines the width of column 1, and that in turn is what - // defines the left side position of column 2. - // - // In this case we don't want to consider inserting a new column at the - // right hand side of button1 a better match than matching left on column 2. - // Therefore, to ensure that this doesn't happen, we "penalize" right column - // matches such that they don't get preferential treatment when the matching - // line is on the left side of the column. - distance += 2; - - if (distance <= max) { - boolean createCell = mGrid.getColumnX(mGrid.getColumn(matchedLine)) != matchedLine; - columnMatches.add(new GridMatch(SegmentType.LEFT, distance, matchedLine, - columnRight, createCell, 0)); - } - } - } - - /** - * Computes a vertical "gap" match - a preferred distance from the nearest edge, - * including margin edges - */ - private void addRowGapMatch(Rect bounds, int y1, int y2, List<GridMatch> rowMatches, int max) { - if (y1 < bounds.y + MARGIN_SIZE + max) { - int matchedLine = bounds.y + MARGIN_SIZE; - int distance = abs(matchedLine - y1); - if (distance <= max) { - boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine; - rowMatches.add(new GridMatch(SegmentType.TOP, distance, matchedLine, - 0, createCell, MARGIN_SIZE)); - } - } else if (y2 > bounds.y2() - MARGIN_SIZE - max) { - int matchedLine = bounds.y2() - MARGIN_SIZE; - int distance = abs(matchedLine - y2); - if (distance <= max) { - // This does not yet work properly; we need to use columnWeights to achieve this - //boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine; - //rowMatches.add(new GridMatch(SegmentType.BOTTOM, distance, matchedLine, - // mGrid.actualRowCount - 1, createCell, MARGIN_SIZE)); - } - } else { - int rowBottom = mGrid.getRow(y1 - SHORT_GAP_DP); - int rowY = mGrid.getRowMaxY(rowBottom); - int matchedLine = rowY + SHORT_GAP_DP; - int distance = abs(matchedLine - y1); - if (distance <= max) { - boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine; - rowMatches.add(new GridMatch(SegmentType.TOP, distance, matchedLine, - rowBottom, createCell, SHORT_GAP_DP)); - } - - // Add a row directly adjacent (no gap) - rowBottom = mGrid.getRow(y1); - rowY = mGrid.getRowMaxY(rowBottom); - matchedLine = rowY; - distance = abs(matchedLine - y1); - distance += 2; // See explanation in addColumnGapMatch - if (distance <= max) { - boolean createCell = mGrid.getRowY(mGrid.getRow(matchedLine)) != matchedLine; - rowMatches.add(new GridMatch(SegmentType.TOP, distance, matchedLine, - rowBottom, createCell, 0)); - } - - } - } - - /** - * Called when a node is dropped in free-form mode. This will insert the dragged - * element into the grid and returns the newly created node. - * - * @param targetNode the GridLayout node - * @param element the dragged element - * @return the newly created {@link INode} - */ - public INode handleFreeFormDrop(INode targetNode, IDragElement element) { - assert mRowMatch != null; - assert mColumnMatch != null; - - String fqcn = element.getFqcn(); - - INode newChild = null; - - Rect bounds = element.getBounds(); - int row = mRowMatch.cellIndex; - int column = mColumnMatch.cellIndex; - - if (targetNode.getChildren().length == 0) { - // - // Set up the initial structure: - // - // - // Fixed Fixed - // Size Size - // Column Expanding Column Column - // +-----+-------------------------------+-----+ - // | | | | - // | 0,0 | 0,1 | 0,2 | Fixed Size Row - // | | | | - // +-----+-------------------------------+-----+ - // | | | | - // | | | | - // | | | | - // | 1,0 | 1,1 | 1,2 | Expanding Row - // | | | | - // | | | | - // | | | | - // +-----+-------------------------------+-----+ - // | | | | - // | 2,0 | 2,1 | 2,2 | Fixed Size Row - // | | | | - // +-----+-------------------------------+-----+ - // - // This is implemented in GridLayout by the following grid, where - // SC1 has columnWeight=1 and SR1 has rowWeight=1. - // (SC=Space for Column, SR=Space for Row) - // - // +------+-------------------------------+------+ - // | | | | - // | SCR0 | SC1 | SC2 | - // | | | | - // +------+-------------------------------+------+ - // | | | | - // | | | | - // | | | | - // | SR1 | | | - // | | | | - // | | | | - // | | | | - // +------+-------------------------------+------+ - // | | | | - // | SR2 | | | - // | | | | - // +------+-------------------------------+------+ - // - // Note that when we split columns and rows here, if splitting the expanding - // row or column then the row or column weight should be moved to the right or - // bottom half! - - - //int columnX = mGrid.getColumnX(column); - //int rowY = mGrid.getRowY(row); - - mGrid.setGridAttribute(targetNode, ATTR_COLUMN_COUNT, 2); - //mGrid.setGridAttribute(targetNode, ATTR_COLUMN_COUNT, 3); - //INode scr0 = addSpacer(targetNode, -1, 0, 0, 1, 1); - //INode sc1 = addSpacer(targetNode, -1, 0, 1, 0, 0); - //INode sc2 = addSpacer(targetNode, -1, 0, 2, 1, 0); - //INode sr1 = addSpacer(targetNode, -1, 1, 0, 0, 0); - //INode sr2 = addSpacer(targetNode, -1, 2, 0, 0, 1); - //mGrid.setGridAttribute(sc1, ATTR_LAYOUT_GRAVITY, VALUE_FILL_HORIZONTAL); - //mGrid.setGridAttribute(sr1, ATTR_LAYOUT_GRAVITY, VALUE_FILL_VERTICAL); - // - //mGrid.loadFromXml(); - //column = mGrid.getColumn(columnX); - //row = mGrid.getRow(rowY); - } - - int startX, endX; - if (mColumnMatch.type == SegmentType.RIGHT) { - endX = mColumnMatch.matchedLine - 1; - startX = endX - bounds.w; - column = mGrid.getColumn(startX); - } else { - startX = mColumnMatch.matchedLine; // TODO: What happens on type=RIGHT? - endX = startX + bounds.w; - } - int startY, endY; - if (mRowMatch.type == SegmentType.BOTTOM) { - endY = mRowMatch.matchedLine - 1; - startY = endY - bounds.h; - row = mGrid.getRow(startY); - } else if (mRowMatch.type == SegmentType.BASELINE) { - // TODO: The rowSpan should always be 1 for baseline alignments, since - // otherwise the alignment won't work! - startY = endY = mRowMatch.matchedLine; - } else { - startY = mRowMatch.matchedLine; - endY = startY + bounds.h; - } - int endColumn = mGrid.getColumn(endX); - int endRow = mGrid.getRow(endY); - int columnSpan = endColumn - column + 1; - int rowSpan = endRow - row + 1; - - // Make sure my math was right: - assert mRowMatch.type != SegmentType.BASELINE || rowSpan == 1 : rowSpan; - - // If the item almost fits into the row (at most N % bigger) then just enlarge - // the row; don't add a rowspan since that will defeat baseline alignment etc - if (!mRowMatch.createCell && bounds.h <= MAX_CELL_DIFFERENCE * mGrid.getRowHeight( - mRowMatch.type == SegmentType.BOTTOM ? endRow : row, 1)) { - if (mRowMatch.type == SegmentType.BOTTOM) { - row += rowSpan - 1; - } - rowSpan = 1; - } - if (!mColumnMatch.createCell && bounds.w <= MAX_CELL_DIFFERENCE * mGrid.getColumnWidth( - mColumnMatch.type == SegmentType.RIGHT ? endColumn : column, 1)) { - if (mColumnMatch.type == SegmentType.RIGHT) { - column += columnSpan - 1; - } - columnSpan = 1; - } - - if (mColumnMatch.type == SegmentType.CENTER_HORIZONTAL) { - column = 0; - columnSpan = mGrid.actualColumnCount; - } - - // Temporary: Ensure we don't get in trouble with implicit positions - mGrid.applyPositionAttributes(); - - // Split cells to make a new column - if (mColumnMatch.createCell) { - int columnWidthPx = mGrid.getColumnDistance(column, mColumnMatch.matchedLine); - //assert columnWidthPx == columnMatch.distance; // TBD? IF so simplify - int columnWidthDp = mRule.getRulesEngine().pxToDp(columnWidthPx); - - int maxX = mGrid.getColumnMaxX(column); - boolean insertMarginColumn = false; - if (mColumnMatch.margin == 0) { - columnWidthDp = 0; - } else if (mColumnMatch.margin != UNDEFINED) { - int distance = abs(mColumnMatch.matchedLine - (maxX + mColumnMatch.margin)); - insertMarginColumn = column > 0 && distance < 2; - if (insertMarginColumn) { - int margin = mColumnMatch.margin; - if (ViewMetadataRepository.INSETS_SUPPORTED) { - IViewMetadata metadata = mRule.getRulesEngine().getMetadata(fqcn); - if (metadata != null) { - Margins insets = metadata.getInsets(); - if (insets != null) { - // TODO: - // Consider left or right side attachment - // TODO: Also consider inset of element on cell to the left - margin -= insets.left; - } - } - } - - columnWidthDp = mRule.getRulesEngine().pxToDp(margin); - } - } - - column++; - mGrid.splitColumn(column, insertMarginColumn, columnWidthDp, mColumnMatch.matchedLine); - if (insertMarginColumn) { - column++; - } - } - - // Split cells to make a new row - if (mRowMatch.createCell) { - int rowHeightPx = mGrid.getRowDistance(row, mRowMatch.matchedLine); - //assert rowHeightPx == rowMatch.distance; // TBD? If so simplify - int rowHeightDp = mRule.getRulesEngine().pxToDp(rowHeightPx); - - int maxY = mGrid.getRowMaxY(row); - boolean insertMarginRow = false; - if (mRowMatch.margin == 0) { - rowHeightDp = 0; - } else if (mRowMatch.margin != UNDEFINED) { - int distance = abs(mRowMatch.matchedLine - (maxY + mRowMatch.margin)); - insertMarginRow = row > 0 && distance < 2; - if (insertMarginRow) { - int margin = mRowMatch.margin; - IViewMetadata metadata = mRule.getRulesEngine().getMetadata(element.getFqcn()); - if (metadata != null) { - Margins insets = metadata.getInsets(); - if (insets != null) { - // TODO: - // Consider left or right side attachment - // TODO: Also consider inset of element on cell to the left - margin -= insets.top; - } - } - - rowHeightDp = mRule.getRulesEngine().pxToDp(margin); - } - } - - row++; - mGrid.splitRow(row, insertMarginRow, rowHeightDp, mRowMatch.matchedLine); - if (insertMarginRow) { - row++; - } - } - - // Figure out where to insert the new child - - int index = mGrid.getInsertIndex(row, column); - if (index == -1) { - // Couldn't find a later place to insert - newChild = targetNode.appendChild(fqcn); - } else { - GridModel.ViewData next = mGrid.getView(index); - - newChild = targetNode.insertChildAt(fqcn, index); - - // Must also apply positions to the following child to ensure - // that the new child doesn't affect the implicit numbering! - // TODO: We can later check whether the implied number is equal to - // what it already is such that we don't need this - next.applyPositionAttributes(); - } - - // Set the cell position (gravity) of the new widget - int gravity = 0; - if (mColumnMatch.type == SegmentType.RIGHT) { - gravity |= GravityHelper.GRAVITY_RIGHT; - } else if (mColumnMatch.type == SegmentType.CENTER_HORIZONTAL) { - gravity |= GravityHelper.GRAVITY_CENTER_HORIZ; - } - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN, column); - if (mRowMatch.type == SegmentType.BASELINE) { - // There *is* no baseline gravity constant, instead, leave the - // vertical gravity unspecified and GridLayout will treat it as - // baseline alignment - //gravity |= GravityHelper.GRAVITY_BASELINE; - } else if (mRowMatch.type == SegmentType.BOTTOM) { - gravity |= GravityHelper.GRAVITY_BOTTOM; - } else if (mRowMatch.type == SegmentType.CENTER_VERTICAL) { - gravity |= GravityHelper.GRAVITY_CENTER_VERT; - } - // Ensure that we have at least one horizontal and vertical constraint, otherwise - // the new item will be fixed. As an example, if we have a single button in the - // table which we inserted *without* a gravity, and we then insert a button - // above it with a vertical gravity, then only the top column would be considered - // stretchable, and it will fill all available vertical space and the previous - // button will jump to the bottom. - if (!GravityHelper.isConstrainedHorizontally(gravity)) { - gravity |= GravityHelper.GRAVITY_LEFT; - } - /* This causes problems: Try placing two buttons vertically from the top of the layout. - We need to solve the free column/free row problem first. - if (!GravityHelper.isConstrainedVertically(gravity) - // There is no baseline constant, so we have to leave it unconstrained instead - && mRowMatch.type != SegmentType.BASELINE - // You also can't baseline align one element with another that has vertical - // alignment top or bottom, so when we first "freely" place views (e.g. - // at a particular y location), also place it freely (no constraint). - && !mRowMatch.createCell) { - gravity |= GravityHelper.GRAVITY_TOP; - } - */ - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_GRAVITY, getGravity(gravity)); - - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_ROW, row); - - // Apply spans to ensure that the widget can fit without pushing columns - if (columnSpan > 1) { - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN_SPAN, columnSpan); - } - if (rowSpan > 1) { - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_ROW_SPAN, rowSpan); - } - - // Ensure that we don't store columnCount=0 - if (mGrid.actualColumnCount == 0) { - mGrid.setGridAttribute(mGrid.layout, ATTR_COLUMN_COUNT, Math.max(1, column + 1)); - } - - return newChild; - } - - /** - * Called when a drop is completed and we're in grid-editing mode. This will insert - * the dragged element into the target cell. - * - * @param targetNode the GridLayout node - * @param element the dragged element - * @return the newly created node - */ - public INode handleGridModeDrop(INode targetNode, IDragElement element) { - String fqcn = element.getFqcn(); - INode newChild = targetNode.appendChild(fqcn); - - int column = mColumnMatch.cellIndex; - if (mColumnMatch.createCell) { - mGrid.addColumn(column, - newChild, UNDEFINED, false, UNDEFINED, UNDEFINED); - } - int row = mRowMatch.cellIndex; - if (mRowMatch.createCell) { - mGrid.addRow(row, newChild, UNDEFINED, false, UNDEFINED, UNDEFINED); - } - - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN, column); - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_ROW, row); - - int gravity = 0; - if (mColumnMatch.type == SegmentType.RIGHT) { - gravity |= GravityHelper.GRAVITY_RIGHT; - } else if (mColumnMatch.type == SegmentType.CENTER_HORIZONTAL) { - gravity |= GravityHelper.GRAVITY_CENTER_HORIZ; - } - if (mRowMatch.type == SegmentType.BASELINE) { - // There *is* no baseline gravity constant, instead, leave the - // vertical gravity unspecified and GridLayout will treat it as - // baseline alignment - //gravity |= GravityHelper.GRAVITY_BASELINE; - } else if (mRowMatch.type == SegmentType.BOTTOM) { - gravity |= GravityHelper.GRAVITY_BOTTOM; - } else if (mRowMatch.type == SegmentType.CENTER_VERTICAL) { - gravity |= GravityHelper.GRAVITY_CENTER_VERT; - } - if (!GravityHelper.isConstrainedHorizontally(gravity)) { - gravity |= GravityHelper.GRAVITY_LEFT; - } - if (!GravityHelper.isConstrainedVertically(gravity)) { - gravity |= GravityHelper.GRAVITY_TOP; - } - mGrid.setGridAttribute(newChild, ATTR_LAYOUT_GRAVITY, getGravity(gravity)); - - if (mGrid.declaredColumnCount == UNDEFINED || mGrid.declaredColumnCount < column + 1) { - mGrid.setGridAttribute(mGrid.layout, ATTR_COLUMN_COUNT, column + 1); - } - - return newChild; - } - - /** - * Returns the best horizontal match - * - * @return the best horizontal match, or null if there is no match - */ - public GridMatch getColumnMatch() { - return mColumnMatch; - } - - /** - * Returns the best vertical match - * - * @return the best vertical match, or null if there is no match - */ - public GridMatch getRowMatch() { - return mRowMatch; - } - - /** - * Returns the grid used by the drop handler - * - * @return the grid used by the drop handler, never null - */ - public GridModel getGrid() { - return mGrid; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridLayoutPainter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridLayoutPainter.java deleted file mode 100644 index 7e2d3a799..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridLayoutPainter.java +++ /dev/null @@ -1,370 +0,0 @@ -/* - * 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.grid; - -import static com.android.ide.common.layout.GridLayoutRule.GRID_SIZE; -import static com.android.ide.common.layout.GridLayoutRule.MARGIN_SIZE; -import static com.android.ide.common.layout.grid.GridModel.UNDEFINED; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.DrawingStyle; -import com.android.ide.common.api.DropFeedback; -import com.android.ide.common.api.IDragElement; -import com.android.ide.common.api.IFeedbackPainter; -import com.android.ide.common.api.IGraphics; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.SegmentType; -import com.android.ide.common.layout.GridLayoutRule; -import com.android.utils.Pair; - -/** - * Painter which paints feedback during drag, drop and resizing operations, as well as - * static selection feedback - */ -public class GridLayoutPainter { - - /** - * Creates a painter for drop feedback - * - * @param rule the corresponding {@link GridLayoutRule} - * @param elements the dragged elements - * @return a {@link IFeedbackPainter} which can paint the drop feedback - */ - public static IFeedbackPainter createDropFeedbackPainter(GridLayoutRule rule, - IDragElement[] elements) { - return new DropFeedbackPainter(rule, elements); - } - - /** - * Paints the structure (the grid model) of the given GridLayout. - * - * @param style the drawing style to use to paint the structure lines - * @param layout the grid layout node - * @param gc the graphics context to paint into - * @param grid the grid model to be visualized - */ - public static void paintStructure(DrawingStyle style, INode layout, IGraphics gc, - GridModel grid) { - Rect b = layout.getBounds(); - - gc.useStyle(style); - for (int row = 0; row < grid.actualRowCount; row++) { - int y = grid.getRowY(row); - gc.drawLine(b.x, y, b.x2(), y); - } - for (int column = 0; column < grid.actualColumnCount; column++) { - int x = grid.getColumnX(column); - gc.drawLine(x, b.y, x, b.y2()); - } - } - - /** - * Paints a regular grid according to the {@link GridLayoutRule#GRID_SIZE} and - * {@link GridLayoutRule#MARGIN_SIZE} dimensions. These are the same lines that - * snap-to-grid will align with. - * - * @param layout the GridLayout node - * @param gc the graphics context to paint the grid into - */ - public static void paintGrid(INode layout, IGraphics gc) { - Rect b = layout.getBounds(); - - int oldAlpha = gc.getAlpha(); - gc.useStyle(DrawingStyle.GUIDELINE); - gc.setAlpha(128); - - int y1 = b.y + MARGIN_SIZE; - int y2 = b.y2() - MARGIN_SIZE; - for (int y = y1; y < y2; y += GRID_SIZE) { - int x1 = b.x + MARGIN_SIZE; - int x2 = b.x2() - MARGIN_SIZE; - for (int x = x1; x < x2; x += GRID_SIZE) { - gc.drawPoint(x, y); - } - } - gc.setAlpha(oldAlpha); - } - - /** - * Paint resizing feedback (which currently paints the grid model faintly.) - * - * @param gc the graphics context - * @param layout the GridLayout - * @param grid the grid model - */ - public static void paintResizeFeedback(IGraphics gc, INode layout, GridModel grid) { - paintStructure(DrawingStyle.GRID, layout, gc, grid); - } - - /** - * A painter which can paint the drop feedback for elements being dragged into or - * within a GridLayout. - */ - private static class DropFeedbackPainter implements IFeedbackPainter { - private final GridLayoutRule mRule; - private final IDragElement[] mElements; - - /** Constructs a new {@link GridLayoutPainter} bound to the given {@link GridLayoutRule} - * @param rule the corresponding rule - * @param elements the elements to draw */ - public DropFeedbackPainter(GridLayoutRule rule, IDragElement[] elements) { - mRule = rule; - mElements = elements; - } - - // Implements IFeedbackPainter - @Override - public void paint(@NonNull IGraphics gc, @NonNull INode node, - @NonNull DropFeedback feedback) { - Rect b = node.getBounds(); - if (!b.isValid()) { - return; - } - - // Highlight the receiver - gc.useStyle(DrawingStyle.DROP_RECIPIENT); - gc.drawRect(b); - GridDropHandler data = (GridDropHandler) feedback.userData; - - if (!GridLayoutRule.sGridMode) { - paintFreeFormDropFeedback(gc, node, feedback, b, data); - } else { - paintGridModeDropFeedback(gc, b, data); - } - } - - /** - * Paints the drag feedback for a free-form mode drag - */ - private void paintFreeFormDropFeedback(IGraphics gc, INode node, DropFeedback feedback, - Rect b, GridDropHandler data) { - GridModel grid = data.getGrid(); - if (GridLayoutRule.sSnapToGrid) { - GridLayoutPainter.paintGrid(node, gc); - } - GridLayoutPainter.paintStructure(DrawingStyle.GRID, node, gc, grid); - - GridMatch rowMatch = data.getRowMatch(); - GridMatch columnMatch = data.getColumnMatch(); - - if (rowMatch == null || columnMatch == null) { - return; - } - - IDragElement first = mElements[0]; - Rect dragBounds = first.getBounds(); - int offsetX = 0; - int offsetY = 0; - if (rowMatch.type == SegmentType.BOTTOM) { - offsetY -= dragBounds.h; - } else if (rowMatch.type == SegmentType.BASELINE) { - offsetY -= feedback.dragBaseline; - } - if (columnMatch.type == SegmentType.RIGHT) { - offsetX -= dragBounds.w; - } else if (columnMatch.type == SegmentType.CENTER_HORIZONTAL) { - offsetX -= dragBounds.w / 2; - } - - // Draw guidelines for matches - int y = rowMatch.matchedLine; - int x = columnMatch.matchedLine; - Rect bounds = first.getBounds(); - - // Draw margin - if (rowMatch.margin != UNDEFINED && rowMatch.margin > 0) { - gc.useStyle(DrawingStyle.DISTANCE); - int centerX = bounds.w / 2 + offsetX + x; - int y1; - int y2; - if (rowMatch.type == SegmentType.TOP) { - y1 = offsetY + y - 1; - y2 = rowMatch.matchedLine - rowMatch.margin; - } else { - assert rowMatch.type == SegmentType.BOTTOM; - y1 = bounds.h + offsetY + y - 1; - y2 = rowMatch.matchedLine + rowMatch.margin; - } - gc.drawLine(b.x, y1, b.x2(), y1); - gc.drawLine(b.x, y2, b.x2(), y2); - gc.drawString(Integer.toString(rowMatch.margin), - centerX - 3, y1 + (y2 - y1 - 16) / 2); - } else { - gc.useStyle(rowMatch.margin == 0 ? DrawingStyle.DISTANCE - : rowMatch.createCell ? DrawingStyle.GUIDELINE_DASHED - : DrawingStyle.GUIDELINE); - gc.drawLine(b.x, y, b.x2(), y ); - } - - if (columnMatch.margin != UNDEFINED && columnMatch.margin > 0) { - gc.useStyle(DrawingStyle.DISTANCE); - int centerY = bounds.h / 2 + offsetY + y; - int x1; - int x2; - if (columnMatch.type == SegmentType.LEFT) { - x1 = offsetX + x - 1; - x2 = columnMatch.matchedLine - columnMatch.margin; - } else { - assert columnMatch.type == SegmentType.RIGHT; - x1 = bounds.w + offsetX + x - 1; - x2 = columnMatch.matchedLine + columnMatch.margin; - } - gc.drawLine(x1, b.y, x1, b.y2()); - gc.drawLine(x2, b.y, x2, b.y2()); - gc.drawString(Integer.toString(columnMatch.margin), - x1 + (x2 - x1 - 16) / 2, centerY - 3); - } else { - gc.useStyle(columnMatch.margin == 0 ? DrawingStyle.DISTANCE - : columnMatch.createCell ? DrawingStyle.GUIDELINE_DASHED - : DrawingStyle.GUIDELINE); - gc.drawLine(x, b.y, x, b.y2()); - } - - // Draw preview rectangles for all the dragged elements - gc.useStyle(DrawingStyle.DROP_PREVIEW); - offsetX += x - bounds.x; - offsetY += y - bounds.y; - - for (IDragElement element : mElements) { - if (element == first) { - mRule.drawElement(gc, first, offsetX, offsetY); - // Preview baseline as well - if (feedback.dragBaseline != -1) { - int x1 = dragBounds.x + offsetX; - int y1 = dragBounds.y + offsetY + feedback.dragBaseline; - gc.drawLine(x1, y1, x1 + dragBounds.w, y1); - } - } else { - b = element.getBounds(); - if (b.isValid()) { - gc.drawRect(b.x + offsetX, b.y + offsetY, - b.x + offsetX + b.w, b.y + offsetY + b.h); - } - } - } - } - - /** - * Paints the drag feedback for a grid-mode drag - */ - private void paintGridModeDropFeedback(IGraphics gc, Rect b, GridDropHandler data) { - int radius = mRule.getNewCellSize(); - GridModel grid = data.getGrid(); - - gc.useStyle(DrawingStyle.GUIDELINE); - // Paint grid - for (int row = 1; row < grid.actualRowCount; row++) { - int y = grid.getRowY(row); - gc.drawLine(b.x, y - radius, b.x2(), y - radius); - gc.drawLine(b.x, y + radius, b.x2(), y + radius); - - } - for (int column = 1; column < grid.actualColumnCount; column++) { - int x = grid.getColumnX(column); - gc.drawLine(x - radius, b.y, x - radius, b.y2()); - gc.drawLine(x + radius, b.y, x + radius, b.y2()); - } - gc.drawRect(b.x, b.y, b.x2(), b.y2()); - gc.drawRect(b.x + 2 * radius, b.y + 2 * radius, - b.x2() - 2 * radius, b.y2() - 2 * radius); - - GridMatch columnMatch = data.getColumnMatch(); - GridMatch rowMatch = data.getRowMatch(); - int column = columnMatch.cellIndex; - int row = rowMatch.cellIndex; - boolean createColumn = columnMatch.createCell; - boolean createRow = rowMatch.createCell; - - Rect cellBounds = grid.getCellBounds(row, column, 1, 1); - - IDragElement first = mElements[0]; - Rect dragBounds = first.getBounds(); - int offsetX = cellBounds.x - dragBounds.x; - int offsetY = cellBounds.y - dragBounds.y; - - gc.useStyle(DrawingStyle.DROP_ZONE_ACTIVE); - if (createColumn) { - gc.fillRect(new Rect(cellBounds.x - radius, - cellBounds.y + (createRow ? -radius : radius), - 2 * radius + 1, cellBounds.h - (createRow ? 0 : 2 * radius))); - offsetX -= radius + dragBounds.w / 2; - } - if (createRow) { - gc.fillRect(new Rect(cellBounds.x + radius, cellBounds.y - radius, - cellBounds.w - 2 * radius, 2 * radius + 1)); - offsetY -= radius + dragBounds.h / 2; - } else if (!createColumn) { - // Choose this cell - gc.fillRect(new Rect(cellBounds.x + radius, cellBounds.y + radius, - cellBounds.w - 2 * radius, cellBounds.h - 2 * radius)); - } - - gc.useStyle(DrawingStyle.DROP_PREVIEW); - - Rect bounds = first.getBounds(); - int x = offsetX; - int y = offsetY; - if (columnMatch.type == SegmentType.RIGHT) { - x += cellBounds.w - bounds.w; - } else if (columnMatch.type == SegmentType.CENTER_HORIZONTAL) { - x += cellBounds.w / 2 - bounds.w / 2; - } - if (rowMatch.type == SegmentType.BOTTOM) { - y += cellBounds.h - bounds.h; - } else if (rowMatch.type == SegmentType.CENTER_VERTICAL) { - y += cellBounds.h / 2 - bounds.h / 2; - } - - mRule.drawElement(gc, first, x, y); - } - } - - /** - * Paints the structure (the row and column boundaries) of the given - * GridLayout - * - * @param view the instance of the GridLayout whose structure should be - * painted - * @param style the drawing style to use for the cell boundaries - * @param layout the layout element - * @param gc the graphics context - * @return true if the structure was successfully inferred from the view and - * painted - */ - public static boolean paintStructure(Object view, DrawingStyle style, INode layout, - IGraphics gc) { - Pair<int[],int[]> cellBounds = GridModel.getAxisBounds(view); - if (cellBounds != null) { - int[] xs = cellBounds.getFirst(); - int[] ys = cellBounds.getSecond(); - Rect b = layout.getBounds(); - gc.useStyle(style); - for (int row = 0; row < ys.length; row++) { - int y = ys[row] + b.y; - gc.drawLine(b.x, y, b.x2(), y); - } - for (int column = 0; column < xs.length; column++) { - int x = xs[column] + b.x; - gc.drawLine(x, b.y, x, b.y2()); - } - - return true; - } else { - return false; - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridMatch.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridMatch.java deleted file mode 100644 index 9bee34345..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridMatch.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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.grid; - -import static com.android.ide.common.layout.grid.GridModel.UNDEFINED; - -import com.android.ide.common.api.INode; -import com.android.ide.common.api.SegmentType; - -/** - * A match for a drag within a GridLayout, corresponding to an alignment with another - * edge, or a margin, or centering, or a gap distance from another edge and so on. - */ -class GridMatch implements Comparable<GridMatch> { - /** The distance to the matched edge - used to pick best matches */ - public final int distance; - - /** Type of edge that was matched (this refers to the edge on the dragged node, - * not on the matched node/row/cell etc) */ - public final SegmentType type; - - /** Row or column for the match */ - public int cellIndex; - - /** If true, create a new row/column */ - public boolean createCell; - - /** The actual x or y position of the matched segment */ - public int matchedLine; - - /** Amount of margin between the matched edges */ - public int margin; - - /** - * Constructs a match. - * - * @param type the edge of the dragged element that was matched - * @param distance the absolute distance from the ideal match - used to find the best - * match - * @param matchedLine the actual X or Y location of the ideal match - * @param cellIndex the index of the row or column we matched with - * @param createCell if true, create a new cell by splitting the existing cell at the - * matchedLine position - * @param margin a margin distance to add to the actual location from the matched line - */ - GridMatch(SegmentType type, int distance, int matchedLine, int cellIndex, - boolean createCell, int margin) { - super(); - this.type = type; - this.distance = distance; - this.matchedLine = matchedLine; - this.cellIndex = cellIndex; - this.createCell = createCell; - this.margin = margin; - } - - // Implements Comparable<GridMatch> - @Override - public int compareTo(GridMatch o) { - // Pick closest matches first - if (distance != o.distance) { - return distance - o.distance; - } - - // Prefer some types of matches over other matches - return getPriority() - o.getPriority(); - } - - /** - * Describes the match for the user - * - * @param layout the GridLayout containing the match - * @return a short description for the user of the match - */ - public String getDisplayName(INode layout) { - switch (type) { - case BASELINE: - return String.format("Align baseline in row %1$d", cellIndex + 1); - case CENTER_HORIZONTAL: - return "Center horizontally"; - case LEFT: - if (!createCell) { - return String.format("Insert into column %1$d", cellIndex + 1); - } - if (margin != UNDEFINED) { - if (cellIndex == 0 && margin != 0) { - return "Add one margin distance from the left"; - } - return String.format("Add next to column %1$d", cellIndex + 1); - } - return String.format("Align left at x=%1$d", matchedLine - layout.getBounds().x); - case RIGHT: - if (!createCell) { - return String.format("Insert right-aligned into column %1$d", cellIndex + 1); - } - return String.format("Align right at x=%1$d", matchedLine - layout.getBounds().x); - case TOP: - if (!createCell) { - return String.format("Insert into row %1$d", cellIndex + 1); - } - if (margin != UNDEFINED) { - if (cellIndex == 0 && margin != 0) { - return "Add one margin distance from the top"; - } - return String.format("Add below row %1$d", cellIndex + 1); - } - return String.format("Align top at y=%1d", matchedLine - layout.getBounds().y); - case BOTTOM: - if (!createCell) { - return String.format("Insert into bottom of row %1$d", cellIndex + 1); - } - return String.format("Align bottom at y=%1d", matchedLine - layout.getBounds().y); - case CENTER_VERTICAL: - return "Center vertically"; - case UNKNOWN: - default: - return null; - } - } - - /** - * Computes the sorting priority of this match, giving baseline matches higher - * precedence than centering which in turn is ordered before external edge matches - */ - private int getPriority() { - switch (type) { - case BASELINE: - return 0; - case CENTER_HORIZONTAL: - case CENTER_VERTICAL: - return 1; - case BOTTOM: - case LEFT: - case RIGHT: - case TOP: - return 2; - } - - return 3; - } -}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java deleted file mode 100644 index 46770e82c..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java +++ /dev/null @@ -1,2384 +0,0 @@ -/* - * 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.grid; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_COLUMN_COUNT; -import static com.android.SdkConstants.ATTR_ID; -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_ROW; -import static com.android.SdkConstants.ATTR_LAYOUT_ROW_SPAN; -import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH; -import static com.android.SdkConstants.ATTR_ORIENTATION; -import static com.android.SdkConstants.ATTR_ROW_COUNT; -import static com.android.SdkConstants.FQCN_GRID_LAYOUT; -import static com.android.SdkConstants.FQCN_SPACE; -import static com.android.SdkConstants.FQCN_SPACE_V7; -import static com.android.SdkConstants.GRID_LAYOUT; -import static com.android.SdkConstants.NEW_ID_PREFIX; -import static com.android.SdkConstants.SPACE; -import static com.android.SdkConstants.VALUE_BOTTOM; -import static com.android.SdkConstants.VALUE_CENTER_VERTICAL; -import static com.android.SdkConstants.VALUE_N_DP; -import static com.android.SdkConstants.VALUE_TOP; -import static com.android.SdkConstants.VALUE_VERTICAL; -import static com.android.ide.common.layout.GravityHelper.GRAVITY_BOTTOM; -import static com.android.ide.common.layout.GravityHelper.GRAVITY_CENTER_HORIZ; -import static com.android.ide.common.layout.GravityHelper.GRAVITY_CENTER_VERT; -import static com.android.ide.common.layout.GravityHelper.GRAVITY_RIGHT; -import static java.lang.Math.abs; -import static java.lang.Math.max; -import static java.lang.Math.min; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.IClientRulesEngine; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.IViewMetadata; -import com.android.ide.common.api.Margins; -import com.android.ide.common.api.Rect; -import com.android.ide.common.layout.GravityHelper; -import com.android.ide.common.layout.GridLayoutRule; -import com.android.utils.Pair; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.lang.ref.WeakReference; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** Models a GridLayout */ -public class GridModel { - /** Marker value used to indicate values (rows, columns, etc) which have not been set */ - static final int UNDEFINED = Integer.MIN_VALUE; - - /** The size of spacers in the dimension that they are not defining */ - static final int SPACER_SIZE_DP = 1; - - /** Attribute value used for {@link #SPACER_SIZE_DP} */ - private static final String SPACER_SIZE = String.format(VALUE_N_DP, SPACER_SIZE_DP); - - /** Width assigned to a newly added column with the Add Column action */ - private static final int DEFAULT_CELL_WIDTH = 100; - - /** Height assigned to a newly added row with the Add Row action */ - private static final int DEFAULT_CELL_HEIGHT = 15; - - /** The GridLayout node, never null */ - public final INode layout; - - /** True if this is a vertical layout, and false if it is horizontal (the default) */ - public boolean vertical; - - /** The declared count of rows (which may be {@link #UNDEFINED} if not specified) */ - public int declaredRowCount; - - /** The declared count of columns (which may be {@link #UNDEFINED} if not specified) */ - public int declaredColumnCount; - - /** The actual count of rows found in the grid */ - public int actualRowCount; - - /** The actual count of columns found in the grid */ - public int actualColumnCount; - - /** - * Array of positions (indexed by column) of the left edge of table cells; this - * corresponds to the column positions in the grid - */ - private int[] mLeft; - - /** - * Array of positions (indexed by row) of the top edge of table cells; this - * corresponds to the row positions in the grid - */ - private int[] mTop; - - /** - * Array of positions (indexed by column) of the maximum right hand side bounds of a - * node in the given column; this represents the visual edge of a column even when the - * actual column is wider - */ - private int[] mMaxRight; - - /** - * Array of positions (indexed by row) of the maximum bottom bounds of a node in the - * given row; this represents the visual edge of a row even when the actual row is - * taller - */ - private int[] mMaxBottom; - - /** - * Array of baselines computed for the rows. This array is populated lazily and should - * not be accessed directly; call {@link #getBaseline(int)} instead. - */ - private int[] mBaselines; - - /** List of all the view data for the children in this layout */ - private List<ViewData> mChildViews; - - /** The {@link IClientRulesEngine} */ - private final IClientRulesEngine mRulesEngine; - - /** - * An actual instance of a GridLayout object that this grid model corresponds to. - */ - private Object mViewObject; - - /** The namespace to use for attributes */ - private String mNamespace; - - /** - * Constructs a {@link GridModel} for the given layout - * - * @param rulesEngine the associated rules engine - * @param node the GridLayout node - * @param viewObject an actual GridLayout instance, or null - */ - private GridModel(IClientRulesEngine rulesEngine, INode node, Object viewObject) { - mRulesEngine = rulesEngine; - layout = node; - mViewObject = viewObject; - loadFromXml(); - } - - // Factory cache for most recent item (used primarily because during paints and drags - // the grid model is called repeatedly for the same view object.) - private static WeakReference<Object> sCachedViewObject = new WeakReference<Object>(null); - private static WeakReference<GridModel> sCachedViewModel; - - /** - * Factory which returns a grid model for the given node. - * - * @param rulesEngine the associated rules engine - * @param node the GridLayout node - * @param viewObject an actual GridLayout instance, or null - * @return a new model - */ - @NonNull - public static GridModel get( - @NonNull IClientRulesEngine rulesEngine, - @NonNull INode node, - @Nullable Object viewObject) { - if (viewObject != null && viewObject == sCachedViewObject.get()) { - GridModel model = sCachedViewModel.get(); - if (model != null) { - return model; - } - } - - GridModel model = new GridModel(rulesEngine, node, viewObject); - sCachedViewModel = new WeakReference<GridModel>(model); - sCachedViewObject = new WeakReference<Object>(viewObject); - return model; - } - - /** - * Returns the {@link ViewData} for the child at the given index - * - * @param index the position of the child node whose view we want to look up - * @return the corresponding {@link ViewData} - */ - public ViewData getView(int index) { - return mChildViews.get(index); - } - - /** - * Returns the {@link ViewData} for the given child node. - * - * @param node the node for which we want the view info - * @return the view info for the node, or null if not found - */ - public ViewData getView(INode node) { - for (ViewData view : mChildViews) { - if (view.node == node) { - return view; - } - } - - return null; - } - - /** - * Computes the index (among the children nodes) to insert a new node into which - * should be positioned at the given row and column. This will skip over any nodes - * that have implicit positions earlier than the given node, and will also ensure that - * all nodes are placed before the spacer nodes. - * - * @param row the target row of the new node - * @param column the target column of the new node - * @return the insert position to use or -1 if no preference is found - */ - public int getInsertIndex(int row, int column) { - if (vertical) { - for (ViewData view : mChildViews) { - if (view.column > column || view.column == column && view.row >= row) { - return view.index; - } - } - } else { - for (ViewData view : mChildViews) { - if (view.row > row || view.row == row && view.column >= column) { - return view.index; - } - } - } - - // Place it before the first spacer - for (ViewData view : mChildViews) { - if (view.isSpacer()) { - return view.index; - } - } - - return -1; - } - - /** - * Returns the baseline of the given row, or -1 if none is found. This looks for views - * in the row which have baseline vertical alignment and also define their own - * baseline, and returns the first such match. - * - * @param row the row to look up a baseline for - * @return the baseline relative to the row position, or -1 if not defined - */ - public int getBaseline(int row) { - if (row < 0 || row >= mBaselines.length) { - return -1; - } - - int baseline = mBaselines[row]; - if (baseline == UNDEFINED) { - baseline = -1; - - // TBD: Consider stringing together row information in the view data - // so I can quickly identify the views in a given row instead of searching - // among all? - for (ViewData view : mChildViews) { - // We only count baselines for views with rowSpan=1 because - // baseline alignment doesn't work for cell spanning views - if (view.row == row && view.rowSpan == 1) { - baseline = view.node.getBaseline(); - if (baseline != -1) { - // Even views that do have baselines do not count towards a row - // baseline if they have a vertical gravity - String gravity = getGridAttribute(view.node, ATTR_LAYOUT_GRAVITY); - if (gravity == null - || !(gravity.contains(VALUE_TOP) - || gravity.contains(VALUE_BOTTOM) - || gravity.contains(VALUE_CENTER_VERTICAL))) { - // Compute baseline relative to the row, not the view itself - baseline += view.node.getBounds().y - getRowY(row); - break; - } - } - } - } - mBaselines[row] = baseline; - } - - return baseline; - } - - /** Applies the row and column values into the XML */ - void applyPositionAttributes() { - for (ViewData view : mChildViews) { - view.applyPositionAttributes(); - } - - // Also fix the columnCount - if (getGridAttribute(layout, ATTR_COLUMN_COUNT) != null && - declaredColumnCount > actualColumnCount) { - setGridAttribute(layout, ATTR_COLUMN_COUNT, actualColumnCount); - } - } - - /** - * Sets the given GridLayout attribute (rowCount, layout_row, etc) to the - * given value. This automatically handles using the right XML namespace - * based on whether the GridLayout is the android.widget.GridLayout, or the - * support library GridLayout, and whether it's in a library project or not - * etc. - * - * @param node the node to apply the attribute to - * @param name the local name of the attribute - * @param value the integer value to set the attribute to - */ - public void setGridAttribute(INode node, String name, int value) { - setGridAttribute(node, name, Integer.toString(value)); - } - - /** - * Sets the given GridLayout attribute (rowCount, layout_row, etc) to the - * given value. This automatically handles using the right XML namespace - * based on whether the GridLayout is the android.widget.GridLayout, or the - * support library GridLayout, and whether it's in a library project or not - * etc. - * - * @param node the node to apply the attribute to - * @param name the local name of the attribute - * @param value the string value to set the attribute to, or null to clear - * it - */ - public void setGridAttribute(INode node, String name, String value) { - node.setAttribute(getNamespace(), name, value); - } - - /** - * Returns the namespace URI to use for GridLayout-specific attributes, such - * as columnCount, layout_column, layout_column_span, layout_gravity etc. - * - * @return the namespace, never null - */ - public String getNamespace() { - if (mNamespace == null) { - mNamespace = ANDROID_URI; - - String fqcn = layout.getFqcn(); - if (!fqcn.equals(GRID_LAYOUT) && !fqcn.equals(FQCN_GRID_LAYOUT)) { - mNamespace = mRulesEngine.getAppNameSpace(); - } - } - - return mNamespace; - } - - /** Removes the given flag from a flag attribute value and returns the result */ - static String removeFlag(String flag, String value) { - if (value.equals(flag)) { - return null; - } - // Handle spaces between pipes and flag are a prefix, suffix and interior occurrences - int index = value.indexOf(flag); - if (index != -1) { - int pipe = value.lastIndexOf('|', index); - int endIndex = index + flag.length(); - if (pipe != -1) { - value = value.substring(0, pipe).trim() + value.substring(endIndex).trim(); - } else { - pipe = value.indexOf('|', endIndex); - if (pipe != -1) { - value = value.substring(0, index).trim() + value.substring(pipe + 1).trim(); - } else { - value = value.substring(0, index).trim() + value.substring(endIndex).trim(); - } - } - } - - return value; - } - - /** - * Loads a {@link GridModel} from the XML model. - */ - private void loadFromXml() { - INode[] children = layout.getChildren(); - - declaredRowCount = getGridAttribute(layout, ATTR_ROW_COUNT, UNDEFINED); - declaredColumnCount = getGridAttribute(layout, ATTR_COLUMN_COUNT, UNDEFINED); - // Horizontal is the default, so if no value is specified it is horizontal. - vertical = VALUE_VERTICAL.equals(getGridAttribute(layout, ATTR_ORIENTATION)); - - mChildViews = new ArrayList<ViewData>(children.length); - int index = 0; - for (INode child : children) { - ViewData view = new ViewData(child, index++); - mChildViews.add(view); - } - - // Assign row/column positions to all cells that do not explicitly define them - if (!assignRowsAndColumnsFromViews(mChildViews)) { - assignRowsAndColumnsFromXml( - declaredRowCount == UNDEFINED ? children.length : declaredRowCount, - declaredColumnCount == UNDEFINED ? children.length : declaredColumnCount); - } - - assignCellBounds(); - - for (int i = 0; i <= actualRowCount; i++) { - mBaselines[i] = UNDEFINED; - } - } - - private Pair<Map<Integer, Integer>, Map<Integer, Integer>> findCellsOutsideDeclaredBounds() { - // See if we have any (row,column) pairs that fall outside the declared - // bounds; for these we identify the number of unique values and assign these - // consecutive values - Map<Integer, Integer> extraColumnsMap = null; - Map<Integer, Integer> extraRowsMap = null; - if (declaredRowCount != UNDEFINED) { - Set<Integer> extraRows = null; - for (ViewData view : mChildViews) { - if (view.row >= declaredRowCount) { - if (extraRows == null) { - extraRows = new HashSet<Integer>(); - } - extraRows.add(view.row); - } - } - if (extraRows != null && declaredRowCount != UNDEFINED) { - List<Integer> rows = new ArrayList<Integer>(extraRows); - Collections.sort(rows); - int row = declaredRowCount; - extraRowsMap = new HashMap<Integer, Integer>(); - for (Integer declared : rows) { - extraRowsMap.put(declared, row++); - } - } - } - if (declaredColumnCount != UNDEFINED) { - Set<Integer> extraColumns = null; - for (ViewData view : mChildViews) { - if (view.column >= declaredColumnCount) { - if (extraColumns == null) { - extraColumns = new HashSet<Integer>(); - } - extraColumns.add(view.column); - } - } - if (extraColumns != null && declaredColumnCount != UNDEFINED) { - List<Integer> columns = new ArrayList<Integer>(extraColumns); - Collections.sort(columns); - int column = declaredColumnCount; - extraColumnsMap = new HashMap<Integer, Integer>(); - for (Integer declared : columns) { - extraColumnsMap.put(declared, column++); - } - } - } - - return Pair.of(extraRowsMap, extraColumnsMap); - } - - /** - * Figure out actual row and column numbers for views that do not specify explicit row - * and/or column numbers - * TODO: Consolidate with the algorithm in GridLayout to ensure we get the - * exact same results! - */ - private void assignRowsAndColumnsFromXml(int rowCount, int columnCount) { - Pair<Map<Integer, Integer>, Map<Integer, Integer>> p = findCellsOutsideDeclaredBounds(); - Map<Integer, Integer> extraRowsMap = p.getFirst(); - Map<Integer, Integer> extraColumnsMap = p.getSecond(); - - if (!vertical) { - // Horizontal GridLayout: this is the default. Row and column numbers - // are assigned by assuming that the children are assigned successive - // column numbers until we get to the column count of the grid, at which - // point we jump to the next row. If any cell specifies either an explicit - // row number of column number, we jump to the next available position. - // Note also that if there are any rowspans on the current row, then the - // next row we jump to is below the largest such rowspan - in other words, - // the algorithm does not fill holes in the middle! - - // TODO: Ensure that we don't run into trouble if a later element specifies - // an earlier number... find out what the layout does in that case! - int row = 0; - int column = 0; - int nextRow = 1; - for (ViewData view : mChildViews) { - int declaredColumn = view.column; - if (declaredColumn != UNDEFINED) { - if (declaredColumn >= columnCount) { - assert extraColumnsMap != null; - declaredColumn = extraColumnsMap.get(declaredColumn); - view.column = declaredColumn; - } - if (declaredColumn < column) { - // Must jump to the next row to accommodate the new row - assert nextRow > row; - //row++; - row = nextRow; - } - column = declaredColumn; - } else { - view.column = column; - } - if (view.row != UNDEFINED) { - // TODO: Should this adjust the column number too? (If so must - // also update view.column since we've already processed the local - // column number) - row = view.row; - } else { - view.row = row; - } - - nextRow = Math.max(nextRow, view.row + view.rowSpan); - - // Advance - column += view.columnSpan; - if (column >= columnCount) { - column = 0; - assert nextRow > row; - //row++; - row = nextRow; - } - } - } else { - // Vertical layout: successive children are assigned to the same column in - // successive rows. - int row = 0; - int column = 0; - int nextColumn = 1; - for (ViewData view : mChildViews) { - int declaredRow = view.row; - if (declaredRow != UNDEFINED) { - if (declaredRow >= rowCount) { - declaredRow = extraRowsMap.get(declaredRow); - view.row = declaredRow; - } - if (declaredRow < row) { - // Must jump to the next column to accommodate the new column - assert nextColumn > column; - column = nextColumn; - } - row = declaredRow; - } else { - view.row = row; - } - if (view.column != UNDEFINED) { - // TODO: Should this adjust the row number too? (If so must - // also update view.row since we've already processed the local - // row number) - column = view.column; - } else { - view.column = column; - } - - nextColumn = Math.max(nextColumn, view.column + view.columnSpan); - - // Advance - row += view.rowSpan; - if (row >= rowCount) { - row = 0; - assert nextColumn > column; - //row++; - column = nextColumn; - } - } - } - } - - private static boolean sAttemptSpecReflection = true; - - private boolean assignRowsAndColumnsFromViews(List<ViewData> views) { - if (!sAttemptSpecReflection) { - return false; - } - - try { - // Lazily initialized reflection methods - Field spanField = null; - Field rowSpecField = null; - Field colSpecField = null; - Field minField = null; - Field maxField = null; - Method getLayoutParams = null; - - for (ViewData view : views) { - // TODO: If the element *specifies* anything in XML, use that instead - Object child = mRulesEngine.getViewObject(view.node); - if (child == null) { - // Fallback to XML model - return false; - } - - if (getLayoutParams == null) { - getLayoutParams = child.getClass().getMethod("getLayoutParams"); //$NON-NLS-1$ - } - Object layoutParams = getLayoutParams.invoke(child); - if (rowSpecField == null) { - Class<? extends Object> layoutParamsClass = layoutParams.getClass(); - rowSpecField = layoutParamsClass.getDeclaredField("rowSpec"); //$NON-NLS-1$ - colSpecField = layoutParamsClass.getDeclaredField("columnSpec"); //$NON-NLS-1$ - rowSpecField.setAccessible(true); - colSpecField.setAccessible(true); - } - assert colSpecField != null; - - Object rowSpec = rowSpecField.get(layoutParams); - Object colSpec = colSpecField.get(layoutParams); - if (spanField == null) { - spanField = rowSpec.getClass().getDeclaredField("span"); //$NON-NLS-1$ - spanField.setAccessible(true); - } - assert spanField != null; - Object rowInterval = spanField.get(rowSpec); - Object colInterval = spanField.get(colSpec); - if (minField == null) { - Class<? extends Object> intervalClass = rowInterval.getClass(); - minField = intervalClass.getDeclaredField("min"); //$NON-NLS-1$ - maxField = intervalClass.getDeclaredField("max"); //$NON-NLS-1$ - minField.setAccessible(true); - maxField.setAccessible(true); - } - assert maxField != null; - - int row = minField.getInt(rowInterval); - int col = minField.getInt(colInterval); - int rowEnd = maxField.getInt(rowInterval); - int colEnd = maxField.getInt(colInterval); - - view.column = col; - view.row = row; - view.columnSpan = colEnd - col; - view.rowSpan = rowEnd - row; - } - - return true; - - } catch (Throwable e) { - sAttemptSpecReflection = false; - return false; - } - } - - /** - * Computes the positions of the column and row boundaries - */ - private void assignCellBounds() { - if (!assignCellBoundsFromView()) { - assignCellBoundsFromBounds(); - } - initializeMaxBounds(); - mBaselines = new int[actualRowCount + 1]; - } - - /** - * Computes the positions of the column and row boundaries, using actual - * layout data from the associated GridLayout instance (stored in - * {@link #mViewObject}) - */ - private boolean assignCellBoundsFromView() { - if (mViewObject != null) { - Pair<int[], int[]> cellBounds = GridModel.getAxisBounds(mViewObject); - if (cellBounds != null) { - int[] xs = cellBounds.getFirst(); - int[] ys = cellBounds.getSecond(); - Rect layoutBounds = layout.getBounds(); - - // Handle "blank" grid layouts: insert a fake grid of CELL_COUNT^2 cells - // where the user can do initial placement - if (actualColumnCount <= 1 && actualRowCount <= 1 && mChildViews.isEmpty()) { - final int CELL_COUNT = 1; - xs = new int[CELL_COUNT + 1]; - ys = new int[CELL_COUNT + 1]; - int cellWidth = layoutBounds.w / CELL_COUNT; - int cellHeight = layoutBounds.h / CELL_COUNT; - - for (int i = 0; i <= CELL_COUNT; i++) { - xs[i] = i * cellWidth; - ys[i] = i * cellHeight; - } - } - - actualColumnCount = xs.length - 1; - actualRowCount = ys.length - 1; - - int layoutBoundsX = layoutBounds.x; - int layoutBoundsY = layoutBounds.y; - mLeft = new int[xs.length]; - mTop = new int[ys.length]; - for (int i = 0; i < xs.length; i++) { - mLeft[i] = xs[i] + layoutBoundsX; - } - for (int i = 0; i < ys.length; i++) { - mTop[i] = ys[i] + layoutBoundsY; - } - - return true; - } - } - - return false; - } - - /** - * Computes the boundaries of the rows and columns by considering the bounds of the - * children. - */ - private void assignCellBoundsFromBounds() { - Rect layoutBounds = layout.getBounds(); - - // Compute the actualColumnCount and actualRowCount. This -should- be - // as easy as declaredColumnCount + extraColumnsMap.size(), - // but the user doesn't *have* to declare a column count (or a row count) - // and we need both, so go and find the actual row and column maximums. - int maxColumn = 0; - int maxRow = 0; - for (ViewData view : mChildViews) { - maxColumn = max(maxColumn, view.column); - maxRow = max(maxRow, view.row); - } - actualColumnCount = maxColumn + 1; - actualRowCount = maxRow + 1; - - mLeft = new int[actualColumnCount + 1]; - for (int i = 1; i < actualColumnCount; i++) { - mLeft[i] = UNDEFINED; - } - mLeft[0] = layoutBounds.x; - mLeft[actualColumnCount] = layoutBounds.x2(); - mTop = new int[actualRowCount + 1]; - for (int i = 1; i < actualRowCount; i++) { - mTop[i] = UNDEFINED; - } - mTop[0] = layoutBounds.y; - mTop[actualRowCount] = layoutBounds.y2(); - - for (ViewData view : mChildViews) { - Rect bounds = view.node.getBounds(); - if (!bounds.isValid()) { - continue; - } - int column = view.column; - int row = view.row; - - if (mLeft[column] == UNDEFINED) { - mLeft[column] = bounds.x; - } else { - mLeft[column] = Math.min(bounds.x, mLeft[column]); - } - if (mTop[row] == UNDEFINED) { - mTop[row] = bounds.y; - } else { - mTop[row] = Math.min(bounds.y, mTop[row]); - } - } - - // Ensure that any empty columns/rows have a valid boundary value; for now, - for (int i = actualColumnCount - 1; i >= 0; i--) { - if (mLeft[i] == UNDEFINED) { - if (i == 0) { - mLeft[i] = layoutBounds.x; - } else if (i < actualColumnCount - 1) { - mLeft[i] = mLeft[i + 1] - 1; - if (mLeft[i - 1] != UNDEFINED && mLeft[i] < mLeft[i - 1]) { - mLeft[i] = mLeft[i - 1]; - } - } else { - mLeft[i] = layoutBounds.x2(); - } - } - } - for (int i = actualRowCount - 1; i >= 0; i--) { - if (mTop[i] == UNDEFINED) { - if (i == 0) { - mTop[i] = layoutBounds.y; - } else if (i < actualRowCount - 1) { - mTop[i] = mTop[i + 1] - 1; - if (mTop[i - 1] != UNDEFINED && mTop[i] < mTop[i - 1]) { - mTop[i] = mTop[i - 1]; - } - } else { - mTop[i] = layoutBounds.y2(); - } - } - } - - // The bounds should be in ascending order now - if (false && GridLayoutRule.sDebugGridLayout) { - for (int i = 1; i < actualRowCount; i++) { - assert mTop[i + 1] >= mTop[i]; - } - for (int i = 0; i < actualColumnCount; i++) { - assert mLeft[i + 1] >= mLeft[i]; - } - } - } - - /** - * Determine, for each row and column, what the largest x and y edges are - * within that row or column. This is used to find a natural split point to - * suggest when adding something "to the right of" or "below" another view. - */ - private void initializeMaxBounds() { - mMaxRight = new int[actualColumnCount + 1]; - mMaxBottom = new int[actualRowCount + 1]; - - for (ViewData view : mChildViews) { - Rect bounds = view.node.getBounds(); - if (!bounds.isValid()) { - continue; - } - - if (!view.isSpacer()) { - int x2 = bounds.x2(); - int y2 = bounds.y2(); - int column = view.column; - int row = view.row; - int targetColumn = min(actualColumnCount - 1, - column + view.columnSpan - 1); - int targetRow = min(actualRowCount - 1, row + view.rowSpan - 1); - IViewMetadata metadata = mRulesEngine.getMetadata(view.node.getFqcn()); - if (metadata != null) { - Margins insets = metadata.getInsets(); - if (insets != null) { - x2 -= insets.right; - y2 -= insets.bottom; - } - } - if (mMaxRight[targetColumn] < x2 - && ((view.gravity & (GRAVITY_CENTER_HORIZ | GRAVITY_RIGHT)) == 0)) { - mMaxRight[targetColumn] = x2; - } - if (mMaxBottom[targetRow] < y2 - && ((view.gravity & (GRAVITY_CENTER_VERT | GRAVITY_BOTTOM)) == 0)) { - mMaxBottom[targetRow] = y2; - } - } - } - } - - /** - * Looks up the x[] and y[] locations of the columns and rows in the given GridLayout - * instance. - * - * @param view the GridLayout object, which should already have performed layout - * @return a pair of x[] and y[] integer arrays, or null if it could not be found - */ - public static Pair<int[], int[]> getAxisBounds(Object view) { - try { - Class<?> clz = view.getClass(); - String verticalAxisName = "verticalAxis"; - Field horizontalAxis; - try { - horizontalAxis = clz.getDeclaredField("horizontalAxis"); //$NON-NLS-1$ - } catch (NoSuchFieldException e) { - // Field names changed in KitKat - horizontalAxis = clz.getDeclaredField("mHorizontalAxis"); //$NON-NLS-1$ - verticalAxisName = "mVerticalAxis"; - } - Field verticalAxis = clz.getDeclaredField(verticalAxisName); - horizontalAxis.setAccessible(true); - verticalAxis.setAccessible(true); - Object horizontal = horizontalAxis.get(view); - Object vertical = verticalAxis.get(view); - Field locations = horizontal.getClass().getDeclaredField("locations"); //$NON-NLS-1$ - assert locations.getType().isArray() : locations.getType(); - locations.setAccessible(true); - Object horizontalLocations = locations.get(horizontal); - Object verticalLocations = locations.get(vertical); - int[] xs = (int[]) horizontalLocations; - int[] ys = (int[]) verticalLocations; - return Pair.of(xs, ys); - } catch (Throwable t) { - // Probably trying to show a GridLayout on a platform that does not support it. - // Return null to indicate that the grid bounds must be computed from view bounds. - return null; - } - } - - /** - * Add a new column. - * - * @param selectedChildren if null or empty, add the column at the end of the grid, - * and otherwise add it before the column of the first selected child - * @return the newly added column spacer - */ - public INode addColumn(List<? extends INode> selectedChildren) { - // Determine insert index - int newColumn = actualColumnCount; - if (selectedChildren != null && selectedChildren.size() > 0) { - INode first = selectedChildren.get(0); - ViewData view = getView(first); - newColumn = view.column; - } - - INode newView = addColumn(newColumn, null, UNDEFINED, false, UNDEFINED, UNDEFINED); - if (newView != null) { - mRulesEngine.select(Collections.singletonList(newView)); - } - - return newView; - } - - /** - * Adds a new column. - * - * @param newColumn the column index to insert before - * @param newView the {@link INode} to insert as the column spacer, which may be null - * (in which case a spacer is automatically created) - * @param columnWidthDp the width, in device independent pixels, of the column to be - * added (which may be {@link #UNDEFINED} - * @param split if true, split the existing column into two at the given x position - * @param row the row to add the newView to - * @param x the x position of the column we're inserting - * @return the column spacer - */ - public INode addColumn(int newColumn, INode newView, int columnWidthDp, - boolean split, int row, int x) { - // Insert a new column - actualColumnCount++; - if (declaredColumnCount != UNDEFINED) { - declaredColumnCount++; - setGridAttribute(layout, ATTR_COLUMN_COUNT, declaredColumnCount); - } - - boolean isLastColumn = true; - for (ViewData view : mChildViews) { - if (view.column >= newColumn) { - isLastColumn = false; - break; - } - } - - for (ViewData view : mChildViews) { - boolean columnSpanSet = false; - - int endColumn = view.column + view.columnSpan; - if (view.column >= newColumn || endColumn == newColumn) { - if (view.column == newColumn || endColumn == newColumn) { - //if (view.row == 0) { - if (newView == null && !isLastColumn) { - // Insert a new spacer - int index = getChildIndex(layout.getChildren(), view.node); - assert view.index == index; // TODO: Get rid of getter - if (endColumn == newColumn) { - // This cell -ends- at the desired position: insert it after - index++; - } - - ViewData newViewData = addSpacer(layout, index, - split ? row : UNDEFINED, - split ? newColumn - 1 : UNDEFINED, - columnWidthDp != UNDEFINED ? columnWidthDp : DEFAULT_CELL_WIDTH, - DEFAULT_CELL_HEIGHT); - newViewData.column = newColumn - 1; - newViewData.row = row; - newView = newViewData.node; - } - - // Set the actual row number on the first cell on the new row. - // This means we don't really need the spacer above to imply - // the new row number, but we use the spacer to assign the row - // some height. - if (view.column == newColumn) { - view.column++; - setGridAttribute(view.node, ATTR_LAYOUT_COLUMN, view.column); - } // else: endColumn == newColumn: handled below - } else if (getGridAttribute(view.node, ATTR_LAYOUT_COLUMN) != null) { - view.column++; - setGridAttribute(view.node, ATTR_LAYOUT_COLUMN, view.column); - } - } else if (endColumn > newColumn) { - view.columnSpan++; - setColumnSpanAttribute(view.node, view.columnSpan); - columnSpanSet = true; - } - - if (split && !columnSpanSet && view.node.getBounds().x2() > x) { - if (view.node.getBounds().x < x) { - view.columnSpan++; - setColumnSpanAttribute(view.node, view.columnSpan); - } - } - } - - // Hardcode the row numbers if the last column is a new column such that - // they don't jump back to backfill the previous row's new last cell - if (isLastColumn) { - for (ViewData view : mChildViews) { - if (view.column == 0 && view.row > 0) { - setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); - } - } - if (split) { - assert newView == null; - addSpacer(layout, -1, row, newColumn -1, - columnWidthDp != UNDEFINED ? columnWidthDp : DEFAULT_CELL_WIDTH, - SPACER_SIZE_DP); - } - } - - return newView; - } - - /** - * Removes the columns containing the given selection - * - * @param selectedChildren a list of nodes whose columns should be deleted - */ - public void removeColumns(List<? extends INode> selectedChildren) { - if (selectedChildren.size() == 0) { - return; - } - - // Figure out which columns should be removed - Set<Integer> removeColumns = new HashSet<Integer>(); - Set<ViewData> removedViews = new HashSet<ViewData>(); - for (INode child : selectedChildren) { - ViewData view = getView(child); - removedViews.add(view); - removeColumns.add(view.column); - } - // Sort them in descending order such that we can process each - // deletion independently - List<Integer> removed = new ArrayList<Integer>(removeColumns); - Collections.sort(removed, Collections.reverseOrder()); - - for (int removedColumn : removed) { - // Remove column. - // First, adjust column count. - // TODO: Don't do this if the column being deleted is outside - // the declared column range! - // TODO: Do this under a write lock? / editXml lock? - actualColumnCount--; - if (declaredColumnCount != UNDEFINED) { - declaredColumnCount--; - } - - // Remove any elements that begin in the deleted columns... - // If they have colspan > 1, then we must insert a spacer instead. - // For any other elements that overlap, we need to subtract from the span. - - for (ViewData view : mChildViews) { - if (view.column == removedColumn) { - int index = getChildIndex(layout.getChildren(), view.node); - assert view.index == index; // TODO: Get rid of getter - if (view.columnSpan > 1) { - // Make a new spacer which is the width of the following - // columns - int columnWidth = getColumnWidth(removedColumn, view.columnSpan) - - getColumnWidth(removedColumn, 1); - int columnWidthDip = mRulesEngine.pxToDp(columnWidth); - ViewData spacer = addSpacer(layout, index, UNDEFINED, UNDEFINED, - columnWidthDip, SPACER_SIZE_DP); - spacer.row = 0; - spacer.column = removedColumn; - } - layout.removeChild(view.node); - } else if (view.column < removedColumn - && view.column + view.columnSpan > removedColumn) { - // Subtract column span to skip this item - view.columnSpan--; - setColumnSpanAttribute(view.node, view.columnSpan); - } else if (view.column > removedColumn) { - view.column--; - if (getGridAttribute(view.node, ATTR_LAYOUT_COLUMN) != null) { - setGridAttribute(view.node, ATTR_LAYOUT_COLUMN, view.column); - } - } - } - } - - // Remove children from child list! - if (removedViews.size() <= 2) { - mChildViews.removeAll(removedViews); - } else { - List<ViewData> remaining = - new ArrayList<ViewData>(mChildViews.size() - removedViews.size()); - for (ViewData view : mChildViews) { - if (!removedViews.contains(view)) { - remaining.add(view); - } - } - mChildViews = remaining; - } - - //if (declaredColumnCount != UNDEFINED) { - setGridAttribute(layout, ATTR_COLUMN_COUNT, actualColumnCount); - //} - - } - - /** - * Add a new row. - * - * @param selectedChildren if null or empty, add the row at the bottom of the grid, - * and otherwise add it before the row of the first selected child - * @return the newly added row spacer - */ - public INode addRow(List<? extends INode> selectedChildren) { - // Determine insert index - int newRow = actualRowCount; - if (selectedChildren.size() > 0) { - INode first = selectedChildren.get(0); - ViewData view = getView(first); - newRow = view.row; - } - - INode newView = addRow(newRow, null, UNDEFINED, false, UNDEFINED, UNDEFINED); - if (newView != null) { - mRulesEngine.select(Collections.singletonList(newView)); - } - - return newView; - } - - /** - * Adds a new column. - * - * @param newRow the row index to insert before - * @param newView the {@link INode} to insert as the row spacer, which may be null (in - * which case a spacer is automatically created) - * @param rowHeightDp the height, in device independent pixels, of the row to be added - * (which may be {@link #UNDEFINED} - * @param split if true, split the existing row into two at the given y position - * @param column the column to add the newView to - * @param y the y position of the row we're inserting - * @return the row spacer - */ - public INode addRow(int newRow, INode newView, int rowHeightDp, boolean split, - int column, int y) { - actualRowCount++; - if (declaredRowCount != UNDEFINED) { - declaredRowCount++; - setGridAttribute(layout, ATTR_ROW_COUNT, declaredRowCount); - } - - boolean added = false; - for (ViewData view : mChildViews) { - if (view.row >= newRow) { - // Adjust the column count - if (view.row == newRow && view.column == 0) { - // Insert a new spacer - if (newView == null) { - int index = getChildIndex(layout.getChildren(), view.node); - assert view.index == index; // TODO: Get rid of getter - if (declaredColumnCount != UNDEFINED && !split) { - setGridAttribute(layout, ATTR_COLUMN_COUNT, declaredColumnCount); - } - ViewData newViewData = addSpacer(layout, index, - split ? newRow - 1 : UNDEFINED, - split ? column : UNDEFINED, - SPACER_SIZE_DP, - rowHeightDp != UNDEFINED ? rowHeightDp : DEFAULT_CELL_HEIGHT); - newViewData.column = column; - newViewData.row = newRow - 1; - newView = newViewData.node; - } - - // Set the actual row number on the first cell on the new row. - // This means we don't really need the spacer above to imply - // the new row number, but we use the spacer to assign the row - // some height. - view.row++; - setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); - - added = true; - } else if (getGridAttribute(view.node, ATTR_LAYOUT_ROW) != null) { - view.row++; - setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); - } - } else { - int endRow = view.row + view.rowSpan; - if (endRow > newRow) { - view.rowSpan++; - setRowSpanAttribute(view.node, view.rowSpan); - } else if (split && view.node.getBounds().y2() > y) { - if (view.node.getBounds().y < y) { - view.rowSpan++; - setRowSpanAttribute(view.node, view.rowSpan); - } - } - } - } - - if (!added) { - // Append a row at the end - if (newView == null) { - ViewData newViewData = addSpacer(layout, -1, UNDEFINED, UNDEFINED, - SPACER_SIZE_DP, - rowHeightDp != UNDEFINED ? rowHeightDp : DEFAULT_CELL_HEIGHT); - newViewData.column = column; - // TODO: MAke sure this row number is right! - newViewData.row = split ? newRow - 1 : newRow; - newView = newViewData.node; - } - if (declaredColumnCount != UNDEFINED && !split) { - setGridAttribute(layout, ATTR_COLUMN_COUNT, declaredColumnCount); - } - if (split) { - setGridAttribute(newView, ATTR_LAYOUT_ROW, newRow - 1); - setGridAttribute(newView, ATTR_LAYOUT_COLUMN, column); - } - } - - return newView; - } - - /** - * Removes the rows containing the given selection - * - * @param selectedChildren a list of nodes whose rows should be deleted - */ - public void removeRows(List<? extends INode> selectedChildren) { - if (selectedChildren.size() == 0) { - return; - } - - // Figure out which rows should be removed - Set<ViewData> removedViews = new HashSet<ViewData>(); - Set<Integer> removedRows = new HashSet<Integer>(); - for (INode child : selectedChildren) { - ViewData view = getView(child); - removedViews.add(view); - removedRows.add(view.row); - } - // Sort them in descending order such that we can process each - // deletion independently - List<Integer> removed = new ArrayList<Integer>(removedRows); - Collections.sort(removed, Collections.reverseOrder()); - - for (int removedRow : removed) { - // Remove row. - // First, adjust row count. - // TODO: Don't do this if the row being deleted is outside - // the declared row range! - actualRowCount--; - if (declaredRowCount != UNDEFINED) { - declaredRowCount--; - setGridAttribute(layout, ATTR_ROW_COUNT, declaredRowCount); - } - - // Remove any elements that begin in the deleted rows... - // If they have colspan > 1, then we must hardcode a new row number - // instead. - // For any other elements that overlap, we need to subtract from the span. - - for (ViewData view : mChildViews) { - if (view.row == removedRow) { - // We don't have to worry about a rowSpan > 1 here, because even - // if it is, those rowspans are not used to assign default row/column - // positions for other cells -// TODO: Check this; it differs from the removeColumns logic! - layout.removeChild(view.node); - } else if (view.row > removedRow) { - view.row--; - if (getGridAttribute(view.node, ATTR_LAYOUT_ROW) != null) { - setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); - } - } else if (view.row < removedRow - && view.row + view.rowSpan > removedRow) { - // Subtract row span to skip this item - view.rowSpan--; - setRowSpanAttribute(view.node, view.rowSpan); - } - } - } - - // Remove children from child list! - if (removedViews.size() <= 2) { - mChildViews.removeAll(removedViews); - } else { - List<ViewData> remaining = - new ArrayList<ViewData>(mChildViews.size() - removedViews.size()); - for (ViewData view : mChildViews) { - if (!removedViews.contains(view)) { - remaining.add(view); - } - } - mChildViews = remaining; - } - } - - /** - * Returns the row containing the given y line - * - * @param y the vertical position - * @return the row containing the given line - */ - public int getRow(int y) { - int row = Arrays.binarySearch(mTop, y); - if (row == -1) { - // Smaller than the first element; just use the first row - return 0; - } else if (row < 0) { - row = -(row + 2); - } - - return row; - } - - /** - * Returns the column containing the given x line - * - * @param x the horizontal position - * @return the column containing the given line - */ - public int getColumn(int x) { - int column = Arrays.binarySearch(mLeft, x); - if (column == -1) { - // Smaller than the first element; just use the first column - return 0; - } else if (column < 0) { - column = -(column + 2); - } - - return column; - } - - /** - * Returns the closest row to the given y line. This is - * either the row containing the line, or the row below it. - * - * @param y the vertical position - * @return the closest row - */ - public int getClosestRow(int y) { - int row = Arrays.binarySearch(mTop, y); - if (row == -1) { - // Smaller than the first element; just use the first column - return 0; - } else if (row < 0) { - row = -(row + 2); - } - - if (getRowDistance(row, y) < getRowDistance(row + 1, y)) { - return row; - } else { - return row + 1; - } - } - - /** - * Returns the closest column to the given x line. This is - * either the column containing the line, or the column following it. - * - * @param x the horizontal position - * @return the closest column - */ - public int getClosestColumn(int x) { - int column = Arrays.binarySearch(mLeft, x); - if (column == -1) { - // Smaller than the first element; just use the first column - return 0; - } else if (column < 0) { - column = -(column + 2); - } - - if (getColumnDistance(column, x) < getColumnDistance(column + 1, x)) { - return column; - } else { - return column + 1; - } - } - - /** - * Returns the distance between the given x position and the beginning of the given column - * - * @param column the column - * @param x the x position - * @return the distance between the two - */ - public int getColumnDistance(int column, int x) { - return abs(getColumnX(column) - x); - } - - /** - * Returns the actual width of the given column. This returns the difference between - * the rightmost edge of the views (not including spacers) and the left edge of the - * column. - * - * @param column the column - * @return the actual width of the non-spacer views in the column - */ - public int getColumnActualWidth(int column) { - return getColumnMaxX(column) - getColumnX(column); - } - - /** - * Returns the distance between the given y position and the top of the given row - * - * @param row the row - * @param y the y position - * @return the distance between the two - */ - public int getRowDistance(int row, int y) { - return abs(getRowY(row) - y); - } - - /** - * Returns the y position of the top of the given row - * - * @param row the target row - * @return the y position of its top edge - */ - public int getRowY(int row) { - return mTop[min(mTop.length - 1, max(0, row))]; - } - - /** - * Returns the bottom-most edge of any of the non-spacer children in the given row - * - * @param row the target row - * @return the bottom-most edge of any of the non-spacer children in the row - */ - public int getRowMaxY(int row) { - return mMaxBottom[min(mMaxBottom.length - 1, max(0, row))]; - } - - /** - * Returns the actual height of the given row. This returns the difference between - * the bottom-most edge of the views (not including spacers) and the top edge of the - * row. - * - * @param row the row - * @return the actual height of the non-spacer views in the row - */ - public int getRowActualHeight(int row) { - return getRowMaxY(row) - getRowY(row); - } - - /** - * Returns a list of all the nodes that intersects the rows in the range - * {@code y1 <= y <= y2}. - * - * @param y1 the starting y, inclusive - * @param y2 the ending y, inclusive - * @return a list of nodes intersecting the given rows, never null but possibly empty - */ - public Collection<INode> getIntersectsRow(int y1, int y2) { - List<INode> nodes = new ArrayList<INode>(); - - for (ViewData view : mChildViews) { - if (!view.isSpacer()) { - Rect bounds = view.node.getBounds(); - if (bounds.y2() >= y1 && bounds.y <= y2) { - nodes.add(view.node); - } - } - } - - return nodes; - } - - /** - * Returns the height of the given row or rows (if the rowSpan is greater than 1) - * - * @param row the target row - * @param rowSpan the row span - * @return the height in pixels of the given rows - */ - public int getRowHeight(int row, int rowSpan) { - return getRowY(row + rowSpan) - getRowY(row); - } - - /** - * Returns the x position of the left edge of the given column - * - * @param column the target column - * @return the x position of its left edge - */ - public int getColumnX(int column) { - return mLeft[min(mLeft.length - 1, max(0, column))]; - } - - /** - * Returns the rightmost edge of any of the non-spacer children in the given row - * - * @param column the target column - * @return the rightmost edge of any of the non-spacer children in the column - */ - public int getColumnMaxX(int column) { - return mMaxRight[min(mMaxRight.length - 1, max(0, column))]; - } - - /** - * Returns the width of the given column or columns (if the columnSpan is greater than 1) - * - * @param column the target column - * @param columnSpan the column span - * @return the width in pixels of the given columns - */ - public int getColumnWidth(int column, int columnSpan) { - return getColumnX(column + columnSpan) - getColumnX(column); - } - - /** - * Returns the bounds of the cell at the given row and column position, with the given - * row and column spans. - * - * @param row the target row - * @param column the target column - * @param rowSpan the row span - * @param columnSpan the column span - * @return the bounds, in pixels, of the given cell - */ - public Rect getCellBounds(int row, int column, int rowSpan, int columnSpan) { - return new Rect(getColumnX(column), getRowY(row), - getColumnWidth(column, columnSpan), - getRowHeight(row, rowSpan)); - } - - /** - * Produces a display of view contents along with the pixel positions of each - * row/column, like the following (used for diagnostics only) - * - * <pre> - * |0 |49 |143 |192 |240 - * 36| | |button2 | - * 72| |radioButton1 |button2 | - * 74|button1 |radioButton1 |button2 | - * 108|button1 | |button2 | - * 110| | |button2 | - * 149| | | | - * 320 - * </pre> - */ - @Override - public String toString() { - // Dump out the view table - int cellWidth = 25; - - List<List<List<ViewData>>> rowList = new ArrayList<List<List<ViewData>>>(mTop.length); - for (int row = 0; row < mTop.length; row++) { - List<List<ViewData>> columnList = new ArrayList<List<ViewData>>(mLeft.length); - for (int col = 0; col < mLeft.length; col++) { - columnList.add(new ArrayList<ViewData>(4)); - } - rowList.add(columnList); - } - for (ViewData view : mChildViews) { - for (int i = 0; i < view.rowSpan; i++) { - if (view.row + i > mTop.length) { // Guard against bogus span values - break; - } - if (rowList.size() <= view.row + i) { - break; - } - for (int j = 0; j < view.columnSpan; j++) { - List<List<ViewData>> columnList = rowList.get(view.row + i); - if (columnList.size() <= view.column + j) { - break; - } - columnList.get(view.column + j).add(view); - } - } - } - - StringWriter stringWriter = new StringWriter(); - PrintWriter out = new PrintWriter(stringWriter); - out.printf("%" + cellWidth + "s", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - for (int col = 0; col < actualColumnCount + 1; col++) { - out.printf("|%-" + (cellWidth - 1) + "d", mLeft[col]); //$NON-NLS-1$ //$NON-NLS-2$ - } - out.printf("\n"); //$NON-NLS-1$ - for (int row = 0; row < actualRowCount + 1; row++) { - out.printf("%" + cellWidth + "d", mTop[row]); //$NON-NLS-1$ //$NON-NLS-2$ - if (row == actualRowCount) { - break; - } - for (int col = 0; col < actualColumnCount; col++) { - List<ViewData> views = rowList.get(row).get(col); - - StringBuilder sb = new StringBuilder(); - for (ViewData view : views) { - String id = view != null ? view.getId() : ""; //$NON-NLS-1$ - if (id.startsWith(NEW_ID_PREFIX)) { - id = id.substring(NEW_ID_PREFIX.length()); - } - if (id.length() > cellWidth - 2) { - id = id.substring(0, cellWidth - 2); - } - if (sb.length() > 0) { - sb.append(','); - } - sb.append(id); - } - String cellString = sb.toString(); - if (cellString.contains(",") && cellString.length() > cellWidth - 2) { //$NON-NLS-1$ - cellString = cellString.substring(0, cellWidth - 6) + "...,"; //$NON-NLS-1$ - } - out.printf("|%-" + (cellWidth - 2) + "s ", cellString); //$NON-NLS-1$ //$NON-NLS-2$ - } - out.printf("\n"); //$NON-NLS-1$ - } - - out.flush(); - return stringWriter.toString(); - } - - /** - * Split a cell into two or three columns. - * - * @param newColumn The column number to insert before - * @param insertMarginColumn If false, then the cell at newColumn -1 is split with the - * left part taking up exactly columnWidthDp dips. If true, then the column - * is split twice; the left part is the implicit width of the column, the - * new middle (margin) column is exactly the columnWidthDp size and the - * right column is the remaining space of the old cell. - * @param columnWidthDp The width of the column inserted before the new column (or if - * insertMarginColumn is false, then the width of the margin column) - * @param x the x coordinate of the new column - */ - public void splitColumn(int newColumn, boolean insertMarginColumn, int columnWidthDp, int x) { - actualColumnCount++; - - // Insert a new column - if (declaredColumnCount != UNDEFINED) { - declaredColumnCount++; - if (insertMarginColumn) { - declaredColumnCount++; - } - setGridAttribute(layout, ATTR_COLUMN_COUNT, declaredColumnCount); - } - - // Are we inserting a new last column in the grid? That requires some special handling... - boolean isLastColumn = true; - for (ViewData view : mChildViews) { - if (view.column >= newColumn) { - isLastColumn = false; - break; - } - } - - // Hardcode the row numbers if the last column is a new column such that - // they don't jump back to backfill the previous row's new last cell: - // TODO: Only do this for horizontal layouts! - if (isLastColumn) { - for (ViewData view : mChildViews) { - if (view.column == 0 && view.row > 0) { - if (getGridAttribute(view.node, ATTR_LAYOUT_ROW) == null) { - setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); - } - } - } - } - - // Find the spacer which marks this column, and if found, mark it as a split - ViewData prevColumnSpacer = null; - for (ViewData view : mChildViews) { - if (view.column == newColumn - 1 && view.isColumnSpacer()) { - prevColumnSpacer = view; - break; - } - } - - // Process all existing grid elements: - // * Increase column numbers for all columns that have a hardcoded column number - // greater than the new column - // * Set an explicit column=0 where needed (TODO: Implement this) - // * Increase the columnSpan for all columns that overlap the newly inserted column edge - // * Split the spacer which defined the size of this column into two - // (and if not found, create a new spacer) - // - for (ViewData view : mChildViews) { - if (view == prevColumnSpacer) { - continue; - } - - INode node = view.node; - int column = view.column; - if (column > newColumn || (column == newColumn && view.node.getBounds().x2() > x)) { - // ALWAYS set the column, because - // (1) if it has been set, it needs to be corrected - // (2) if it has not been set, it needs to be set to cause this column - // to skip over the new column (there may be no views for the new - // column on this row). - // TODO: Enhance this such that we only set the column to a skip number - // where necessary, e.g. only on the FIRST view on this row following the - // skipped column! - - //if (getGridAttribute(node, ATTR_LAYOUT_COLUMN) != null) { - view.column += insertMarginColumn ? 2 : 1; - setGridAttribute(node, ATTR_LAYOUT_COLUMN, view.column); - //} - } else if (!view.isSpacer()) { - // Adjust the column span? We must increase it if - // (1) the new column is inside the range [column, column + columnSpan] - // (2) the new column is within the last cell in the column span, - // and the exact X location of the split is within the horizontal - // *bounds* of this node (provided it has gravity=left) - // (3) the new column is within the last cell and the cell has gravity - // right or gravity center - int endColumn = column + view.columnSpan; - if (endColumn > newColumn - || endColumn == newColumn && (view.node.getBounds().x2() > x - || GravityHelper.isConstrainedHorizontally(view.gravity) - && !GravityHelper.isLeftAligned(view.gravity))) { - // This cell spans the new insert position, so increment the column span - view.columnSpan += insertMarginColumn ? 2 : 1; - setColumnSpanAttribute(node, view.columnSpan); - } - } - } - - // Insert new spacer: - if (prevColumnSpacer != null) { - int px = getColumnWidth(newColumn - 1, 1); - if (insertMarginColumn || columnWidthDp == 0) { - px -= getColumnActualWidth(newColumn - 1); - } - int dp = mRulesEngine.pxToDp(px); - int remaining = dp - columnWidthDp; - if (remaining > 0) { - prevColumnSpacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, - String.format(VALUE_N_DP, remaining)); - prevColumnSpacer.column = insertMarginColumn ? newColumn + 1 : newColumn; - setGridAttribute(prevColumnSpacer.node, ATTR_LAYOUT_COLUMN, - prevColumnSpacer.column); - } - } - - if (columnWidthDp > 0) { - int index = prevColumnSpacer != null ? prevColumnSpacer.index : -1; - - addSpacer(layout, index, 0, insertMarginColumn ? newColumn : newColumn - 1, - columnWidthDp, SPACER_SIZE_DP); - } - } - - /** - * Split a cell into two or three rows. - * - * @param newRow The row number to insert before - * @param insertMarginRow If false, then the cell at newRow -1 is split with the above - * part taking up exactly rowHeightDp dips. If true, then the row is split - * twice; the top part is the implicit height of the row, the new middle - * (margin) row is exactly the rowHeightDp size and the bottom column is - * the remaining space of the old cell. - * @param rowHeightDp The height of the row inserted before the new row (or if - * insertMarginRow is false, then the height of the margin row) - * @param y the y coordinate of the new row - */ - public void splitRow(int newRow, boolean insertMarginRow, int rowHeightDp, int y) { - actualRowCount++; - - // Insert a new row - if (declaredRowCount != UNDEFINED) { - declaredRowCount++; - if (insertMarginRow) { - declaredRowCount++; - } - setGridAttribute(layout, ATTR_ROW_COUNT, declaredRowCount); - } - - // Find the spacer which marks this row, and if found, mark it as a split - ViewData prevRowSpacer = null; - for (ViewData view : mChildViews) { - if (view.row == newRow - 1 && view.isRowSpacer()) { - prevRowSpacer = view; - break; - } - } - - // Se splitColumn() for details - for (ViewData view : mChildViews) { - if (view == prevRowSpacer) { - continue; - } - - INode node = view.node; - int row = view.row; - if (row > newRow || (row == newRow && view.node.getBounds().y2() > y)) { - //if (getGridAttribute(node, ATTR_LAYOUT_ROW) != null) { - view.row += insertMarginRow ? 2 : 1; - setGridAttribute(node, ATTR_LAYOUT_ROW, view.row); - //} - } else if (!view.isSpacer()) { - int endRow = row + view.rowSpan; - if (endRow > newRow - || endRow == newRow && (view.node.getBounds().y2() > y - || GravityHelper.isConstrainedVertically(view.gravity) - && !GravityHelper.isTopAligned(view.gravity))) { - // This cell spans the new insert position, so increment the row span - view.rowSpan += insertMarginRow ? 2 : 1; - setRowSpanAttribute(node, view.rowSpan); - } - } - } - - // Insert new spacer: - if (prevRowSpacer != null) { - int px = getRowHeight(newRow - 1, 1); - if (insertMarginRow || rowHeightDp == 0) { - px -= getRowActualHeight(newRow - 1); - } - int dp = mRulesEngine.pxToDp(px); - int remaining = dp - rowHeightDp; - if (remaining > 0) { - prevRowSpacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, - String.format(VALUE_N_DP, remaining)); - prevRowSpacer.row = insertMarginRow ? newRow + 1 : newRow; - setGridAttribute(prevRowSpacer.node, ATTR_LAYOUT_ROW, prevRowSpacer.row); - } - } - - if (rowHeightDp > 0) { - int index = prevRowSpacer != null ? prevRowSpacer.index : -1; - addSpacer(layout, index, insertMarginRow ? newRow : newRow - 1, - 0, SPACER_SIZE_DP, rowHeightDp); - } - } - - /** - * Data about a view in a table; this is not the same as a cell because multiple views - * can share a single cell, and a view can span many cells. - */ - public class ViewData { - public final INode node; - public final int index; - public int row; - public int column; - public int rowSpan; - public int columnSpan; - public int gravity; - - ViewData(INode n, int index) { - node = n; - this.index = index; - - column = getGridAttribute(n, ATTR_LAYOUT_COLUMN, UNDEFINED); - columnSpan = getGridAttribute(n, ATTR_LAYOUT_COLUMN_SPAN, 1); - row = getGridAttribute(n, ATTR_LAYOUT_ROW, UNDEFINED); - rowSpan = getGridAttribute(n, ATTR_LAYOUT_ROW_SPAN, 1); - gravity = GravityHelper.getGravity(getGridAttribute(n, ATTR_LAYOUT_GRAVITY), 0); - } - - /** Applies the column and row fields into the XML model */ - void applyPositionAttributes() { - setGridAttribute(node, ATTR_LAYOUT_COLUMN, column); - setGridAttribute(node, ATTR_LAYOUT_ROW, row); - } - - /** Returns the id of this node, or makes one up for display purposes */ - String getId() { - String id = node.getStringAttr(ANDROID_URI, ATTR_ID); - if (id == null) { - id = "<unknownid>"; //$NON-NLS-1$ - String fqn = node.getFqcn(); - fqn = fqn.substring(fqn.lastIndexOf('.') + 1); - id = fqn + "-" - + Integer.toString(System.identityHashCode(node)).substring(0, 3); - } - - return id; - } - - /** Returns true if this {@link ViewData} represents a spacer */ - boolean isSpacer() { - return isSpace(node.getFqcn()); - } - - /** - * Returns true if this {@link ViewData} represents a column spacer - */ - boolean isColumnSpacer() { - return isSpacer() && - // Any spacer not found in column 0 is a column spacer since we - // place all horizontal spacers in column 0 - ((column > 0) - // TODO: Find a cleaner way. Maybe set ids on the elements in (0,0) and - // for column distinguish by id. Or at least only do this for column 0! - || !SPACER_SIZE.equals(node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_WIDTH))); - } - - /** - * Returns true if this {@link ViewData} represents a row spacer - */ - boolean isRowSpacer() { - return isSpacer() && - // Any spacer not found in row 0 is a row spacer since we - // place all vertical spacers in row 0 - ((row > 0) - // TODO: Find a cleaner way. Maybe set ids on the elements in (0,0) and - // for column distinguish by id. Or at least only do this for column 0! - || !SPACER_SIZE.equals(node.getStringAttr(ANDROID_URI, ATTR_LAYOUT_HEIGHT))); - } - } - - /** - * Sets the column span of the given node to the given value (or if the value is 1, - * removes it) - * - * @param node the target node - * @param span the new column span - */ - public void setColumnSpanAttribute(INode node, int span) { - setGridAttribute(node, ATTR_LAYOUT_COLUMN_SPAN, span > 1 ? Integer.toString(span) : null); - } - - /** - * Sets the row span of the given node to the given value (or if the value is 1, - * removes it) - * - * @param node the target node - * @param span the new row span - */ - public void setRowSpanAttribute(INode node, int span) { - setGridAttribute(node, ATTR_LAYOUT_ROW_SPAN, span > 1 ? Integer.toString(span) : null); - } - - /** Returns the index of the given target node in the given child node array */ - static int getChildIndex(INode[] children, INode target) { - int index = 0; - for (INode child : children) { - if (child == target) { - return index; - } - index++; - } - - return -1; - } - - /** - * Update the model to account for the given nodes getting deleted. The nodes - * are not actually deleted by this method; that is assumed to be performed by the - * caller. Instead this method performs whatever model updates are necessary to - * preserve the grid structure. - * - * @param nodes the nodes to be deleted - */ - public void onDeleted(@NonNull List<INode> nodes) { - if (nodes.size() == 0) { - return; - } - - // Attempt to clean up spacer objects for any newly-empty rows or columns - // as the result of this deletion - - Set<INode> deleted = new HashSet<INode>(); - - for (INode child : nodes) { - // We don't care about deletion of spacers - String fqcn = child.getFqcn(); - if (fqcn.equals(FQCN_SPACE) || fqcn.equals(FQCN_SPACE_V7)) { - continue; - } - deleted.add(child); - } - - Set<Integer> usedColumns = new HashSet<Integer>(actualColumnCount); - Set<Integer> usedRows = new HashSet<Integer>(actualRowCount); - Multimap<Integer, ViewData> columnSpacers = ArrayListMultimap.create(actualColumnCount, 2); - Multimap<Integer, ViewData> rowSpacers = ArrayListMultimap.create(actualRowCount, 2); - Set<ViewData> removedViews = new HashSet<ViewData>(); - - for (ViewData view : mChildViews) { - if (deleted.contains(view.node)) { - removedViews.add(view); - } else if (view.isColumnSpacer()) { - columnSpacers.put(view.column, view); - } else if (view.isRowSpacer()) { - rowSpacers.put(view.row, view); - } else { - usedColumns.add(Integer.valueOf(view.column)); - usedRows.add(Integer.valueOf(view.row)); - } - } - - if (usedColumns.size() == 0 || usedRows.size() == 0) { - // No more views - just remove all the spacers - for (ViewData spacer : columnSpacers.values()) { - layout.removeChild(spacer.node); - } - for (ViewData spacer : rowSpacers.values()) { - layout.removeChild(spacer.node); - } - mChildViews.clear(); - actualColumnCount = 0; - declaredColumnCount = 2; - actualRowCount = 0; - declaredRowCount = UNDEFINED; - setGridAttribute(layout, ATTR_COLUMN_COUNT, 2); - - return; - } - - // Determine columns to introduce spacers into: - // This is tricky; I should NOT combine spacers if there are cells tied to - // individual ones - - // TODO: Invalidate column sizes too! Otherwise repeated updates might get confused! - // Similarly, inserts need to do the same! - - // Produce map of old column numbers to new column numbers - // Collapse regions of consecutive space and non-space ranges together - int[] columnMap = new int[actualColumnCount + 1]; // +1: Easily handle columnSpans as well - int newColumn = 0; - boolean prevUsed = usedColumns.contains(0); - for (int column = 1; column < actualColumnCount; column++) { - boolean used = usedColumns.contains(column); - if (used || prevUsed != used) { - newColumn++; - prevUsed = used; - } - columnMap[column] = newColumn; - } - newColumn++; - columnMap[actualColumnCount] = newColumn; - assert columnMap[0] == 0; - - int[] rowMap = new int[actualRowCount + 1]; // +1: Easily handle rowSpans as well - int newRow = 0; - prevUsed = usedRows.contains(0); - for (int row = 1; row < actualRowCount; row++) { - boolean used = usedRows.contains(row); - if (used || prevUsed != used) { - newRow++; - prevUsed = used; - } - rowMap[row] = newRow; - } - newRow++; - rowMap[actualRowCount] = newRow; - assert rowMap[0] == 0; - - - // Adjust column and row numbers to account for deletions: for a given cell, if it - // is to the right of a deleted column, reduce its column number, and if it only - // spans across the deleted column, reduce its column span. - for (ViewData view : mChildViews) { - if (removedViews.contains(view)) { - continue; - } - int newColumnStart = columnMap[Math.min(columnMap.length - 1, view.column)]; - // Gracefully handle rogue/invalid columnSpans in the XML - int newColumnEnd = columnMap[Math.min(columnMap.length - 1, - view.column + view.columnSpan)]; - if (newColumnStart != view.column) { - view.column = newColumnStart; - setGridAttribute(view.node, ATTR_LAYOUT_COLUMN, view.column); - } - - int columnSpan = newColumnEnd - newColumnStart; - if (columnSpan != view.columnSpan) { - if (columnSpan >= 1) { - view.columnSpan = columnSpan; - setColumnSpanAttribute(view.node, view.columnSpan); - } // else: merging spacing columns together - } - - - int newRowStart = rowMap[Math.min(rowMap.length - 1, view.row)]; - int newRowEnd = rowMap[Math.min(rowMap.length - 1, view.row + view.rowSpan)]; - if (newRowStart != view.row) { - view.row = newRowStart; - setGridAttribute(view.node, ATTR_LAYOUT_ROW, view.row); - } - - int rowSpan = newRowEnd - newRowStart; - if (rowSpan != view.rowSpan) { - if (rowSpan >= 1) { - view.rowSpan = rowSpan; - setRowSpanAttribute(view.node, view.rowSpan); - } // else: merging spacing rows together - } - } - - // Merge spacers (and add spacers for newly empty columns) - int start = 0; - while (start < actualColumnCount) { - // Find next unused span - while (start < actualColumnCount && usedColumns.contains(start)) { - start++; - } - if (start == actualColumnCount) { - break; - } - assert !usedColumns.contains(start); - // Find the next span of unused columns and produce a SINGLE - // spacer for that range (unless it's a zero-sized columns) - int end = start + 1; - for (; end < actualColumnCount; end++) { - if (usedColumns.contains(end)) { - break; - } - } - - // Add up column sizes - int width = getColumnWidth(start, end - start); - - // Find all spacers: the first one found should be moved to the start column - // and assigned to the full height of the columns, and - // the column count reduced by the corresponding amount - - // TODO: if width = 0, fully remove - - boolean isFirstSpacer = true; - for (int column = start; column < end; column++) { - Collection<ViewData> spacers = columnSpacers.get(column); - if (spacers != null && !spacers.isEmpty()) { - // Avoid ConcurrentModificationException since we're inserting into the - // map within this loop (always at a different index, but the map doesn't - // know that) - spacers = new ArrayList<ViewData>(spacers); - for (ViewData spacer : spacers) { - if (isFirstSpacer) { - isFirstSpacer = false; - spacer.column = columnMap[start]; - setGridAttribute(spacer.node, ATTR_LAYOUT_COLUMN, spacer.column); - if (end - start > 1) { - // Compute a merged width for all the spacers (not needed if - // there's just one spacer; it should already have the correct width) - int columnWidthDp = mRulesEngine.pxToDp(width); - spacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, - String.format(VALUE_N_DP, columnWidthDp)); - } - columnSpacers.put(start, spacer); - } else { - removedViews.add(spacer); // Mark for model removal - layout.removeChild(spacer.node); - } - } - } - } - - if (isFirstSpacer) { - // No spacer: create one - int columnWidthDp = mRulesEngine.pxToDp(width); - addSpacer(layout, -1, UNDEFINED, columnMap[start], columnWidthDp, DEFAULT_CELL_HEIGHT); - } - - start = end; - } - actualColumnCount = newColumn; -//if (usedColumns.contains(newColumn)) { -// // TODO: This may be totally wrong for right aligned content! -// actualColumnCount++; -//} - - // Merge spacers for rows - start = 0; - while (start < actualRowCount) { - // Find next unused span - while (start < actualRowCount && usedRows.contains(start)) { - start++; - } - if (start == actualRowCount) { - break; - } - assert !usedRows.contains(start); - // Find the next span of unused rows and produce a SINGLE - // spacer for that range (unless it's a zero-sized rows) - int end = start + 1; - for (; end < actualRowCount; end++) { - if (usedRows.contains(end)) { - break; - } - } - - // Add up row sizes - int height = getRowHeight(start, end - start); - - // Find all spacers: the first one found should be moved to the start row - // and assigned to the full height of the rows, and - // the row count reduced by the corresponding amount - - // TODO: if width = 0, fully remove - - boolean isFirstSpacer = true; - for (int row = start; row < end; row++) { - Collection<ViewData> spacers = rowSpacers.get(row); - if (spacers != null && !spacers.isEmpty()) { - // Avoid ConcurrentModificationException since we're inserting into the - // map within this loop (always at a different index, but the map doesn't - // know that) - spacers = new ArrayList<ViewData>(spacers); - for (ViewData spacer : spacers) { - if (isFirstSpacer) { - isFirstSpacer = false; - spacer.row = rowMap[start]; - setGridAttribute(spacer.node, ATTR_LAYOUT_ROW, spacer.row); - if (end - start > 1) { - // Compute a merged width for all the spacers (not needed if - // there's just one spacer; it should already have the correct height) - int rowHeightDp = mRulesEngine.pxToDp(height); - spacer.node.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, - String.format(VALUE_N_DP, rowHeightDp)); - } - rowSpacers.put(start, spacer); - } else { - removedViews.add(spacer); // Mark for model removal - layout.removeChild(spacer.node); - } - } - } - } - - if (isFirstSpacer) { - // No spacer: create one - int rowWidthDp = mRulesEngine.pxToDp(height); - addSpacer(layout, -1, rowMap[start], UNDEFINED, DEFAULT_CELL_WIDTH, rowWidthDp); - } - - start = end; - } - actualRowCount = newRow; -// if (usedRows.contains(newRow)) { -// actualRowCount++; -// } - - // Update the model: remove removed children from the view data list - if (removedViews.size() <= 2) { - mChildViews.removeAll(removedViews); - } else { - List<ViewData> remaining = - new ArrayList<ViewData>(mChildViews.size() - removedViews.size()); - for (ViewData view : mChildViews) { - if (!removedViews.contains(view)) { - remaining.add(view); - } - } - mChildViews = remaining; - } - - // Update the final column and row declared attributes - if (declaredColumnCount != UNDEFINED) { - declaredColumnCount = actualColumnCount; - setGridAttribute(layout, ATTR_COLUMN_COUNT, actualColumnCount); - } - if (declaredRowCount != UNDEFINED) { - declaredRowCount = actualRowCount; - setGridAttribute(layout, ATTR_ROW_COUNT, actualRowCount); - } - } - - /** - * Adds a spacer to the given parent, at the given index. - * - * @param parent the GridLayout - * @param index the index to insert the spacer at, or -1 to append - * @param row the row to add the spacer to (or {@link #UNDEFINED} to not set a row yet - * @param column the column to add the spacer to (or {@link #UNDEFINED} to not set a - * column yet - * @param widthDp the width in device independent pixels to assign to the spacer - * @param heightDp the height in device independent pixels to assign to the spacer - * @return the newly added spacer - */ - ViewData addSpacer(INode parent, int index, int row, int column, - int widthDp, int heightDp) { - INode spacer; - - String tag = FQCN_SPACE; - String gridLayout = parent.getFqcn(); - if (!gridLayout.equals(GRID_LAYOUT) && gridLayout.length() > GRID_LAYOUT.length()) { - String pkg = gridLayout.substring(0, gridLayout.length() - GRID_LAYOUT.length()); - tag = pkg + SPACE; - } - if (index != -1) { - spacer = parent.insertChildAt(tag, index); - } else { - spacer = parent.appendChild(tag); - } - - ViewData view = new ViewData(spacer, index != -1 ? index : mChildViews.size()); - mChildViews.add(view); - - if (row != UNDEFINED) { - view.row = row; - setGridAttribute(spacer, ATTR_LAYOUT_ROW, row); - } - if (column != UNDEFINED) { - view.column = column; - setGridAttribute(spacer, ATTR_LAYOUT_COLUMN, column); - } - if (widthDp > 0) { - spacer.setAttribute(ANDROID_URI, ATTR_LAYOUT_WIDTH, - String.format(VALUE_N_DP, widthDp)); - } - if (heightDp > 0) { - spacer.setAttribute(ANDROID_URI, ATTR_LAYOUT_HEIGHT, - String.format(VALUE_N_DP, heightDp)); - } - - // Temporary hack - if (GridLayoutRule.sDebugGridLayout) { - //String id = NEW_ID_PREFIX + "s"; - //if (row == 0) { - // id += "c"; - //} - //if (column == 0) { - // id += "r"; - //} - //if (row > 0) { - // id += Integer.toString(row); - //} - //if (column > 0) { - // id += Integer.toString(column); - //} - String id = NEW_ID_PREFIX + "spacer_" //$NON-NLS-1$ - + Integer.toString(System.identityHashCode(spacer)).substring(0, 3); - spacer.setAttribute(ANDROID_URI, ATTR_ID, id); - } - - - return view; - } - - /** - * Returns the string value of the given attribute, or null if it does not - * exist. This only works for attributes that are GridLayout specific, such - * as columnCount, layout_column, layout_row_span, etc. - * - * @param node the target node - * @param name the attribute name (which must be in the android: namespace) - * @return the attribute value or null - */ - - public String getGridAttribute(INode node, String name) { - return node.getStringAttr(getNamespace(), name); - } - - /** - * Returns the integer value of the given attribute, or the given defaultValue if the - * attribute was not set. This only works for attributes that are GridLayout specific, - * such as columnCount, layout_column, layout_row_span, etc. - * - * @param node the target node - * @param attribute the attribute name (which must be in the android: namespace) - * @param defaultValue the default value to use if the value is not set - * @return the attribute integer value - */ - private int getGridAttribute(INode node, String attribute, int defaultValue) { - String valueString = node.getStringAttr(getNamespace(), attribute); - if (valueString != null) { - try { - return Integer.decode(valueString); - } catch (NumberFormatException nufe) { - // Ignore - error in user's XML - } - } - - return defaultValue; - } - - /** - * Returns the number of children views in the GridLayout - * - * @return the number of children views in the GridLayout - */ - public int getViewCount() { - return mChildViews.size(); - } - - /** - * Returns true if the given class name represents a spacer - * - * @param fqcn the fully qualified class name - * @return true if this is a spacer - */ - public static boolean isSpace(String fqcn) { - return FQCN_SPACE.equals(fqcn) || FQCN_SPACE_V7.equals(fqcn); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/gridmode.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/gridmode.png Binary files differdeleted file mode 100644 index 59e0a4511..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/gridmode.png +++ /dev/null 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 differdeleted file mode 100644 index b293fe7c5..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/hlinear.png +++ /dev/null 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 differdeleted file mode 100644 index b0d814116..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/margins.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintPainter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintPainter.java deleted file mode 100644 index 447d2d880..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintPainter.java +++ /dev/null @@ -1,783 +0,0 @@ -/* - * 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.relative; - -import static com.android.ide.common.api.DrawingStyle.DEPENDENCY; -import static com.android.ide.common.api.DrawingStyle.GUIDELINE; -import static com.android.ide.common.api.DrawingStyle.GUIDELINE_DASHED; -import static com.android.ide.common.api.SegmentType.BASELINE; -import static com.android.ide.common.api.SegmentType.BOTTOM; -import static com.android.ide.common.api.SegmentType.CENTER_HORIZONTAL; -import static com.android.ide.common.api.SegmentType.CENTER_VERTICAL; -import static com.android.ide.common.api.SegmentType.LEFT; -import static com.android.ide.common.api.SegmentType.RIGHT; -import static com.android.ide.common.api.SegmentType.TOP; -import static com.android.ide.common.api.SegmentType.UNKNOWN; -import static com.android.ide.common.layout.relative.ConstraintType.ALIGN_BASELINE; -import static com.android.ide.common.layout.relative.ConstraintType.ALIGN_BOTTOM; -import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_ABOVE; -import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_BELOW; -import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_LEFT_OF; -import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_RIGHT_OF; - -import com.android.ide.common.api.DrawingStyle; -import com.android.ide.common.api.IGraphics; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Margins; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.SegmentType; -import com.android.ide.common.layout.relative.DependencyGraph.Constraint; -import com.android.ide.common.layout.relative.DependencyGraph.ViewData; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * The {@link ConstraintPainter} is responsible for painting relative layout constraints - - * such as a source node having its top edge constrained to a target node with a given margin. - * This painter is used both to show static constraints, as well as visualizing proposed - * constraints during a move or resize operation. - */ -public class ConstraintPainter { - /** The size of the arrow head */ - private static final int ARROW_SIZE = 5; - /** Size (height for horizontal, and width for vertical) parent feedback rectangles */ - private static final int PARENT_RECT_SIZE = 12; - - /** - * Paints a given match as a constraint. - * - * @param graphics the graphics context - * @param sourceBounds the source bounds - * @param match the match - */ - static void paintConstraint(IGraphics graphics, Rect sourceBounds, Match match) { - Rect targetBounds = match.edge.node.getBounds(); - ConstraintType type = match.type; - assert type != null; - paintConstraint(graphics, type, match.with.node, sourceBounds, match.edge.node, - targetBounds, null /* allConstraints */, true /* highlightTargetEdge */); - } - - /** - * Paints a constraint. - * <p> - * TODO: when there are multiple links originating in the same direction from - * center, maybe offset them slightly from each other? - * - * @param graphics the graphics context to draw into - * @param constraint The constraint to be drawn - */ - private static void paintConstraint(IGraphics graphics, Constraint constraint, - Set<Constraint> allConstraints) { - ViewData source = constraint.from; - ViewData target = constraint.to; - - INode sourceNode = source.node; - INode targetNode = target.node; - if (sourceNode == targetNode) { - // Self reference - don't visualize - return; - } - - Rect sourceBounds = sourceNode.getBounds(); - Rect targetBounds = targetNode.getBounds(); - paintConstraint(graphics, constraint.type, sourceNode, sourceBounds, targetNode, - targetBounds, allConstraints, false /* highlightTargetEdge */); - } - - /** - * Paint selection feedback by painting constraints for the selected nodes - * - * @param graphics the graphics context - * @param parentNode the parent relative layout - * @param childNodes the nodes whose constraints should be painted - * @param showDependents whether incoming constraints should be shown as well - */ - public static void paintSelectionFeedback(IGraphics graphics, INode parentNode, - List<? extends INode> childNodes, boolean showDependents) { - - DependencyGraph dependencyGraph = new DependencyGraph(parentNode); - Set<INode> horizontalDeps = dependencyGraph.dependsOn(childNodes, false /* vertical */); - Set<INode> verticalDeps = dependencyGraph.dependsOn(childNodes, true /* vertical */); - Set<INode> deps = new HashSet<INode>(horizontalDeps.size() + verticalDeps.size()); - deps.addAll(horizontalDeps); - deps.addAll(verticalDeps); - if (deps.size() > 0) { - graphics.useStyle(DEPENDENCY); - for (INode node : deps) { - // Don't highlight the selected nodes themselves - if (childNodes.contains(node)) { - continue; - } - Rect bounds = node.getBounds(); - graphics.fillRect(bounds); - } - } - - graphics.useStyle(GUIDELINE); - for (INode childNode : childNodes) { - ViewData view = dependencyGraph.getView(childNode); - if (view == null) { - continue; - } - - // Paint all incoming constraints - if (showDependents) { - paintConstraints(graphics, view.dependedOnBy); - } - - // Paint all outgoing constraints - paintConstraints(graphics, view.dependsOn); - } - } - - /** - * Paints a set of constraints. - */ - private static void paintConstraints(IGraphics graphics, List<Constraint> constraints) { - Set<Constraint> mutableConstraintSet = new HashSet<Constraint>(constraints); - - // WORKAROUND! Hide alignBottom attachments if we also have a alignBaseline - // constraint; this is because we also *add* alignBottom attachments when you add - // alignBaseline constraints to work around a surprising behavior of baseline - // constraints. - for (Constraint constraint : constraints) { - if (constraint.type == ALIGN_BASELINE) { - // Remove any baseline - for (Constraint c : constraints) { - if (c.type == ALIGN_BOTTOM && c.to.node == constraint.to.node) { - mutableConstraintSet.remove(c); - } - } - } - } - - for (Constraint constraint : constraints) { - // paintConstraint can digest more than one constraint, so we need to keep - // checking to see if the given constraint is still relevant. - if (mutableConstraintSet.contains(constraint)) { - paintConstraint(graphics, constraint, mutableConstraintSet); - } - } - } - - /** - * Paints a constraint of the given type from the given source node, to the - * given target node, with the specified bounds. - */ - private static void paintConstraint(IGraphics graphics, ConstraintType type, INode sourceNode, - Rect sourceBounds, INode targetNode, Rect targetBounds, - Set<Constraint> allConstraints, boolean highlightTargetEdge) { - - SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX; - SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY; - SegmentType targetSegmentTypeX = type.targetSegmentTypeX; - SegmentType targetSegmentTypeY = type.targetSegmentTypeY; - - // Horizontal center constraint? - if (sourceSegmentTypeX == CENTER_VERTICAL && targetSegmentTypeX == CENTER_VERTICAL) { - paintHorizontalCenterConstraint(graphics, sourceBounds, targetBounds); - return; - } - - // Vertical center constraint? - if (sourceSegmentTypeY == CENTER_HORIZONTAL && targetSegmentTypeY == CENTER_HORIZONTAL) { - paintVerticalCenterConstraint(graphics, sourceBounds, targetBounds); - return; - } - - // Corner constraint? - if (allConstraints != null - && (type == LAYOUT_ABOVE || type == LAYOUT_BELOW - || type == LAYOUT_LEFT_OF || type == LAYOUT_RIGHT_OF)) { - if (paintCornerConstraint(graphics, type, sourceNode, sourceBounds, targetNode, - targetBounds, allConstraints)) { - return; - } - } - - // Vertical constraint? - if (sourceSegmentTypeX == UNKNOWN) { - paintVerticalConstraint(graphics, type, sourceNode, sourceBounds, targetNode, - targetBounds, highlightTargetEdge); - return; - } - - // Horizontal constraint? - if (sourceSegmentTypeY == UNKNOWN) { - paintHorizontalConstraint(graphics, type, sourceNode, sourceBounds, targetNode, - targetBounds, highlightTargetEdge); - return; - } - - // This shouldn't happen - it means we have a constraint that defines all sides - // and is not a centering constraint - assert false; - } - - /** - * Paints a corner constraint, or returns false if this constraint is not a corner. - * A corner is one where there are two constraints from this source node to the - * same target node, one horizontal and one vertical, to the closest edges on - * the target node. - * <p> - * Corners are a common occurrence. If we treat the horizontal and vertical - * constraints separately (below & toRightOf), then we end up with a lot of - * extra lines and arrows -- e.g. two shared edges and arrows pointing to these - * shared edges: - * - * <pre> - * +--------+ | - * | Target --> - * +----|---+ | - * v - * - - - - - -|- - - - - - - * ^ - * | +---|----+ - * <-- Source | - * | +--------+ - * - * Instead, we can simply draw a diagonal arrow here to represent BOTH constraints and - * reduce clutter: - * - * +---------+ - * | Target _| - * +-------|\+ - * \ - * \--------+ - * | Source | - * +--------+ - * </pre> - * - * @param graphics the graphics context to draw - * @param type the constraint to be drawn - * @param sourceNode the source node - * @param sourceBounds the bounds of the source node - * @param targetNode the target node - * @param targetBounds the bounds of the target node - * @param allConstraints the set of all constraints; if a corner is found and painted the - * matching corner constraint is removed from the set - * @return true if the constraint was handled and painted as a corner, false otherwise - */ - private static boolean paintCornerConstraint(IGraphics graphics, ConstraintType type, - INode sourceNode, Rect sourceBounds, INode targetNode, Rect targetBounds, - Set<Constraint> allConstraints) { - - SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX; - SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY; - SegmentType targetSegmentTypeX = type.targetSegmentTypeX; - SegmentType targetSegmentTypeY = type.targetSegmentTypeY; - - ConstraintType opposite1 = null, opposite2 = null; - switch (type) { - case LAYOUT_BELOW: - case LAYOUT_ABOVE: - opposite1 = LAYOUT_LEFT_OF; - opposite2 = LAYOUT_RIGHT_OF; - break; - case LAYOUT_LEFT_OF: - case LAYOUT_RIGHT_OF: - opposite1 = LAYOUT_ABOVE; - opposite2 = LAYOUT_BELOW; - break; - default: - return false; - } - Constraint pair = null; - for (Constraint constraint : allConstraints) { - if ((constraint.type == opposite1 || constraint.type == opposite2) && - constraint.to.node == targetNode && constraint.from.node == sourceNode) { - pair = constraint; - break; - } - } - - // TODO -- ensure that the nodes are adjacent! In other words, that - // their bounds are within N pixels. - - if (pair != null) { - // Visualize the corner constraint - if (sourceSegmentTypeX == UNKNOWN) { - sourceSegmentTypeX = pair.type.sourceSegmentTypeX; - } - if (sourceSegmentTypeY == UNKNOWN) { - sourceSegmentTypeY = pair.type.sourceSegmentTypeY; - } - if (targetSegmentTypeX == UNKNOWN) { - targetSegmentTypeX = pair.type.targetSegmentTypeX; - } - if (targetSegmentTypeY == UNKNOWN) { - targetSegmentTypeY = pair.type.targetSegmentTypeY; - } - - int x1, y1, x2, y2; - if (sourceSegmentTypeX == LEFT) { - x1 = sourceBounds.x + 1 * sourceBounds.w / 4; - } else { - x1 = sourceBounds.x + 3 * sourceBounds.w / 4; - } - if (sourceSegmentTypeY == TOP) { - y1 = sourceBounds.y + 1 * sourceBounds.h / 4; - } else { - y1 = sourceBounds.y + 3 * sourceBounds.h / 4; - } - if (targetSegmentTypeX == LEFT) { - x2 = targetBounds.x + 1 * targetBounds.w / 4; - } else { - x2 = targetBounds.x + 3 * targetBounds.w / 4; - } - if (targetSegmentTypeY == TOP) { - y2 = targetBounds.y + 1 * targetBounds.h / 4; - } else { - y2 = targetBounds.y + 3 * targetBounds.h / 4; - } - - graphics.useStyle(GUIDELINE); - graphics.drawArrow(x1, y1, x2, y2, ARROW_SIZE); - - // Don't process this constraint on its own later. - allConstraints.remove(pair); - - return true; - } - - return false; - } - - /** - * Paints a vertical constraint, handling the various scenarios where there are - * margins, or where the two nodes overlap horizontally and where they don't, etc. - * <p> - * Here's an example of what will be shown for a "below" constraint where the - * nodes do not overlap horizontally and the target node has a bottom margin: - * <pre> - * +--------+ - * | Target | - * +--------+ - * | - * v - * - - - - - - - - - - - - - - - * ^ - * | - * +--------+ - * | Source | - * +--------+ - * </pre> - */ - private static void paintVerticalConstraint(IGraphics graphics, ConstraintType type, - INode sourceNode, Rect sourceBounds, INode targetNode, Rect targetBounds, - boolean highlightTargetEdge) { - SegmentType sourceSegmentTypeY = type.sourceSegmentTypeY; - SegmentType targetSegmentTypeY = type.targetSegmentTypeY; - Margins targetMargins = targetNode.getMargins(); - - assert sourceSegmentTypeY != UNKNOWN; - assert targetBounds != null; - - int sourceY = sourceSegmentTypeY.getY(sourceNode, sourceBounds); - int targetY = targetSegmentTypeY == - UNKNOWN ? sourceY : targetSegmentTypeY.getY(targetNode, targetBounds); - - if (highlightTargetEdge && type.isRelativeToParentEdge()) { - graphics.useStyle(DrawingStyle.DROP_ZONE_ACTIVE); - graphics.fillRect(targetBounds.x, targetY - PARENT_RECT_SIZE / 2, - targetBounds.x2(), targetY + PARENT_RECT_SIZE / 2); - } - - // First see if the two views overlap horizontally. If so, we can just draw a direct - // arrow from the source up to (or down to) the target. - // - // +--------+ - // | Target | - // +--------+ - // ^ - // | - // | - // +--------+ - // | Source | - // +--------+ - // - int maxLeft = Math.max(sourceBounds.x, targetBounds.x); - int minRight = Math.min(sourceBounds.x2(), targetBounds.x2()); - - int center = (maxLeft + minRight) / 2; - if (center > sourceBounds.x && center < sourceBounds.x2()) { - // Yes, the lines overlap -- just draw a straight arrow - // - // - // If however there is a margin on the target edge, it should be drawn like this: - // - // +--------+ - // | Target | - // +--------+ - // | - // | - // v - // - - - - - - - - // ^ - // | - // | - // +--------+ - // | Source | - // +--------+ - // - // Use a minimum threshold for this visualization since it doesn't look good - // for small margins - if (targetSegmentTypeY == BOTTOM && targetMargins.bottom > 5) { - int sharedY = targetY + targetMargins.bottom; - if (sourceY > sharedY + 2) { // Skip when source falls on the margin line - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(targetBounds.x, sharedY, targetBounds.x2(), sharedY); - graphics.useStyle(GUIDELINE); - graphics.drawArrow(center, sourceY, center, sharedY + 2, ARROW_SIZE); - graphics.drawArrow(center, targetY, center, sharedY - 3, ARROW_SIZE); - } else { - graphics.useStyle(GUIDELINE); - // Draw reverse arrow to make it clear the node is as close - // at it can be - graphics.drawArrow(center, targetY, center, sourceY, ARROW_SIZE); - } - return; - } else if (targetSegmentTypeY == TOP && targetMargins.top > 5) { - int sharedY = targetY - targetMargins.top; - if (sourceY < sharedY - 2) { - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(targetBounds.x, sharedY, targetBounds.x2(), sharedY); - graphics.useStyle(GUIDELINE); - graphics.drawArrow(center, sourceY, center, sharedY - 3, ARROW_SIZE); - graphics.drawArrow(center, targetY, center, sharedY + 3, ARROW_SIZE); - } else { - graphics.useStyle(GUIDELINE); - graphics.drawArrow(center, targetY, center, sourceY, ARROW_SIZE); - } - return; - } - - // TODO: If the center falls smack in the center of the sourceBounds, - // AND the source node is part of the selection, then adjust the - // center location such that it is off to the side, let's say 1/4 or 3/4 of - // the overlap region, to ensure that it does not overlap the center selection - // handle - - // When the constraint is for two immediately adjacent edges, we - // need to make some adjustments to make sure the arrow points in the right - // direction - if (sourceY == targetY) { - if (sourceSegmentTypeY == BOTTOM || sourceSegmentTypeY == BASELINE) { - sourceY -= 2 * ARROW_SIZE; - } else if (sourceSegmentTypeY == TOP) { - sourceY += 2 * ARROW_SIZE; - } else { - assert sourceSegmentTypeY == CENTER_HORIZONTAL : sourceSegmentTypeY; - sourceY += sourceBounds.h / 2 - 2 * ARROW_SIZE; - } - } else if (sourceSegmentTypeY == BASELINE) { - sourceY = targetY - 2 * ARROW_SIZE; - } - - // Center the vertical line in the overlap region - graphics.useStyle(GUIDELINE); - graphics.drawArrow(center, sourceY, center, targetY, ARROW_SIZE); - - return; - } - - // If there is no horizontal overlap in the vertical constraints, then we - // will show the attachment relative to a dashed line that extends beyond - // the target bounds, like this: - // - // +--------+ - // | Target | - // +--------+ - - - - - - - - - - // ^ - // | - // +--------+ - // | Source | - // +--------+ - // - // However, if the target node has a vertical margin, we may need to offset - // the line: - // - // +--------+ - // | Target | - // +--------+ - // | - // v - // - - - - - - - - - - - - - - - // ^ - // | - // +--------+ - // | Source | - // +--------+ - // - // If not, we'll need to indicate a shared edge. This is the edge that separate - // them (but this will require me to evaluate margins!) - - // Compute overlap region and pick the middle - int sharedY = targetSegmentTypeY == - UNKNOWN ? sourceY : targetSegmentTypeY.getY(targetNode, targetBounds); - if (type.relativeToMargin) { - if (targetSegmentTypeY == TOP) { - sharedY -= targetMargins.top; - } else if (targetSegmentTypeY == BOTTOM) { - sharedY += targetMargins.bottom; - } - } - - int startX; - int endX; - if (center <= sourceBounds.x) { - startX = targetBounds.x + targetBounds.w / 4; - endX = sourceBounds.x2(); - } else { - assert (center >= sourceBounds.x2()); - startX = sourceBounds.x; - endX = targetBounds.x + 3 * targetBounds.w / 4; - } - // Must draw segmented line instead - // Place the arrow 1/4 instead of 1/2 in the source to avoid overlapping with the - // selection handles - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(startX, sharedY, endX, sharedY); - - // Adjust position of source arrow such that it does not sit across edge; it - // should point directly at the edge - if (Math.abs(sharedY - sourceY) < 2 * ARROW_SIZE) { - if (sourceSegmentTypeY == BASELINE) { - sourceY = sharedY - 2 * ARROW_SIZE; - } else if (sourceSegmentTypeY == TOP) { - sharedY = sourceY; - sourceY = sharedY + 2 * ARROW_SIZE; - } else { - sharedY = sourceY; - sourceY = sharedY - 2 * ARROW_SIZE; - } - } - - graphics.useStyle(GUIDELINE); - - // Draw the line from the source anchor to the shared edge - int x = sourceBounds.x + ((sourceSegmentTypeY == BASELINE) ? - sourceBounds.w / 2 : sourceBounds.w / 4); - graphics.drawArrow(x, sourceY, x, sharedY, ARROW_SIZE); - - // Draw the line from the target to the horizontal shared edge - int tx = targetBounds.centerX(); - if (targetSegmentTypeY == TOP) { - int ty = targetBounds.y; - int margin = targetMargins.top; - if (margin == 0 || !type.relativeToMargin) { - graphics.drawArrow(tx, ty + 2 * ARROW_SIZE, tx, ty, ARROW_SIZE); - } else { - graphics.drawArrow(tx, ty, tx, ty - margin, ARROW_SIZE); - } - } else if (targetSegmentTypeY == BOTTOM) { - int ty = targetBounds.y2(); - int margin = targetMargins.bottom; - if (margin == 0 || !type.relativeToMargin) { - graphics.drawArrow(tx, ty - 2 * ARROW_SIZE, tx, ty, ARROW_SIZE); - } else { - graphics.drawArrow(tx, ty, tx, ty + margin, ARROW_SIZE); - } - } else { - assert targetSegmentTypeY == BASELINE : targetSegmentTypeY; - int ty = targetSegmentTypeY.getY(targetNode, targetBounds); - graphics.drawArrow(tx, ty - 2 * ARROW_SIZE, tx, ty, ARROW_SIZE); - } - - return; - } - - /** - * Paints a horizontal constraint, handling the various scenarios where there are margins, - * or where the two nodes overlap horizontally and where they don't, etc. - */ - private static void paintHorizontalConstraint(IGraphics graphics, ConstraintType type, - INode sourceNode, Rect sourceBounds, INode targetNode, Rect targetBounds, - boolean highlightTargetEdge) { - SegmentType sourceSegmentTypeX = type.sourceSegmentTypeX; - SegmentType targetSegmentTypeX = type.targetSegmentTypeX; - Margins targetMargins = targetNode.getMargins(); - - assert sourceSegmentTypeX != UNKNOWN; - assert targetBounds != null; - - // See paintVerticalConstraint for explanations of the various cases. - - int sourceX = sourceSegmentTypeX.getX(sourceNode, sourceBounds); - int targetX = targetSegmentTypeX == UNKNOWN ? - sourceX : targetSegmentTypeX.getX(targetNode, targetBounds); - - if (highlightTargetEdge && type.isRelativeToParentEdge()) { - graphics.useStyle(DrawingStyle.DROP_ZONE_ACTIVE); - graphics.fillRect(targetX - PARENT_RECT_SIZE / 2, targetBounds.y, - targetX + PARENT_RECT_SIZE / 2, targetBounds.y2()); - } - - int maxTop = Math.max(sourceBounds.y, targetBounds.y); - int minBottom = Math.min(sourceBounds.y2(), targetBounds.y2()); - - // First see if the two views overlap vertically. If so, we can just draw a direct - // arrow from the source over to the target. - int center = (maxTop + minBottom) / 2; - if (center > sourceBounds.y && center < sourceBounds.y2()) { - // See if we should draw a margin line - if (targetSegmentTypeX == RIGHT && targetMargins.right > 5) { - int sharedX = targetX + targetMargins.right; - if (sourceX > sharedX + 2) { // Skip when source falls on the margin line - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(sharedX, targetBounds.y, sharedX, targetBounds.y2()); - graphics.useStyle(GUIDELINE); - graphics.drawArrow(sourceX, center, sharedX + 2, center, ARROW_SIZE); - graphics.drawArrow(targetX, center, sharedX - 3, center, ARROW_SIZE); - } else { - graphics.useStyle(GUIDELINE); - // Draw reverse arrow to make it clear the node is as close - // at it can be - graphics.drawArrow(targetX, center, sourceX, center, ARROW_SIZE); - } - return; - } else if (targetSegmentTypeX == LEFT && targetMargins.left > 5) { - int sharedX = targetX - targetMargins.left; - if (sourceX < sharedX - 2) { - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(sharedX, targetBounds.y, sharedX, targetBounds.y2()); - graphics.useStyle(GUIDELINE); - graphics.drawArrow(sourceX, center, sharedX - 3, center, ARROW_SIZE); - graphics.drawArrow(targetX, center, sharedX + 3, center, ARROW_SIZE); - } else { - graphics.useStyle(GUIDELINE); - graphics.drawArrow(targetX, center, sourceX, center, ARROW_SIZE); - } - return; - } - - if (sourceX == targetX) { - if (sourceSegmentTypeX == RIGHT) { - sourceX -= 2 * ARROW_SIZE; - } else if (sourceSegmentTypeX == LEFT ) { - sourceX += 2 * ARROW_SIZE; - } else { - assert sourceSegmentTypeX == CENTER_VERTICAL : sourceSegmentTypeX; - sourceX += sourceBounds.w / 2 - 2 * ARROW_SIZE; - } - } - - graphics.useStyle(GUIDELINE); - graphics.drawArrow(sourceX, center, targetX, center, ARROW_SIZE); - return; - } - - // Segment line - - // Compute overlap region and pick the middle - int sharedX = targetSegmentTypeX == UNKNOWN ? - sourceX : targetSegmentTypeX.getX(targetNode, targetBounds); - if (type.relativeToMargin) { - if (targetSegmentTypeX == LEFT) { - sharedX -= targetMargins.left; - } else if (targetSegmentTypeX == RIGHT) { - sharedX += targetMargins.right; - } - } - - int startY, endY; - if (center <= sourceBounds.y) { - startY = targetBounds.y + targetBounds.h / 4; - endY = sourceBounds.y2(); - } else { - assert (center >= sourceBounds.y2()); - startY = sourceBounds.y; - endY = targetBounds.y + 3 * targetBounds.h / 2; - } - - // Must draw segmented line instead - // Place at 1/4 instead of 1/2 to avoid overlapping with selection handles - int y = sourceBounds.y + sourceBounds.h / 4; - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(sharedX, startY, sharedX, endY); - - // Adjust position of source arrow such that it does not sit across edge; it - // should point directly at the edge - if (Math.abs(sharedX - sourceX) < 2 * ARROW_SIZE) { - if (sourceSegmentTypeX == LEFT) { - sharedX = sourceX; - sourceX = sharedX + 2 * ARROW_SIZE; - } else { - sharedX = sourceX; - sourceX = sharedX - 2 * ARROW_SIZE; - } - } - - graphics.useStyle(GUIDELINE); - - // Draw the line from the source anchor to the shared edge - graphics.drawArrow(sourceX, y, sharedX, y, ARROW_SIZE); - - // Draw the line from the target to the horizontal shared edge - int ty = targetBounds.centerY(); - if (targetSegmentTypeX == LEFT) { - int tx = targetBounds.x; - int margin = targetMargins.left; - if (margin == 0 || !type.relativeToMargin) { - graphics.drawArrow(tx + 2 * ARROW_SIZE, ty, tx, ty, ARROW_SIZE); - } else { - graphics.drawArrow(tx, ty, tx - margin, ty, ARROW_SIZE); - } - } else { - assert targetSegmentTypeX == RIGHT; - int tx = targetBounds.x2(); - int margin = targetMargins.right; - if (margin == 0 || !type.relativeToMargin) { - graphics.drawArrow(tx - 2 * ARROW_SIZE, ty, tx, ty, ARROW_SIZE); - } else { - graphics.drawArrow(tx, ty, tx + margin, ty, ARROW_SIZE); - } - } - - return; - } - - /** - * Paints a vertical center constraint. The constraint is shown as a dashed line - * through the vertical view, and a solid line over the node bounds. - */ - private static void paintVerticalCenterConstraint(IGraphics graphics, Rect sourceBounds, - Rect targetBounds) { - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(targetBounds.x, targetBounds.centerY(), - targetBounds.x2(), targetBounds.centerY()); - graphics.useStyle(GUIDELINE); - graphics.drawLine(sourceBounds.x, sourceBounds.centerY(), - sourceBounds.x2(), sourceBounds.centerY()); - } - - /** - * Paints a horizontal center constraint. The constraint is shown as a dashed line - * through the horizontal view, and a solid line over the node bounds. - */ - private static void paintHorizontalCenterConstraint(IGraphics graphics, Rect sourceBounds, - Rect targetBounds) { - graphics.useStyle(GUIDELINE_DASHED); - graphics.drawLine(targetBounds.centerX(), targetBounds.y, - targetBounds.centerX(), targetBounds.y2()); - graphics.useStyle(GUIDELINE); - graphics.drawLine(sourceBounds.centerX(), sourceBounds.y, - sourceBounds.centerX(), sourceBounds.y2()); - } -}
\ No newline at end of file diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintType.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintType.java deleted file mode 100644 index ed4ac1bf4..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ConstraintType.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * 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.relative; - -import static com.android.ide.common.api.SegmentType.BASELINE; -import static com.android.ide.common.api.SegmentType.BOTTOM; -import static com.android.ide.common.api.SegmentType.CENTER_HORIZONTAL; -import static com.android.ide.common.api.SegmentType.CENTER_VERTICAL; -import static com.android.ide.common.api.SegmentType.LEFT; -import static com.android.ide.common.api.SegmentType.RIGHT; -import static com.android.ide.common.api.SegmentType.TOP; -import static com.android.ide.common.api.SegmentType.UNKNOWN; -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_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_TO_LEFT_OF; -import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.SegmentType; - -import java.util.HashMap; -import java.util.Map; - -/** - * Each constraint type corresponds to a type of constraint available for the - * RelativeLayout; for example, {@link #LAYOUT_ABOVE} corresponds to the layout_above constraint. - */ -enum ConstraintType { - LAYOUT_ABOVE(ATTR_LAYOUT_ABOVE, - null /* sourceX */, BOTTOM, null /* targetX */, TOP, - false /* targetParent */, true /* horizontalEdge */, false /* verticalEdge */, - true /* relativeToMargin */), - - LAYOUT_BELOW(ATTR_LAYOUT_BELOW, null, TOP, null, BOTTOM, false, true, false, true), - ALIGN_TOP(ATTR_LAYOUT_ALIGN_TOP, null, TOP, null, TOP, false, true, false, false), - ALIGN_BOTTOM(ATTR_LAYOUT_ALIGN_BOTTOM, null, BOTTOM, null, BOTTOM, false, true, false, false), - ALIGN_LEFT(ATTR_LAYOUT_ALIGN_LEFT, LEFT, null, LEFT, null, false, false, true, false), - ALIGN_RIGHT(ATTR_LAYOUT_ALIGN_RIGHT, RIGHT, null, RIGHT, null, false, false, true, false), - LAYOUT_LEFT_OF(ATTR_LAYOUT_TO_LEFT_OF, RIGHT, null, LEFT, null, false, false, true, true), - LAYOUT_RIGHT_OF(ATTR_LAYOUT_TO_RIGHT_OF, LEFT, null, RIGHT, null, false, false, true, true), - ALIGN_PARENT_TOP(ATTR_LAYOUT_ALIGN_PARENT_TOP, null, TOP, null, TOP, true, true, false, false), - ALIGN_BASELINE(ATTR_LAYOUT_ALIGN_BASELINE, null, BASELINE, null, BASELINE, false, true, false, - false), - ALIGN_PARENT_LEFT(ATTR_LAYOUT_ALIGN_PARENT_LEFT, LEFT, null, LEFT, null, true, false, true, - false), - ALIGN_PARENT_RIGHT(ATTR_LAYOUT_ALIGN_PARENT_RIGHT, RIGHT, null, RIGHT, null, true, false, true, - false), - ALIGN_PARENT_BOTTOM(ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, null, BOTTOM, null, BOTTOM, true, true, - false, false), - LAYOUT_CENTER_HORIZONTAL(ATTR_LAYOUT_CENTER_HORIZONTAL, CENTER_VERTICAL, null, CENTER_VERTICAL, - null, true, true, false, false), - LAYOUT_CENTER_VERTICAL(ATTR_LAYOUT_CENTER_VERTICAL, null, CENTER_HORIZONTAL, null, - CENTER_HORIZONTAL, true, false, true, false), - LAYOUT_CENTER_IN_PARENT(ATTR_LAYOUT_CENTER_IN_PARENT, CENTER_VERTICAL, CENTER_HORIZONTAL, - CENTER_VERTICAL, CENTER_HORIZONTAL, true, true, true, false); - - private ConstraintType(String name, SegmentType sourceSegmentTypeX, - SegmentType sourceSegmentTypeY, SegmentType targetSegmentTypeX, - SegmentType targetSegmentTypeY, boolean targetParent, boolean horizontalEdge, - boolean verticalEdge, boolean relativeToMargin) { - assert horizontalEdge || verticalEdge; - - this.name = name; - this.sourceSegmentTypeX = sourceSegmentTypeX != null ? sourceSegmentTypeX : UNKNOWN; - this.sourceSegmentTypeY = sourceSegmentTypeY != null ? sourceSegmentTypeY : UNKNOWN; - this.targetSegmentTypeX = targetSegmentTypeX != null ? targetSegmentTypeX : UNKNOWN; - this.targetSegmentTypeY = targetSegmentTypeY != null ? targetSegmentTypeY : UNKNOWN; - this.targetParent = targetParent; - this.horizontalEdge = horizontalEdge; - this.verticalEdge = verticalEdge; - this.relativeToMargin = relativeToMargin; - } - - /** The attribute name of the constraint */ - public final String name; - - /** The horizontal position of the source of the constraint */ - public final SegmentType sourceSegmentTypeX; - - /** The vertical position of the source of the constraint */ - public final SegmentType sourceSegmentTypeY; - - /** The horizontal position of the target of the constraint */ - public final SegmentType targetSegmentTypeX; - - /** The vertical position of the target of the constraint */ - public final SegmentType targetSegmentTypeY; - - /** - * If true, the constraint targets the parent layout, otherwise it targets another - * view - */ - public final boolean targetParent; - - /** If true, this constraint affects the horizontal dimension */ - public final boolean horizontalEdge; - - /** If true, this constraint affects the vertical dimension */ - public final boolean verticalEdge; - - /** - * Whether this constraint is relative to the margin bounds of the node rather than - * the node's actual bounds - */ - public final boolean relativeToMargin; - - /** Map from attribute name to constraint type */ - private static Map<String, ConstraintType> sNameToType; - - /** - * Returns the {@link ConstraintType} corresponding to the given attribute name, or - * null if not found. - * - * @param attribute the name of the attribute to look up - * @return the corresponding {@link ConstraintType} - */ - @Nullable - public static ConstraintType fromAttribute(@NonNull String attribute) { - if (sNameToType == null) { - ConstraintType[] types = ConstraintType.values(); - Map<String, ConstraintType> map = new HashMap<String, ConstraintType>(types.length); - for (ConstraintType type : types) { - map.put(type.name, type); - } - sNameToType = map; - } - return sNameToType.get(attribute); - } - - /** - * Returns true if this constraint type represents a constraint where the target edge - * is one of the parent edges (actual edge, not center/baseline segments) - * - * @return true if the target segment is a parent edge - */ - public boolean isRelativeToParentEdge() { - return this == ALIGN_PARENT_LEFT || this == ALIGN_PARENT_RIGHT || this == ALIGN_PARENT_TOP - || this == ALIGN_PARENT_BOTTOM; - } - - /** - * Returns a {@link ConstraintType} for a potential match of edges. - * - * @param withParent if true, the target is the parent - * @param from the source edge - * @param to the target edge - * @return a {@link ConstraintType}, or null - */ - @Nullable - public static ConstraintType forMatch(boolean withParent, SegmentType from, SegmentType to) { - // Attached to parent edge? - if (withParent) { - switch (from) { - case TOP: - return ALIGN_PARENT_TOP; - case BOTTOM: - return ALIGN_PARENT_BOTTOM; - case LEFT: - return ALIGN_PARENT_LEFT; - case RIGHT: - return ALIGN_PARENT_RIGHT; - case CENTER_HORIZONTAL: - return LAYOUT_CENTER_VERTICAL; - case CENTER_VERTICAL: - return LAYOUT_CENTER_HORIZONTAL; - } - - return null; - } - - // Attached to some other node. - switch (from) { - case TOP: - switch (to) { - case TOP: - return ALIGN_TOP; - case BOTTOM: - return LAYOUT_BELOW; - case BASELINE: - return ALIGN_BASELINE; - } - break; - case BOTTOM: - switch (to) { - case TOP: - return LAYOUT_ABOVE; - case BOTTOM: - return ALIGN_BOTTOM; - case BASELINE: - return ALIGN_BASELINE; - } - break; - case LEFT: - switch (to) { - case LEFT: - return ALIGN_LEFT; - case RIGHT: - return LAYOUT_RIGHT_OF; - } - break; - case RIGHT: - switch (to) { - case LEFT: - return LAYOUT_LEFT_OF; - case RIGHT: - return ALIGN_RIGHT; - } - break; - case BASELINE: - return ALIGN_BASELINE; - } - - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DeletionHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DeletionHandler.java deleted file mode 100644 index 3eac510df..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DeletionHandler.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (C) 2012 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.relative; - -import static com.android.SdkConstants.ANDROID_URI; -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.ATTR_LAYOUT_MARGIN; -import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX; -import static com.android.SdkConstants.ID_PREFIX; -import static com.android.SdkConstants.NEW_ID_PREFIX; -import static com.android.ide.common.layout.BaseViewRule.stripIdPrefix; -import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_CENTER_HORIZONTAL; -import static com.android.ide.common.layout.relative.ConstraintType.LAYOUT_CENTER_VERTICAL; - -import com.android.SdkConstants; -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.INode.IAttribute; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Handles deletions in a relative layout, transferring constraints across - * deleted nodes - * <p> - * TODO: Consider adding the - * {@link SdkConstants#ATTR_LAYOUT_ALIGN_WITH_PARENT_MISSING} attribute to a - * node if it's pointing to a node which is deleted and which has no transitive - * reference to another node. - */ -public class DeletionHandler { - private final INode mLayout; - private final INode[] mChildren; - private final List<INode> mDeleted; - private final Set<String> mDeletedIds; - private final Map<String, INode> mNodeMap; - private final List<INode> mMoved; - - /** - * Creates a new {@link DeletionHandler} - * - * @param deleted the deleted nodes - * @param moved nodes that were moved (e.g. deleted, but also inserted elsewhere) - * @param layout the parent layout of the deleted nodes - */ - public DeletionHandler(@NonNull List<INode> deleted, @NonNull List<INode> moved, - @NonNull INode layout) { - mDeleted = deleted; - mMoved = moved; - mLayout = layout; - - mChildren = mLayout.getChildren(); - mNodeMap = Maps.newHashMapWithExpectedSize(mChildren.length); - for (INode child : mChildren) { - String id = child.getStringAttr(ANDROID_URI, ATTR_ID); - if (id != null) { - mNodeMap.put(stripIdPrefix(id), child); - } - } - - mDeletedIds = Sets.newHashSetWithExpectedSize(mDeleted.size()); - for (INode node : mDeleted) { - String id = node.getStringAttr(ANDROID_URI, ATTR_ID); - if (id != null) { - mDeletedIds.add(stripIdPrefix(id)); - } - } - - // Any widgets that remain (e.g. typically because they were moved) should - // keep their incoming dependencies - for (INode node : mMoved) { - String id = node.getStringAttr(ANDROID_URI, ATTR_ID); - if (id != null) { - mDeletedIds.remove(stripIdPrefix(id)); - } - } - } - - @Nullable - private static String getId(@NonNull IAttribute attribute) { - if (attribute.getName().startsWith(ATTR_LAYOUT_RESOURCE_PREFIX) - && ANDROID_URI.equals(attribute.getUri()) - && !attribute.getName().startsWith(ATTR_LAYOUT_MARGIN)) { - String id = attribute.getValue(); - // It might not be an id reference, so check manually rather than just - // calling stripIdPrefix(): - if (id.startsWith(NEW_ID_PREFIX)) { - return id.substring(NEW_ID_PREFIX.length()); - } else if (id.startsWith(ID_PREFIX)) { - return id.substring(ID_PREFIX.length()); - } - } - - return null; - } - - /** - * Updates the constraints in the layout to handle deletion of a set of - * nodes. This ensures that any constraints pointing to one of the deleted - * nodes are changed properly to point to a non-deleted node with similar - * constraints. - */ - public void updateConstraints() { - if (mChildren.length == mDeleted.size()) { - // Deleting everything: Nothing to be done - return; - } - - // Now remove incoming edges to any views that were deleted. If possible, - // don't just delete them but replace them with a transitive constraint, e.g. - // if we have "A <= B <= C" and "B" is removed, then we end up with "A <= C", - - for (INode child : mChildren) { - if (mDeleted.contains(child)) { - continue; - } - - for (IAttribute attribute : child.getLiveAttributes()) { - String id = getId(attribute); - if (id != null) { - if (mDeletedIds.contains(id)) { - // Unset this reference to a deleted widget. It might be - // replaced if the pointed to node points to some other node - // on the same side, but it may use a different constraint name, - // or have none at all (e.g. parent). - String name = attribute.getName(); - child.setAttribute(ANDROID_URI, name, null); - - INode deleted = mNodeMap.get(id); - if (deleted != null) { - ConstraintType type = ConstraintType.fromAttribute(name); - if (type != null) { - transfer(deleted, child, type, 0); - } - } - } - } - } - } - } - - private void transfer(INode deleted, INode target, ConstraintType targetType, int depth) { - if (depth == 20) { - // Prevent really deep flow or unbounded recursion in case there is a bug in - // the cycle detection code - return; - } - - assert mDeleted.contains(deleted); - - for (IAttribute attribute : deleted.getLiveAttributes()) { - String name = attribute.getName(); - ConstraintType type = ConstraintType.fromAttribute(name); - if (type == null) { - continue; - } - - ConstraintType transfer = getCompatibleConstraint(type, targetType); - if (transfer != null) { - String id = getId(attribute); - if (id != null) { - if (mDeletedIds.contains(id)) { - INode nextDeleted = mNodeMap.get(id); - if (nextDeleted != null) { - // Points to another deleted node: recurse - transfer(nextDeleted, target, targetType, depth + 1); - } - } else { - // Found an undeleted node destination: point to it directly. - // Note that we're using the - target.setAttribute(ANDROID_URI, transfer.name, attribute.getValue()); - } - } else { - // Pointing to parent or center etc (non-id ref): replicate this on the target - target.setAttribute(ANDROID_URI, name, attribute.getValue()); - } - } - } - } - - /** - * Determines if two constraints are in the same direction and if so returns - * the constraint in the same direction. Rather than returning boolean true - * or false, this returns the constraint which is sometimes modified. For - * example, if you have a node which points left to a node which is centered - * in parent, then the constraint is turned into center horizontal. - */ - @Nullable - private static ConstraintType getCompatibleConstraint( - @NonNull ConstraintType first, @NonNull ConstraintType second) { - if (first == second) { - return first; - } - - switch (second) { - case ALIGN_LEFT: - case LAYOUT_RIGHT_OF: - switch (first) { - case LAYOUT_CENTER_HORIZONTAL: - case LAYOUT_LEFT_OF: - case ALIGN_LEFT: - return first; - case LAYOUT_CENTER_IN_PARENT: - return LAYOUT_CENTER_HORIZONTAL; - } - return null; - - case ALIGN_RIGHT: - case LAYOUT_LEFT_OF: - switch (first) { - case LAYOUT_CENTER_HORIZONTAL: - case ALIGN_RIGHT: - case LAYOUT_LEFT_OF: - return first; - case LAYOUT_CENTER_IN_PARENT: - return LAYOUT_CENTER_HORIZONTAL; - } - return null; - - case ALIGN_TOP: - case LAYOUT_BELOW: - case ALIGN_BASELINE: - switch (first) { - case LAYOUT_CENTER_VERTICAL: - case ALIGN_TOP: - case LAYOUT_BELOW: - case ALIGN_BASELINE: - return first; - case LAYOUT_CENTER_IN_PARENT: - return LAYOUT_CENTER_VERTICAL; - } - return null; - case ALIGN_BOTTOM: - case LAYOUT_ABOVE: - switch (first) { - case LAYOUT_CENTER_VERTICAL: - case ALIGN_BOTTOM: - case LAYOUT_ABOVE: - case ALIGN_BASELINE: - return first; - case LAYOUT_CENTER_IN_PARENT: - return LAYOUT_CENTER_VERTICAL; - } - return null; - } - - return null; - } -} 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 deleted file mode 100644 index 43d52d137..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/DependencyGraph.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * 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.relative; - -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX; -import static com.android.SdkConstants.VALUE_TRUE; - - -import com.android.SdkConstants; -import static com.android.SdkConstants.ANDROID_URI; -import com.android.ide.common.api.IDragElement; -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; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Data structure about relative layout relationships which makes it possible to: - * <ul> - * <li> Quickly determine not just the dependencies on other nodes, but which nodes - * depend on this node such that they can be visualized for the selection - * <li> Determine if there are cyclic dependencies, and whether a potential move - * would result in a cycle - * <li> Determine the "depth" of a given node (in terms of how many connections it - * is away from a parent edge) such that we can prioritize connections which - * minimizes the depth - * </ul> - */ -class DependencyGraph { - /** Format to chain include cycles in: a=>b=>c=>d etc */ - static final String CHAIN_FORMAT = "%1$s=>%2$s"; //$NON-NLS-1$ - - /** Format to chain constraint dependencies: button 1 above button2 etc */ - private static final String DEPENDENCY_FORMAT = "%1$s %2$s %3$s"; //$NON-NLS-1$ - - private final Map<String, ViewData> mIdToView = new HashMap<String, ViewData>(); - private final Map<INode, ViewData> mNodeToView = new HashMap<INode, ViewData>(); - - /** Constructs a new {@link DependencyGraph} for the given relative layout */ - DependencyGraph(INode layout) { - INode[] nodes = layout.getChildren(); - - // Parent view: - String parentId = layout.getStringAttr(ANDROID_URI, ATTR_ID); - if (parentId != null) { - parentId = BaseLayoutRule.stripIdPrefix(parentId); - } else { - parentId = "RelativeLayout"; // For display purposes; we never reference - // the parent id from a constraint, only via parent-relative params - // like centerInParent - } - ViewData parentView = new ViewData(layout, parentId); - mNodeToView.put(layout, parentView); - if (parentId != null) { - mIdToView.put(parentId, parentView); - } - - for (INode child : nodes) { - String id = child.getStringAttr(ANDROID_URI, ATTR_ID); - if (id != null) { - id = BaseLayoutRule.stripIdPrefix(id); - } - ViewData view = new ViewData(child, id); - mNodeToView.put(child, view); - if (id != null) { - mIdToView.put(id, view); - } - } - - for (ViewData view : mNodeToView.values()) { - for (IAttribute attribute : view.node.getLiveAttributes()) { - String name = attribute.getName(); - ConstraintType type = ConstraintType.fromAttribute(name); - if (type != null) { - String value = attribute.getValue(); - - if (type.targetParent) { - if (value.equals(VALUE_TRUE)) { - Constraint constraint = new Constraint(type, view, parentView); - view.dependsOn.add(constraint); - parentView.dependedOnBy.add(constraint); - } - } else { - // id-based constraint. - // NOTE: The id could refer to some widget that is NOT a sibling! - String targetId = BaseLayoutRule.stripIdPrefix(value); - ViewData target = mIdToView.get(targetId); - if (target == view) { - // Self-reference. RelativeLayout ignores these so it's - // not an error like a deeper cycle (where RelativeLayout - // will throw an exception), but we might as well warn - // the user about it. - // TODO: Where do we emit this error? - } else if (target != null) { - Constraint constraint = new Constraint(type, view, target); - view.dependsOn.add(constraint); - target.dependedOnBy.add(constraint); - } else { - // This is valid but we might want to warn... - //System.out.println("Warning: no view data found for " + targetId); - } - } - } - } - } - } - - public ViewData getView(IDragElement element) { - IDragAttribute attribute = element.getAttribute(ANDROID_URI, ATTR_ID); - if (attribute != null) { - String id = attribute.getValue(); - id = BaseLayoutRule.stripIdPrefix(id); - return getView(id); - } - - return null; - } - - public ViewData getView(String id) { - return mIdToView.get(id); - } - - public ViewData getView(INode node) { - return mNodeToView.get(node); - } - - /** - * Returns the set of views that depend on the given node in either the horizontal or - * vertical direction - * - * @param nodes the set of nodes that we want to compute the transitive dependencies - * for - * @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 - */ - public Set<INode> dependsOn(Collection<? extends INode> nodes, boolean vertical) { - List<ViewData> reachable = new ArrayList<ViewData>(); - - // Traverse the graph of constraints and determine all nodes affected by - // this node - Set<ViewData> visiting = new HashSet<ViewData>(); - for (INode node : nodes) { - ViewData view = mNodeToView.get(node); - if (view != null) { - findBackwards(view, visiting, reachable, vertical, view); - } - } - - Set<INode> dependents = new HashSet<INode>(reachable.size()); - - for (ViewData v : reachable) { - dependents.add(v.node); - } - - return dependents; - } - - private void findBackwards(ViewData view, - Set<ViewData> visiting, List<ViewData> reachable, - boolean vertical, ViewData start) { - visiting.add(view); - reachable.add(view); - - for (Constraint constraint : view.dependedOnBy) { - if (vertical && !constraint.type.verticalEdge) { - continue; - } else if (!vertical && !constraint.type.horizontalEdge) { - continue; - } - - assert constraint.to == view; - ViewData from = constraint.from; - if (visiting.contains(from)) { - // Cycle - what do we do to highlight this? - List<Constraint> path = getPathTo(start.node, view.node, vertical); - if (path != null) { - // TODO: display to the user somehow. We need log access for the - // view rules. - System.out.println(Constraint.describePath(path, null, null)); - } - } else { - findBackwards(from, visiting, reachable, vertical, start); - } - } - - visiting.remove(view); - } - - public List<Constraint> getPathTo(INode from, INode to, boolean vertical) { - // Traverse the graph of constraints and determine all nodes affected by - // this node - Set<ViewData> visiting = new HashSet<ViewData>(); - List<Constraint> path = new ArrayList<Constraint>(); - ViewData view = mNodeToView.get(from); - if (view != null) { - return findForwards(view, visiting, path, vertical, to); - } - - return null; - } - - private List<Constraint> findForwards(ViewData view, Set<ViewData> visiting, - List<Constraint> path, boolean vertical, INode target) { - visiting.add(view); - - for (Constraint constraint : view.dependsOn) { - if (vertical && !constraint.type.verticalEdge) { - continue; - } else if (!vertical && !constraint.type.horizontalEdge) { - continue; - } - - try { - path.add(constraint); - - if (constraint.to.node == target) { - return new ArrayList<Constraint>(path); - } - - assert constraint.from == view; - ViewData to = constraint.to; - if (visiting.contains(to)) { - // CYCLE! - continue; - } - - List<Constraint> chain = findForwards(to, visiting, path, vertical, target); - if (chain != null) { - return chain; - } - } finally { - path.remove(constraint); - } - } - - visiting.remove(view); - - return null; - } - - /** - * Info about a specific widget child of a relative layout and its constraints. This - * is a node in the dependency graph. - */ - static class ViewData { - public final INode node; - public final String id; - public final List<Constraint> dependsOn = new ArrayList<Constraint>(4); - public final List<Constraint> dependedOnBy = new ArrayList<Constraint>(8); - - ViewData(INode node, String id) { - this.node = node; - this.id = id; - } - } - - /** - * Info about a specific constraint between two widgets in a relative layout. This is - * an edge in the dependency graph. - */ - static class Constraint { - public final ConstraintType type; - public final ViewData from; - public final ViewData to; - - // TODO: Initialize depth -- should be computed independently for top, left, etc. - // We can use this in GuidelineHandler.MatchComparator to prefer matches that - // are closer to a parent edge: - //public int depth; - - Constraint(ConstraintType type, ViewData from, ViewData to) { - this.type = type; - this.from = from; - this.to = to; - } - - static String describePath(List<Constraint> path, String newName, String newId) { - String s = ""; - for (int i = path.size() - 1; i >= 0; i--) { - Constraint constraint = path.get(i); - String suffix = (i == path.size() -1) ? constraint.to.id : s; - s = String.format(DEPENDENCY_FORMAT, constraint.from.id, - stripLayoutAttributePrefix(constraint.type.name), suffix); - } - - if (newName != null) { - s = String.format(DEPENDENCY_FORMAT, s, stripLayoutAttributePrefix(newName), - BaseLayoutRule.stripIdPrefix(newId)); - } - - return s; - } - - private static String stripLayoutAttributePrefix(String name) { - if (name.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) { - return name.substring(ATTR_LAYOUT_RESOURCE_PREFIX.length()); - } - - return name; - } - } -} 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 deleted file mode 100644 index db08b1857..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelineHandler.java +++ /dev/null @@ -1,839 +0,0 @@ -/* - * 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.relative; - -import static com.android.ide.common.api.MarginType.NO_MARGIN; -import static com.android.ide.common.api.MarginType.WITHOUT_MARGIN; -import static com.android.ide.common.api.MarginType.WITH_MARGIN; -import static com.android.ide.common.api.SegmentType.BASELINE; -import static com.android.ide.common.api.SegmentType.BOTTOM; -import static com.android.ide.common.api.SegmentType.CENTER_HORIZONTAL; -import static com.android.ide.common.api.SegmentType.CENTER_VERTICAL; -import static com.android.ide.common.api.SegmentType.LEFT; -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.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_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_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_TO_LEFT_OF; -import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF; -import static com.android.SdkConstants.VALUE_N_DP; -import static com.android.SdkConstants.VALUE_TRUE; -import static com.android.ide.common.layout.relative.ConstraintType.ALIGN_BASELINE; - -import static java.lang.Math.abs; - -import com.android.SdkConstants; -import static com.android.SdkConstants.ANDROID_URI; -import com.android.ide.common.api.DropFeedback; -import com.android.ide.common.api.IClientRulesEngine; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Margins; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.Segment; -import com.android.ide.common.api.SegmentType; -import com.android.ide.common.layout.BaseLayoutRule; -import com.android.ide.common.layout.relative.DependencyGraph.Constraint; -import com.android.ide.common.layout.relative.DependencyGraph.ViewData; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Set; - -/** - * The {@link GuidelineHandler} class keeps track of state related to a guideline operation - * like move and resize, and performs various constraint computations. - */ -public class GuidelineHandler { - /** - * A dependency graph for the relative layout recording constraint relationships - */ - protected DependencyGraph mDependencyGraph; - - /** The RelativeLayout we are moving/resizing within */ - public INode layout; - - /** The set of nodes being dragged (may be null) */ - protected Collection<INode> mDraggedNodes; - - /** The bounds of the primary child node being dragged */ - protected Rect mBounds; - - /** Whether the left edge is being moved/resized */ - protected boolean mMoveLeft; - - /** Whether the right edge is being moved/resized */ - protected boolean mMoveRight; - - /** Whether the top edge is being moved/resized */ - protected boolean mMoveTop; - - /** Whether the bottom edge is being moved/resized */ - protected boolean mMoveBottom; - - /** - * Whether the drop/move/resize position should be snapped (which can be turned off - * with a modifier key during the operation) - */ - protected boolean mSnap = true; - - /** - * The set of nodes which depend on the currently selected nodes, including - * 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 (a "vertical constraint" - * is a constraint between two vertical edges) - */ - protected Set<INode> mVerticalDeps; - - /** The current list of constraints which result in a horizontal cycle (if applicable) */ - protected List<Constraint> mHorizontalCycle; - - /** The current list of constraints which result in a vertical cycle (if applicable) */ - protected List<Constraint> mVerticalCycle; - - /** - * All horizontal segments in the relative layout - top and bottom edges, baseline - * edges, and top and bottom edges offset by the applicable margins in each direction - */ - protected List<Segment> mHorizontalEdges; - - /** - * All vertical segments in the relative layout - left and right edges, and left and - * right edges offset by the applicable margins in each direction - */ - protected List<Segment> mVerticalEdges; - - /** - * All center vertical segments in the relative layout. These are kept separate since - * they only match other center edges. - */ - protected List<Segment> mCenterVertEdges; - - /** - * All center horizontal segments in the relative layout. These are kept separate - * since they only match other center edges. - */ - protected List<Segment> mCenterHorizEdges; - - /** - * Suggestions for horizontal matches. There could be more than one, but all matches - * will be equidistant from the current position (as well as in the same direction, - * which means that you can't have one match 5 pixels to the left and one match 5 - * pixels to the right since it would be impossible to snap to fit with both; you can - * however have multiple matches all 5 pixels to the left.) - * <p - * The best vertical match will be found in {@link #mCurrentTopMatch} or - * {@link #mCurrentBottomMatch}. - */ - protected List<Match> mHorizontalSuggestions; - - /** - * Suggestions for vertical matches. - * <p - * The best vertical match will be found in {@link #mCurrentLeftMatch} or - * {@link #mCurrentRightMatch}. - */ - protected List<Match> mVerticalSuggestions; - - /** - * The current match on the left edge, or null if no match or if the left edge is not - * being moved or resized. - */ - protected Match mCurrentLeftMatch; - - /** - * The current match on the top edge, or null if no match or if the top edge is not - * being moved or resized. - */ - protected Match mCurrentTopMatch; - - /** - * The current match on the right edge, or null if no match or if the right edge is - * not being moved or resized. - */ - protected Match mCurrentRightMatch; - - /** - * The current match on the bottom edge, or null if no match or if the bottom edge is - * not being moved or resized. - */ - protected Match mCurrentBottomMatch; - - /** - * The amount of margin to add to the top edge, or 0 - */ - protected int mTopMargin; - - /** - * The amount of margin to add to the bottom edge, or 0 - */ - protected int mBottomMargin; - - /** - * The amount of margin to add to the left edge, or 0 - */ - protected int mLeftMargin; - - /** - * The amount of margin to add to the right edge, or 0 - */ - protected int mRightMargin; - - /** - * The associated rules engine - */ - protected IClientRulesEngine mRulesEngine; - - /** - * Construct a new {@link GuidelineHandler} for the given relative layout. - * - * @param layout the RelativeLayout to handle - */ - GuidelineHandler(INode layout, IClientRulesEngine rulesEngine) { - this.layout = layout; - mRulesEngine = rulesEngine; - - mHorizontalEdges = new ArrayList<Segment>(); - mVerticalEdges = new ArrayList<Segment>(); - mCenterVertEdges = new ArrayList<Segment>(); - mCenterHorizEdges = new ArrayList<Segment>(); - mDependencyGraph = new DependencyGraph(layout); - } - - /** - * Returns true if the handler has any suggestions to offer - * - * @return true if the handler has any suggestions to offer - */ - public boolean haveSuggestions() { - return mCurrentLeftMatch != null || mCurrentTopMatch != null - || mCurrentRightMatch != null || mCurrentBottomMatch != null; - } - - /** - * Returns the closest match. - * - * @return the closest match, or null if nothing matched - */ - protected Match pickBestMatch(List<Match> matches) { - int alternatives = matches.size(); - if (alternatives == 0) { - return null; - } else if (alternatives == 1) { - Match match = matches.get(0); - return match; - } else { - assert alternatives > 1; - Collections.sort(matches, new MatchComparator()); - return matches.get(0); - } - } - - private boolean checkCycle(DropFeedback feedback, Match match, boolean vertical) { - if (match != null && match.cycle) { - for (INode node : mDraggedNodes) { - INode from = match.edge.node; - assert match.with.node == null || match.with.node == node; - INode to = node; - List<Constraint> path = mDependencyGraph.getPathTo(from, to, vertical); - if (path != null) { - if (vertical) { - mVerticalCycle = path; - } else { - mHorizontalCycle = path; - } - String desc = Constraint.describePath(path, - match.type.name, match.edge.id); - - feedback.errorMessage = "Constraint creates a cycle: " + desc; - return true; - } - } - } - - return false; - } - - /** - * Checks for any cycles in the dependencies - * - * @param feedback the drop feedback state - */ - public void checkCycles(DropFeedback feedback) { - // Deliberate short circuit evaluation -- only list the first cycle - feedback.errorMessage = null; - mHorizontalCycle = null; - mVerticalCycle = null; - - if (checkCycle(feedback, mCurrentTopMatch, true /* vertical */) - || checkCycle(feedback, mCurrentBottomMatch, true)) { - } - - if (checkCycle(feedback, mCurrentLeftMatch, false) - || checkCycle(feedback, mCurrentRightMatch, false)) { - } - } - - /** Records the matchable outside edges for the given node to the potential match list */ - protected void addBounds(INode node, String id, - boolean addHorizontal, boolean addVertical) { - Rect b = node.getBounds(); - Margins margins = node.getMargins(); - if (addHorizontal) { - if (margins.top != 0) { - mHorizontalEdges.add(new Segment(b.y, b.x, b.x2(), node, id, TOP, WITHOUT_MARGIN)); - mHorizontalEdges.add(new Segment(b.y - margins.top, b.x, b.x2(), node, id, - TOP, WITH_MARGIN)); - } else { - mHorizontalEdges.add(new Segment(b.y, b.x, b.x2(), node, id, TOP, NO_MARGIN)); - } - if (margins.bottom != 0) { - mHorizontalEdges.add(new Segment(b.y2(), b.x, b.x2(), node, id, BOTTOM, - WITHOUT_MARGIN)); - mHorizontalEdges.add(new Segment(b.y2() + margins.bottom, b.x, b.x2(), node, - id, BOTTOM, WITH_MARGIN)); - } else { - mHorizontalEdges.add(new Segment(b.y2(), b.x, b.x2(), node, id, - BOTTOM, NO_MARGIN)); - } - } - if (addVertical) { - if (margins.left != 0) { - mVerticalEdges.add(new Segment(b.x, b.y, b.y2(), node, id, LEFT, WITHOUT_MARGIN)); - mVerticalEdges.add(new Segment(b.x - margins.left, b.y, b.y2(), node, id, LEFT, - WITH_MARGIN)); - } else { - mVerticalEdges.add(new Segment(b.x, b.y, b.y2(), node, id, LEFT, NO_MARGIN)); - } - - if (margins.right != 0) { - mVerticalEdges.add(new Segment(b.x2(), b.y, b.y2(), node, id, - RIGHT, WITHOUT_MARGIN)); - mVerticalEdges.add(new Segment(b.x2() + margins.right, b.y, b.y2(), node, id, - RIGHT, WITH_MARGIN)); - } else { - mVerticalEdges.add(new Segment(b.x2(), b.y, b.y2(), node, id, - RIGHT, NO_MARGIN)); - } - } - } - - /** Records the center edges for the given node to the potential match list */ - protected void addCenter(INode node, String id, - boolean addHorizontal, boolean addVertical) { - Rect b = node.getBounds(); - - if (addHorizontal) { - mCenterHorizEdges.add(new Segment(b.centerY(), b.x, b.x2(), - node, id, CENTER_HORIZONTAL, NO_MARGIN)); - } - if (addVertical) { - mCenterVertEdges.add(new Segment(b.centerX(), b.y, b.y2(), - node, id, CENTER_VERTICAL, NO_MARGIN)); - } - } - - /** Records the baseline edge for the given node to the potential match list */ - protected int addBaseLine(INode node, String id) { - int baselineY = node.getBaseline(); - if (baselineY != -1) { - Rect b = node.getBounds(); - mHorizontalEdges.add(new Segment(b.y + baselineY, b.x, b.x2(), node, id, BASELINE, - NO_MARGIN)); - } - - return baselineY; - } - - protected void snapVertical(Segment vEdge, int x, Rect newBounds) { - newBounds.x = x; - } - - protected void snapHorizontal(Segment hEdge, int y, Rect newBounds) { - newBounds.y = y; - } - - /** - * Returns whether two edge types are compatible. For example, we only match the - * center of one object with the center of another. - * - * @param edge the first edge type to compare - * @param dragged the second edge type to compare the first one with - * @param delta the delta between the two edge locations - * @return true if the two edge types can be compatibly matched - */ - protected boolean isEdgeTypeCompatible(SegmentType edge, SegmentType dragged, int delta) { - - if (Math.abs(delta) > BaseLayoutRule.getMaxMatchDistance()) { - if (dragged == LEFT || dragged == TOP) { - if (delta > 0) { - return false; - } - } else { - if (delta < 0) { - return false; - } - } - } - - switch (edge) { - case BOTTOM: - case TOP: - return dragged == TOP || dragged == BOTTOM; - case LEFT: - case RIGHT: - return dragged == LEFT || dragged == RIGHT; - - // Center horizontal, center vertical and Baseline only matches the same - // type, and only within the matching distance -- no margins! - case BASELINE: - case CENTER_HORIZONTAL: - case CENTER_VERTICAL: - return dragged == edge && Math.abs(delta) < getMaxMatchDistance(); - default: assert false : edge; - } - return false; - } - - /** - * Finds the closest matching segments among the given list of edges for the given - * dragged edge, and returns these as a list of matches - */ - protected List<Match> findClosest(Segment draggedEdge, List<Segment> edges) { - List<Match> closest = new ArrayList<Match>(); - addClosest(draggedEdge, edges, closest); - return closest; - } - - protected void addClosest(Segment draggedEdge, List<Segment> edges, - List<Match> closest) { - int at = draggedEdge.at; - int closestDelta = closest.size() > 0 ? closest.get(0).delta : Integer.MAX_VALUE; - int closestDistance = abs(closestDelta); - for (Segment edge : edges) { - assert draggedEdge.edgeType.isHorizontal() == edge.edgeType.isHorizontal(); - - int delta = edge.at - at; - int distance = abs(delta); - if (distance > closestDistance) { - continue; - } - - if (!isEdgeTypeCompatible(edge.edgeType, draggedEdge.edgeType, delta)) { - continue; - } - - boolean withParent = edge.node == layout; - ConstraintType type = ConstraintType.forMatch(withParent, - draggedEdge.edgeType, edge.edgeType); - if (type == null) { - continue; - } - - // Ensure that the edge match is compatible; for example, a "below" - // constraint can only apply to the margin bounds and a "bottom" - // constraint can only apply to the non-margin bounds. - if (type.relativeToMargin && edge.marginType == WITHOUT_MARGIN) { - continue; - } else if (!type.relativeToMargin && edge.marginType == WITH_MARGIN) { - continue; - } - - Match match = new Match(this, edge, draggedEdge, type, delta); - - if (distance < closestDistance) { - closest.clear(); - closestDistance = distance; - closestDelta = delta; - } else if (delta * closestDelta < 0) { - // They have different signs, e.g. the matches are equal but - // on opposite sides; can't accept them both - continue; - } - closest.add(match); - } - } - - protected void clearSuggestions() { - mHorizontalSuggestions = mVerticalSuggestions = null; - mCurrentLeftMatch = mCurrentRightMatch = null; - mCurrentTopMatch = mCurrentBottomMatch = null; - } - - /** - * 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 - String centerBoth = n.getStringAttr(ANDROID_URI, ATTR_LAYOUT_CENTER_IN_PARENT); - if (centerBoth != null && centerBoth.equals(VALUE_TRUE)) { - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_IN_PARENT, null); - - // If you had a center-in-both-directions attribute, and you're - // only resizing in one dimension, then leave the other dimension - // centered, e.g. if you have centerInParent and apply alignLeft, - // then you should end up with alignLeft and centerVertically - if (mCurrentTopMatch == null && mCurrentBottomMatch == null) { - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_VERTICAL, VALUE_TRUE); - } - if (mCurrentLeftMatch == null && mCurrentRightMatch == null) { - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_HORIZONTAL, VALUE_TRUE); - } - } - - if (mMoveTop) { - // Remove top attachments - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_PARENT_TOP, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_TOP, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_BELOW, null); - - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_VERTICAL, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_BASELINE, null); - - } - - if (mMoveBottom) { - // Remove bottom attachments - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_BOTTOM, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ABOVE, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_VERTICAL, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_BASELINE, null); - } - - if (mMoveLeft) { - // Remove left attachments - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_PARENT_LEFT, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_LEFT, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_TO_RIGHT_OF, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_HORIZONTAL, null); - } - - if (mMoveRight) { - // Remove right attachments - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_PARENT_RIGHT, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_ALIGN_RIGHT, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_TO_LEFT_OF, null); - n.setAttribute(ANDROID_URI, ATTR_LAYOUT_CENTER_HORIZONTAL, null); - } - - if (mMoveTop && mCurrentTopMatch != null) { - 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(true); - c = c.replace(ATTR_LAYOUT_ALIGN_BASELINE, ATTR_LAYOUT_ALIGN_BOTTOM); - applyConstraint(n, c); - } - } - - if (mMoveBottom && mCurrentBottomMatch != null) { - applyConstraint(n, mCurrentBottomMatch.getConstraint(true)); - } - - if (mMoveLeft && mCurrentLeftMatch != null) { - applyConstraint(n, mCurrentLeftMatch.getConstraint(true)); - } - - if (mMoveRight && mCurrentRightMatch != null) { - applyConstraint(n, mCurrentRightMatch.getConstraint(true)); - } - - if (mMoveLeft) { - applyMargin(n, ATTR_LAYOUT_MARGIN_LEFT, mLeftMargin); - } - if (mMoveRight) { - applyMargin(n, ATTR_LAYOUT_MARGIN_RIGHT, mRightMargin); - } - if (mMoveTop) { - applyMargin(n, ATTR_LAYOUT_MARGIN_TOP, mTopMargin); - } - if (mMoveBottom) { - applyMargin(n, ATTR_LAYOUT_MARGIN_BOTTOM, mBottomMargin); - } - } - - private void applyConstraint(INode n, String constraint) { - assert constraint.contains("=") : constraint; - 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) { - if (margin > 0) { - int dp = mRulesEngine.pxToDp(margin); - n.setAttribute(ANDROID_URI, marginAttribute, String.format(VALUE_N_DP, dp)); - } else if (n.getStringAttr(ANDROID_URI, marginAttribute) != null) { - // Clear out existing margin - n.setAttribute(ANDROID_URI, marginAttribute, null); - } - } - - 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; - } - } - - /** Breaks any cycles detected by the handler */ - public void removeCycles() { - if (mHorizontalCycle != null) { - removeCycles(mHorizontalDeps); - } - if (mVerticalCycle != null) { - removeCycles(mVerticalDeps); - } - } - - private void removeCycles(Set<INode> deps) { - for (INode node : mDraggedNodes) { - ViewData view = mDependencyGraph.getView(node); - if (view != null) { - for (Constraint constraint : view.dependedOnBy) { - // For now, remove ALL constraints pointing to this node in this orientation. - // Later refine this to be smarter. (We can't JUST remove the constraints - // identified in the cycle since there could be multiple.) - constraint.from.node.setAttribute(ANDROID_URI, constraint.type.name, null); - } - } - } - } - - /** - * Comparator used to sort matches such that the first match is the most desirable - * match (where we prefer attaching to parent bounds, we avoid matches that lead to a - * cycle, we prefer constraints on closer widgets rather than ones further away, and - * so on.) - * <p> - * There are a number of sorting criteria. One of them is the distance between the - * matched edges. We may end up with multiple matches that are the same distance. In - * that case we look at the orientation; on the left side, prefer left-oriented - * attachments, and on the right-side prefer right-oriented attachments. For example, - * consider the following scenario: - * - * <pre> - * +--------------------+-------------------------+ - * | Attached on left | | - * +--------------------+ | - * | | - * | +-----+ | - * | | A | | - * | +-----+ | - * | | - * | +-------------------------+ - * | | Attached on right | - * +--------------------+-------------------------+ - * </pre> - * - * Here, dragging the left edge should attach to the top left attached view, whereas - * in the following layout dragging the right edge would attach to the bottom view: - * - * <pre> - * +--------------------------+-------------------+ - * | Attached on left | | - * +--------------------------+ | - * | | - * | +-----+ | - * | | A | | - * | +-----+ | - * | | - * | +-------------------+ - * | | Attached on right | - * +--------------------------+-------------------+ - * - * </pre> - * - * </ul> - */ - private final class MatchComparator implements Comparator<Match> { - @Override - public int compare(Match m1, Match m2) { - // Always prefer matching parent bounds - int parent1 = m1.edge.node == layout ? -1 : 1; - int parent2 = m2.edge.node == layout ? -1 : 1; - // unless it's a center bound -- those should always get lowest priority since - // they overlap with other usually more interesting edges near the center of - // the layout. - if (m1.edge.edgeType == CENTER_HORIZONTAL - || m1.edge.edgeType == CENTER_VERTICAL) { - parent1 = 2; - } - if (m2.edge.edgeType == CENTER_HORIZONTAL - || m2.edge.edgeType == CENTER_VERTICAL) { - parent2 = 2; - } - if (parent1 != parent2) { - return parent1 - parent2; - } - - // Avoid matching edges that would lead to a cycle - if (m1.edge.edgeType.isHorizontal()) { - int cycle1 = mHorizontalDeps.contains(m1.edge.node) ? 1 : -1; - int cycle2 = mHorizontalDeps.contains(m2.edge.node) ? 1 : -1; - if (cycle1 != cycle2) { - return cycle1 - cycle2; - } - } else { - int cycle1 = mVerticalDeps.contains(m1.edge.node) ? 1 : -1; - int cycle2 = mVerticalDeps.contains(m2.edge.node) ? 1 : -1; - if (cycle1 != cycle2) { - return cycle1 - cycle2; - } - } - - // TODO: Sort by minimum depth -- do we have the depth anywhere? - - // Prefer nodes that are closer - int distance1, distance2; - if (m1.edge.to <= m1.with.from) { - distance1 = m1.with.from - m1.edge.to; - } else if (m1.edge.from >= m1.with.to) { - distance1 = m1.edge.from - m1.with.to; - } else { - // Some kind of overlap - not sure how to prioritize these yet... - distance1 = 0; - } - if (m2.edge.to <= m2.with.from) { - distance2 = m2.with.from - m2.edge.to; - } else if (m2.edge.from >= m2.with.to) { - distance2 = m2.edge.from - m2.with.to; - } else { - // Some kind of overlap - not sure how to prioritize these yet... - distance2 = 0; - } - - if (distance1 != distance2) { - return distance1 - distance2; - } - - // Prefer matching on baseline - int baseline1 = (m1.edge.edgeType == BASELINE) ? -1 : 1; - int baseline2 = (m2.edge.edgeType == BASELINE) ? -1 : 1; - if (baseline1 != baseline2) { - return baseline1 - baseline2; - } - - // Prefer matching top/left edges before matching bottom/right edges - int orientation1 = (m1.with.edgeType == LEFT || - m1.with.edgeType == TOP) ? -1 : 1; - int orientation2 = (m2.with.edgeType == LEFT || - m2.with.edgeType == TOP) ? -1 : 1; - if (orientation1 != orientation2) { - return orientation1 - orientation2; - } - - // Prefer opposite-matching over same-matching. - // In other words, if we have the choice of matching - // our left edge with another element's left edge, - // or matching our left edge with another element's right - // edge, prefer the right edge since that - // The two matches have identical distance; try to sort by - // orientation - int edgeType1 = (m1.edge.edgeType != m1.with.edgeType) ? -1 : 1; - int edgeType2 = (m2.edge.edgeType != m2.with.edgeType) ? -1 : 1; - if (edgeType1 != edgeType2) { - return edgeType1 - edgeType2; - } - - return 0; - } - } - - /** - * Returns the {@link IClientRulesEngine} IDE callback - * - * @return the {@link IClientRulesEngine} IDE callback, never null - */ - public IClientRulesEngine getRulesEngine() { - return mRulesEngine; - } -} 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 deleted file mode 100644 index 2fe74768f..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/GuidelinePainter.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * 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.relative; - -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_RESOURCE_PREFIX; -import static com.android.SdkConstants.ID_PREFIX; -import static com.android.SdkConstants.NEW_ID_PREFIX; - -import com.android.annotations.NonNull; -import com.android.ide.common.api.DrawingStyle; -import com.android.ide.common.api.DropFeedback; -import com.android.ide.common.api.IFeedbackPainter; -import com.android.ide.common.api.IGraphics; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Point; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.SegmentType; -import com.android.ide.common.layout.relative.DependencyGraph.Constraint; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * The {@link GuidelinePainter} is responsible for painting guidelines during an operation - * which uses a {@link GuidelineHandler} such as a resize operation. - */ -public final class GuidelinePainter implements IFeedbackPainter { - // ---- Implements IFeedbackPainter ---- - @Override - public void paint(@NonNull IGraphics gc, @NonNull INode node, @NonNull DropFeedback feedback) { - GuidelineHandler state = (GuidelineHandler) feedback.userData; - - for (INode dragged : state.mDraggedNodes) { - gc.useStyle(DrawingStyle.DRAGGED); - Rect bounds = dragged.getBounds(); - if (bounds.isValid()) { - gc.fillRect(bounds); - } - } - - Set<INode> horizontalDeps = state.mHorizontalDeps; - Set<INode> verticalDeps = state.mVerticalDeps; - Set<INode> deps = new HashSet<INode>(horizontalDeps.size() + verticalDeps.size()); - deps.addAll(horizontalDeps); - deps.addAll(verticalDeps); - if (deps.size() > 0) { - gc.useStyle(DrawingStyle.DEPENDENCY); - for (INode n : deps) { - // Don't highlight the selected nodes themselves - if (state.mDraggedNodes.contains(n)) { - continue; - } - Rect bounds = n.getBounds(); - gc.fillRect(bounds); - } - } - - if (state.mBounds != null) { - if (state instanceof MoveHandler) { - gc.useStyle(DrawingStyle.DROP_PREVIEW); - } else { - // Resizing - if (state.haveSuggestions()) { - gc.useStyle(DrawingStyle.RESIZE_PREVIEW); - } else { - gc.useStyle(DrawingStyle.RESIZE_FAIL); - } - } - gc.drawRect(state.mBounds); - - // Draw baseline preview too - if (feedback.dragBaseline != -1) { - int y = state.mBounds.y + feedback.dragBaseline; - gc.drawLine(state.mBounds.x, y, state.mBounds.x2(), y); - } - } - - List<String> strings = new ArrayList<String>(); - - showMatch(gc, state.mCurrentLeftMatch, state, strings, - state.mLeftMargin, ATTR_LAYOUT_MARGIN_LEFT); - showMatch(gc, state.mCurrentRightMatch, state, strings, - state.mRightMargin, ATTR_LAYOUT_MARGIN_RIGHT); - showMatch(gc, state.mCurrentTopMatch, state, strings, - state.mTopMargin, ATTR_LAYOUT_MARGIN_TOP); - showMatch(gc, state.mCurrentBottomMatch, state, strings, - state.mBottomMargin, ATTR_LAYOUT_MARGIN_BOTTOM); - - if (strings.size() > 0) { - // Update the drag tooltip - StringBuilder sb = new StringBuilder(200); - for (String s : strings) { - if (sb.length() > 0) { - sb.append('\n'); - } - sb.append(s); - } - feedback.tooltip = sb.toString(); - - // Set the tooltip orientation to ensure that it does not interfere with - // the constraint arrows - if (state.mCurrentLeftMatch != null) { - feedback.tooltipX = SegmentType.RIGHT; - } else if (state.mCurrentRightMatch != null) { - feedback.tooltipX = SegmentType.LEFT; - } - if (state.mCurrentTopMatch != null) { - feedback.tooltipY = SegmentType.BOTTOM; - } else if (state.mCurrentBottomMatch != null) { - feedback.tooltipY = SegmentType.TOP; - } - } else { - feedback.tooltip = null; - } - - if (state.mHorizontalCycle != null) { - paintCycle(gc, state, state.mHorizontalCycle); - } - if (state.mVerticalCycle != null) { - paintCycle(gc, state, state.mVerticalCycle); - } - } - - /** Paints a particular match constraint */ - private void showMatch(IGraphics gc, Match m, GuidelineHandler state, List<String> strings, - int margin, String marginAttribute) { - if (m == null) { - return; - } - ConstraintPainter.paintConstraint(gc, state.mBounds, m); - - // 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(false /* generateId */); - String description = constraint.replace(NEW_ID_PREFIX, "").replace(ID_PREFIX, ""); - if (description.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) { - description = description.substring(ATTR_LAYOUT_RESOURCE_PREFIX.length()); - } - if (margin > 0) { - int dp = state.getRulesEngine().pxToDp(margin); - description = String.format("%1$s, margin=%2$d dp", description, dp); - } - strings.add(description); - } - - /** Paints a constraint cycle */ - void paintCycle(IGraphics gc, GuidelineHandler state, List<Constraint> cycle) { - gc.useStyle(DrawingStyle.CYCLE); - assert cycle.size() > 0; - - INode from = cycle.get(0).from.node; - Rect fromBounds = from.getBounds(); - if (state.mDraggedNodes.contains(from)) { - fromBounds = state.mBounds; - } - Point fromCenter = fromBounds.center(); - INode to = null; - - List<Point> points = new ArrayList<Point>(); - points.add(fromCenter); - - for (Constraint constraint : cycle) { - assert constraint.from.node == from; - to = constraint.to.node; - assert from != null && to != null; - - Point toCenter = to.getBounds().center(); - points.add(toCenter); - - // Also go through the dragged node bounds - boolean isDragged = state.mDraggedNodes.contains(to); - if (isDragged) { - toCenter = state.mBounds.center(); - points.add(toCenter); - } - - from = to; - fromCenter = toCenter; - } - - points.add(fromCenter); - points.add(points.get(0)); - - for (int i = 1, n = points.size(); i < n; i++) { - gc.drawLine(points.get(i-1), points.get(i)); - } - } -} 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 deleted file mode 100644 index 6f3f0d0f7..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/Match.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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.relative; - -import static com.android.SdkConstants.ATTR_ID; -import static com.android.SdkConstants.VALUE_TRUE; - - -import com.android.SdkConstants; -import static com.android.SdkConstants.ANDROID_URI; -import com.android.ide.common.api.Segment; - -/** A match is a potential pairing of two segments with a given {@link ConstraintType}. */ -class Match { - /** the edge of the dragged node that is matched */ - public final Segment with; - - /** the "other" edge that the dragged edge is matched with */ - public final Segment edge; - - /** the signed distance between the matched edges */ - public final int delta; - - /** the type of constraint this is a match for */ - public final ConstraintType type; - - /** 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(GuidelineHandler handler, Segment edge, Segment with, - ConstraintType type, int delta) { - mHandler = handler; - - this.edge = edge; - this.with = with; - this.type = type; - this.delta = delta; - } - - /** - * 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(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; - } - } - - @Override - public String toString() { - return "Match [type=" + type + ", delta=" + delta + ", edge=" + edge - + "]"; - } -} 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 deleted file mode 100644 index 0fa915d81..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/MoveHandler.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * 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.relative; - -import static com.android.ide.common.api.MarginType.NO_MARGIN; -import static com.android.ide.common.api.SegmentType.BASELINE; -import static com.android.ide.common.api.SegmentType.BOTTOM; -import static com.android.ide.common.api.SegmentType.CENTER_HORIZONTAL; -import static com.android.ide.common.api.SegmentType.CENTER_VERTICAL; -import static com.android.ide.common.api.SegmentType.LEFT; -import static com.android.ide.common.api.SegmentType.RIGHT; -import static com.android.ide.common.api.SegmentType.TOP; -import static com.android.SdkConstants.ATTR_ID; - -import static java.lang.Math.abs; - -import com.android.SdkConstants; -import static com.android.SdkConstants.ANDROID_URI; -import com.android.ide.common.api.DropFeedback; -import com.android.ide.common.api.IClientRulesEngine; -import com.android.ide.common.api.IDragElement; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.Segment; -import com.android.ide.common.layout.BaseLayoutRule; -import com.android.ide.common.layout.relative.DependencyGraph.ViewData; - -import java.util.ArrayList; -import java.util.List; - -/** - * A {@link MoveHandler} is a {@link GuidelineHandler} which handles move and drop - * gestures, and offers guideline suggestions and snapping. - * <p> - * Unlike the {@link ResizeHandler}, the {@link MoveHandler} looks for matches for all - * different segment types -- the left edge, the right edge, the baseline, the center - * edges, and so on -- and picks the best among these. - */ -public class MoveHandler extends GuidelineHandler { - private int mDraggedBaseline; - - /** - * Creates a new {@link MoveHandler}. - * - * @param layout the layout element the handler is operating on - * @param elements the elements being dragged in the move operation - * @param rulesEngine the corresponding {@link IClientRulesEngine} - */ - public MoveHandler(INode layout, IDragElement[] elements, IClientRulesEngine rulesEngine) { - super(layout, rulesEngine); - - // Compute list of nodes being dragged within the layout, if any - List<INode> nodes = new ArrayList<INode>(); - for (IDragElement element : elements) { - ViewData view = mDependencyGraph.getView(element); - if (view != null) { - nodes.add(view.node); - } - } - mDraggedNodes = nodes; - - mHorizontalDeps = mDependencyGraph.dependsOn(nodes, false /* verticalEdge */); - mVerticalDeps = mDependencyGraph.dependsOn(nodes, true /* verticalEdge */); - - for (INode child : layout.getChildren()) { - Rect bc = child.getBounds(); - if (bc.isValid()) { - // First see if this node looks like it's the same as one of the - // *dragged* bounds - boolean isDragged = false; - for (IDragElement element : elements) { - // This tries to determine if an INode corresponds to an - // IDragElement, by comparing their bounds. - if (bc.equals(element.getBounds())) { - isDragged = true; - } - } - - if (!isDragged) { - String id = child.getStringAttr(ANDROID_URI, ATTR_ID); - // 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); - - addBounds(child, id, addHorizontal, addVertical); - if (addHorizontal) { - addBaseLine(child, id); - } - } - } - } - - String id = layout.getStringAttr(ANDROID_URI, ATTR_ID); - addBounds(layout, id, true, true); - addCenter(layout, id, true, true); - } - - @Override - protected void snapVertical(Segment vEdge, int x, Rect newBounds) { - int maxDistance = BaseLayoutRule.getMaxMatchDistance(); - if (vEdge.edgeType == LEFT) { - int margin = !mSnap ? 0 : abs(newBounds.x - x); - if (margin > maxDistance) { - mLeftMargin = margin; - } else { - newBounds.x = x; - } - } else if (vEdge.edgeType == RIGHT) { - int margin = !mSnap ? 0 : abs(newBounds.x - (x - newBounds.w)); - if (margin > maxDistance) { - mRightMargin = margin; - } else { - newBounds.x = x - newBounds.w; - } - } else if (vEdge.edgeType == CENTER_VERTICAL) { - newBounds.x = x - newBounds.w / 2; - } else { - assert false : vEdge; - } - } - - // TODO: Consider unifying this with the snapping logic in ResizeHandler - @Override - protected void snapHorizontal(Segment hEdge, int y, Rect newBounds) { - int maxDistance = BaseLayoutRule.getMaxMatchDistance(); - if (hEdge.edgeType == TOP) { - int margin = !mSnap ? 0 : abs(newBounds.y - y); - if (margin > maxDistance) { - mTopMargin = margin; - } else { - newBounds.y = y; - } - } else if (hEdge.edgeType == BOTTOM) { - int margin = !mSnap ? 0 : abs(newBounds.y - (y - newBounds.h)); - if (margin > maxDistance) { - mBottomMargin = margin; - } else { - newBounds.y = y - newBounds.h; - } - } else if (hEdge.edgeType == CENTER_HORIZONTAL) { - int margin = !mSnap ? 0 : abs(newBounds.y - (y - newBounds.h / 2)); - if (margin > maxDistance) { - mTopMargin = margin; - // or bottomMargin? - } else { - newBounds.y = y - newBounds.h / 2; - } - } else if (hEdge.edgeType == BASELINE) { - newBounds.y = y - mDraggedBaseline; - } else { - assert false : hEdge; - } - } - - /** - * Updates the handler for the given mouse move - * - * @param feedback the feedback handler - * @param elements the elements being dragged - * @param offsetX the new mouse X coordinate - * @param offsetY the new mouse Y coordinate - * @param modifierMask the keyboard modifiers pressed during the drag - */ - public void updateMove(DropFeedback feedback, IDragElement[] elements, - int offsetX, int offsetY, int modifierMask) { - mSnap = (modifierMask & DropFeedback.MODIFIER2) == 0; - - Rect firstBounds = elements[0].getBounds(); - INode firstNode = null; - if (mDraggedNodes != null && mDraggedNodes.size() > 0) { - // TODO - this isn't quite right; this could be a different node than we have - // bounds for! - firstNode = mDraggedNodes.iterator().next(); - firstBounds = firstNode.getBounds(); - } - - mBounds = new Rect(offsetX, offsetY, firstBounds.w, firstBounds.h); - Rect layoutBounds = layout.getBounds(); - if (mBounds.x2() > layoutBounds.x2()) { - mBounds.x -= mBounds.x2() - layoutBounds.x2(); - } - if (mBounds.y2() > layoutBounds.y2()) { - mBounds.y -= mBounds.y2() - layoutBounds.y2(); - } - if (mBounds.x < layoutBounds.x) { - mBounds.x = layoutBounds.x; - } - if (mBounds.y < layoutBounds.y) { - mBounds.y = layoutBounds.y; - } - - clearSuggestions(); - - Rect b = mBounds; - Segment edge = new Segment(b.y, b.x, b.x2(), null, null, TOP, NO_MARGIN); - List<Match> horizontalMatches = findClosest(edge, mHorizontalEdges); - edge = new Segment(b.y2(), b.x, b.x2(), null, null, BOTTOM, NO_MARGIN); - addClosest(edge, mHorizontalEdges, horizontalMatches); - - edge = new Segment(b.x, b.y, b.y2(), null, null, LEFT, NO_MARGIN); - List<Match> verticalMatches = findClosest(edge, mVerticalEdges); - edge = new Segment(b.x2(), b.y, b.y2(), null, null, RIGHT, NO_MARGIN); - addClosest(edge, mVerticalEdges, verticalMatches); - - // Match center - edge = new Segment(b.centerX(), b.y, b.y2(), null, null, CENTER_VERTICAL, NO_MARGIN); - addClosest(edge, mCenterVertEdges, verticalMatches); - edge = new Segment(b.centerY(), b.x, b.x2(), null, null, CENTER_HORIZONTAL, NO_MARGIN); - addClosest(edge, mCenterHorizEdges, horizontalMatches); - - // Match baseline - if (firstNode != null) { - int baseline = firstNode.getBaseline(); - if (baseline != -1) { - mDraggedBaseline = baseline; - edge = new Segment(b.y + baseline, b.x, b.x2(), firstNode, null, BASELINE, - NO_MARGIN); - addClosest(edge, mHorizontalEdges, horizontalMatches); - } - } else { - int baseline = feedback.dragBaseline; - if (baseline != -1) { - mDraggedBaseline = baseline; - edge = new Segment(offsetY + baseline, b.x, b.x2(), null, null, BASELINE, - NO_MARGIN); - addClosest(edge, mHorizontalEdges, horizontalMatches); - } - } - - mHorizontalSuggestions = horizontalMatches; - mVerticalSuggestions = verticalMatches; - mTopMargin = mBottomMargin = mLeftMargin = mRightMargin = 0; - - Match match = pickBestMatch(mHorizontalSuggestions); - if (match != null) { - if (mHorizontalDeps.contains(match.edge.node)) { - match.cycle = true; - } - - // Reset top AND bottom bounds regardless of whether both are bound - mMoveTop = true; - mMoveBottom = true; - - // TODO: Consider doing the snap logic on all the possible matches - // BEFORE sorting, in case this affects the best-pick algorithm (since some - // edges snap and others don't). - snapHorizontal(match.with, match.edge.at, mBounds); - - if (match.with.edgeType == TOP) { - mCurrentTopMatch = match; - } else if (match.with.edgeType == BOTTOM) { - mCurrentBottomMatch = match; - } else { - assert match.with.edgeType == CENTER_HORIZONTAL - || match.with.edgeType == BASELINE : match.with.edgeType; - mCurrentTopMatch = match; - } - } - - match = pickBestMatch(mVerticalSuggestions); - if (match != null) { - if (mVerticalDeps.contains(match.edge.node)) { - match.cycle = true; - } - - // Reset left AND right bounds regardless of whether both are bound - mMoveLeft = true; - mMoveRight = true; - - snapVertical(match.with, match.edge.at, mBounds); - - if (match.with.edgeType == LEFT) { - mCurrentLeftMatch = match; - } else if (match.with.edgeType == RIGHT) { - mCurrentRightMatch = match; - } else { - assert match.with.edgeType == CENTER_VERTICAL; - mCurrentLeftMatch = match; - } - } - - checkCycles(feedback); - } -} 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 deleted file mode 100644 index a5e071d74..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/relative/ResizeHandler.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * 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.relative; - -import static com.android.ide.common.api.MarginType.NO_MARGIN; -import static com.android.ide.common.api.SegmentType.BASELINE; -import static com.android.ide.common.api.SegmentType.BOTTOM; -import static com.android.ide.common.api.SegmentType.CENTER_HORIZONTAL; -import static com.android.ide.common.api.SegmentType.CENTER_VERTICAL; -import static com.android.ide.common.api.SegmentType.LEFT; -import static com.android.ide.common.api.SegmentType.RIGHT; -import static com.android.ide.common.api.SegmentType.TOP; -import static com.android.SdkConstants.ATTR_ID; - -import static java.lang.Math.abs; - -import com.android.SdkConstants; -import static com.android.SdkConstants.ANDROID_URI; -import com.android.ide.common.api.DropFeedback; -import com.android.ide.common.api.IClientRulesEngine; -import com.android.ide.common.api.INode; -import com.android.ide.common.api.Rect; -import com.android.ide.common.api.Segment; -import com.android.ide.common.api.SegmentType; -import com.android.ide.common.layout.BaseLayoutRule; - -import java.util.Collections; -import java.util.Set; - -/** - * A {@link ResizeHandler} is a {@link GuidelineHandler} which handles resizing of individual - * edges in a RelativeLayout. - */ -public class ResizeHandler extends GuidelineHandler { - private final SegmentType mHorizontalEdgeType; - private final SegmentType mVerticalEdgeType; - - /** - * Creates a new {@link ResizeHandler} - * - * @param layout the layout containing the resized node - * @param resized the node being resized - * @param rulesEngine the applicable {@link IClientRulesEngine} - * @param horizontalEdgeType the type of horizontal edge being resized, or null - * @param verticalEdgeType the type of vertical edge being resized, or null - */ - public ResizeHandler(INode layout, INode resized, - IClientRulesEngine rulesEngine, - SegmentType horizontalEdgeType, SegmentType verticalEdgeType) { - super(layout, rulesEngine); - - assert horizontalEdgeType != null || verticalEdgeType != null; - assert horizontalEdgeType != BASELINE && verticalEdgeType != BASELINE; - assert horizontalEdgeType != CENTER_HORIZONTAL && verticalEdgeType != CENTER_HORIZONTAL; - assert horizontalEdgeType != CENTER_VERTICAL && verticalEdgeType != CENTER_VERTICAL; - - mHorizontalEdgeType = horizontalEdgeType; - mVerticalEdgeType = verticalEdgeType; - - Set<INode> nodes = Collections.singleton(resized); - mDraggedNodes = nodes; - - mHorizontalDeps = mDependencyGraph.dependsOn(nodes, false /* vertical */); - mVerticalDeps = mDependencyGraph.dependsOn(nodes, true /* vertical */); - - if (horizontalEdgeType != null) { - if (horizontalEdgeType == TOP) { - mMoveTop = true; - } else if (horizontalEdgeType == BOTTOM) { - mMoveBottom = true; - } - } - if (verticalEdgeType != null) { - if (verticalEdgeType == LEFT) { - mMoveLeft = true; - } else if (verticalEdgeType == RIGHT) { - mMoveRight = true; - } - } - - for (INode child : layout.getChildren()) { - if (child != resized) { - String id = child.getStringAttr(ANDROID_URI, ATTR_ID); - addBounds(child, id, - !mHorizontalDeps.contains(child), - !mVerticalDeps.contains(child)); - } - } - - addBounds(layout, layout.getStringAttr(ANDROID_URI, ATTR_ID), true, true); - } - - @Override - protected void snapVertical(Segment vEdge, int x, Rect newBounds) { - int maxDistance = BaseLayoutRule.getMaxMatchDistance(); - if (vEdge.edgeType == LEFT) { - int margin = mSnap ? 0 : abs(newBounds.x - x); - if (margin > maxDistance) { - mLeftMargin = margin; - } else { - newBounds.w += newBounds.x - x; - newBounds.x = x; - } - } else if (vEdge.edgeType == RIGHT) { - int margin = mSnap ? 0 : abs(newBounds.x - (x - newBounds.w)); - if (margin > maxDistance) { - mRightMargin = margin; - } else { - newBounds.w = x - newBounds.x; - } - } else { - assert false : vEdge; - } - } - - @Override - protected void snapHorizontal(Segment hEdge, int y, Rect newBounds) { - int maxDistance = BaseLayoutRule.getMaxMatchDistance(); - if (hEdge.edgeType == TOP) { - int margin = mSnap ? 0 : abs(newBounds.y - y); - if (margin > maxDistance) { - mTopMargin = margin; - } else { - newBounds.h += newBounds.y - y; - newBounds.y = y; - } - } else if (hEdge.edgeType == BOTTOM) { - int margin = mSnap ? 0 : abs(newBounds.y - (y - newBounds.h)); - if (margin > maxDistance) { - mBottomMargin = margin; - } else { - newBounds.h = y - newBounds.y; - } - } else { - assert false : hEdge; - } - } - - @Override - protected boolean isEdgeTypeCompatible(SegmentType edge, SegmentType dragged, int delta) { - boolean compatible = super.isEdgeTypeCompatible(edge, dragged, delta); - - // When resizing and not snapping (e.g. using margins to pick a specific pixel - // width) we cannot use -negative- margins to jump back to a closer edge; we - // must always use positive margins, so mark closer edges that result in a negative - // margin as not compatible. - if (compatible && !mSnap) { - switch (dragged) { - case LEFT: - case TOP: - return delta <= 0; - default: - return delta >= 0; - } - } - - return compatible; - } - - /** - * Updates the handler for the given mouse resize - * - * @param feedback the feedback handler - * @param child the node being resized - * @param newBounds the new bounds of the resize rectangle - * @param modifierMask the keyboard modifiers pressed during the drag - */ - public void updateResize(DropFeedback feedback, INode child, Rect newBounds, - int modifierMask) { - mSnap = (modifierMask & DropFeedback.MODIFIER2) == 0; - mBounds = newBounds; - clearSuggestions(); - - Rect b = newBounds; - Segment hEdge = null; - Segment vEdge = null; - String childId = child.getStringAttr(ANDROID_URI, ATTR_ID); - - // TODO: MarginType=NO_MARGIN may not be right. Consider resizing a widget - // that has margins and how that should be handled. - - if (mHorizontalEdgeType == TOP) { - hEdge = new Segment(b.y, b.x, b.x2(), child, childId, mHorizontalEdgeType, NO_MARGIN); - } else if (mHorizontalEdgeType == BOTTOM) { - hEdge = new Segment(b.y2(), b.x, b.x2(), child, childId, mHorizontalEdgeType, - NO_MARGIN); - } else { - assert mHorizontalEdgeType == null; - } - - if (mVerticalEdgeType == LEFT) { - vEdge = new Segment(b.x, b.y, b.y2(), child, childId, mVerticalEdgeType, NO_MARGIN); - } else if (mVerticalEdgeType == RIGHT) { - vEdge = new Segment(b.x2(), b.y, b.y2(), child, childId, mVerticalEdgeType, NO_MARGIN); - } else { - assert mVerticalEdgeType == null; - } - - mTopMargin = mBottomMargin = mLeftMargin = mRightMargin = 0; - - if (hEdge != null && mHorizontalEdges.size() > 0) { - // Compute horizontal matches - mHorizontalSuggestions = findClosest(hEdge, mHorizontalEdges); - - Match match = pickBestMatch(mHorizontalSuggestions); - if (match != null - && (!mSnap || Math.abs(match.delta) < BaseLayoutRule.getMaxMatchDistance())) { - if (mHorizontalDeps.contains(match.edge.node)) { - match.cycle = true; - } - - snapHorizontal(hEdge, match.edge.at, newBounds); - - if (hEdge.edgeType == TOP) { - mCurrentTopMatch = match; - } else if (hEdge.edgeType == BOTTOM) { - mCurrentBottomMatch = match; - } else { - assert hEdge.edgeType == CENTER_HORIZONTAL - || hEdge.edgeType == BASELINE : hEdge; - mCurrentTopMatch = match; - } - } - } - - if (vEdge != null && mVerticalEdges.size() > 0) { - mVerticalSuggestions = findClosest(vEdge, mVerticalEdges); - - Match match = pickBestMatch(mVerticalSuggestions); - if (match != null - && (!mSnap || Math.abs(match.delta) < BaseLayoutRule.getMaxMatchDistance())) { - if (mVerticalDeps.contains(match.edge.node)) { - match.cycle = true; - } - - // Snap - snapVertical(vEdge, match.edge.at, newBounds); - - if (vEdge.edgeType == LEFT) { - mCurrentLeftMatch = match; - } else if (vEdge.edgeType == RIGHT) { - mCurrentRightMatch = match; - } else { - assert vEdge.edgeType == CENTER_VERTICAL; - mCurrentLeftMatch = match; - } - } - } - - checkCycles(feedback); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/removecol.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/removecol.png Binary files differdeleted file mode 100644 index c41261afa..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/removecol.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/removerow.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/removerow.png Binary files differdeleted file mode 100644 index db695a714..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/removerow.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/showgrid.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/showgrid.png Binary files differdeleted file mode 100644 index 6f7bf9160..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/showgrid.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/snap.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/snap.png Binary files differdeleted file mode 100644 index b50a16ed1..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/snap.png +++ /dev/null diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/structure.png b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/structure.png Binary files differdeleted file mode 100644 index e5d753885..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/structure.png +++ /dev/null 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 differdeleted file mode 100644 index e03c16e00..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/vlinear.png +++ /dev/null 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 differdeleted file mode 100644 index cb654a140..000000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/weights.png +++ /dev/null |