diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java | 1092 |
1 files changed, 0 insertions, 1092 deletions
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; - } -} |