Home | History | Annotate | Download | only in layout
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.common.layout;
     18 
     19 import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
     20 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_X;
     21 import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_Y;
     22 import static com.android.ide.common.layout.LayoutConstants.VALUE_N_DP;
     23 
     24 import com.android.ide.common.api.DrawingStyle;
     25 import com.android.ide.common.api.DropFeedback;
     26 import com.android.ide.common.api.IDragElement;
     27 import com.android.ide.common.api.IFeedbackPainter;
     28 import com.android.ide.common.api.IGraphics;
     29 import com.android.ide.common.api.INode;
     30 import com.android.ide.common.api.INodeHandler;
     31 import com.android.ide.common.api.IViewRule;
     32 import com.android.ide.common.api.Point;
     33 import com.android.ide.common.api.Rect;
     34 import com.android.ide.common.api.SegmentType;
     35 import com.android.util.Pair;
     36 
     37 import java.util.ArrayList;
     38 import java.util.List;
     39 import java.util.Map;
     40 
     41 /**
     42  * An {@link IViewRule} for android.widget.AbsoluteLayout and all its derived
     43  * classes.
     44  */
     45 public class AbsoluteLayoutRule extends BaseLayoutRule {
     46 
     47     @Override
     48     public List<String> getSelectionHint(INode parentNode, INode childNode) {
     49         List<String> infos = new ArrayList<String>(2);
     50         infos.add("AbsoluteLayout is deprecated.");
     51         infos.add("Use other layouts instead.");
     52         return infos;
     53     }
     54 
     55     // ==== Drag'n'drop support ====
     56     // The AbsoluteLayout accepts any drag'n'drop anywhere on its surface.
     57 
     58     @Override
     59     public DropFeedback onDropEnter(INode targetNode, Object targetView,
     60             final IDragElement[] elements) {
     61 
     62         if (elements.length == 0) {
     63             return null;
     64         }
     65 
     66         DropFeedback df = new DropFeedback(null, new IFeedbackPainter() {
     67             @Override
     68             public void paint(IGraphics gc, INode node, DropFeedback feedback) {
     69                 // Paint callback for the AbsoluteLayout.
     70                 // This is called by the canvas when a draw is needed.
     71                 drawFeedback(gc, node, elements, feedback);
     72             }
     73         });
     74         df.errorMessage = "AbsoluteLayout is deprecated.";
     75         return df;
     76     }
     77 
     78     void drawFeedback(
     79             IGraphics gc,
     80             INode targetNode,
     81             IDragElement[] elements,
     82             DropFeedback feedback) {
     83         Rect b = targetNode.getBounds();
     84         if (!b.isValid()) {
     85             return;
     86         }
     87 
     88         // Highlight the receiver
     89         gc.useStyle(DrawingStyle.DROP_RECIPIENT);
     90         gc.drawRect(b);
     91 
     92         // Get the drop point
     93         Point p = (Point) feedback.userData;
     94 
     95         if (p == null) {
     96             return;
     97         }
     98 
     99         int x = p.x;
    100         int y = p.y;
    101 
    102         Rect be = elements[0].getBounds();
    103 
    104         if (be.isValid()) {
    105             // At least the first element has a bound. Draw rectangles
    106             // for all dropped elements with valid bounds, offset at
    107             // the drop point.
    108             int offsetX = x - be.x + (feedback.dragBounds != null ? feedback.dragBounds.x : 0);
    109             int offsetY = y - be.y + (feedback.dragBounds != null ? feedback.dragBounds.y : 0);
    110             gc.useStyle(DrawingStyle.DROP_PREVIEW);
    111             for (IDragElement element : elements) {
    112                 drawElement(gc, element, offsetX, offsetY);
    113             }
    114         } else {
    115             // We don't have bounds for new elements. In this case
    116             // just draw cross hairs to the drop point.
    117             gc.useStyle(DrawingStyle.GUIDELINE);
    118             gc.drawLine(x, b.y, x, b.y + b.h);
    119             gc.drawLine(b.x, y, b.x + b.w, y);
    120 
    121             // Use preview lines to indicate the bottom quadrant as well (to
    122             // indicate that you are looking at the top left position of the
    123             // drop, not the center for example)
    124             gc.useStyle(DrawingStyle.DROP_PREVIEW);
    125             gc.drawLine(x, y, b.x + b.w, y);
    126             gc.drawLine(x, y, x, b.y + b.h);
    127         }
    128     }
    129 
    130     @Override
    131     public DropFeedback onDropMove(INode targetNode, IDragElement[] elements,
    132             DropFeedback feedback, Point p) {
    133         // Update the data used by the DropFeedback.paintCallback above.
    134         feedback.userData = p;
    135         feedback.requestPaint = true;
    136 
    137         return feedback;
    138     }
    139 
    140     @Override
    141     public void onDropLeave(INode targetNode, IDragElement[] elements, DropFeedback feedback) {
    142         // Nothing to do.
    143     }
    144 
    145     @Override
    146     public void onDropped(final INode targetNode, final IDragElement[] elements,
    147             final DropFeedback feedback, final Point p) {
    148 
    149         final Rect b = targetNode.getBounds();
    150         if (!b.isValid()) {
    151             return;
    152         }
    153 
    154         // Collect IDs from dropped elements and remap them to new IDs
    155         // if this is a copy or from a different canvas.
    156         final Map<String, Pair<String, String>> idMap = getDropIdMap(targetNode, elements,
    157                 feedback.isCopy || !feedback.sameCanvas);
    158 
    159         targetNode.editXml("Add elements to AbsoluteLayout", new INodeHandler() {
    160             @Override
    161             public void handle(INode node) {
    162                 boolean first = true;
    163                 Point offset = null;
    164 
    165                 // Now write the new elements.
    166                 for (IDragElement element : elements) {
    167                     String fqcn = element.getFqcn();
    168                     Rect be = element.getBounds();
    169 
    170                     INode newChild = targetNode.appendChild(fqcn);
    171 
    172                     // Copy all the attributes, modifying them as needed.
    173                     addAttributes(newChild, element, idMap, DEFAULT_ATTR_FILTER);
    174 
    175                     int deltaX = (feedback.dragBounds != null ? feedback.dragBounds.x : 0);
    176                     int deltaY = (feedback.dragBounds != null ? feedback.dragBounds.y : 0);
    177 
    178                     int x = p.x - b.x + deltaX;
    179                     int y = p.y - b.y + deltaY;
    180 
    181                     if (first) {
    182                         first = false;
    183                         if (be.isValid()) {
    184                             offset = new Point(x - be.x, y - be.y);
    185                         }
    186                     } else if (offset != null && be.isValid()) {
    187                         x = offset.x + be.x;
    188                         y = offset.y + be.y;
    189                     } else {
    190                         x += 10;
    191                         y += be.isValid() ? be.h : 10;
    192                     }
    193 
    194                     double scale = feedback.dipScale;
    195                     if (scale != 1.0) {
    196                         x *= scale;
    197                         y *= scale;
    198                     }
    199 
    200                     newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_X,
    201                             String.format(VALUE_N_DP, x));
    202                     newChild.setAttribute(ANDROID_URI, ATTR_LAYOUT_Y,
    203                             String.format(VALUE_N_DP, y));
    204 
    205                     addInnerElements(newChild, element, idMap);
    206                 }
    207             }
    208         });
    209     }
    210 
    211     /**
    212      * {@inheritDoc}
    213      * <p>
    214      * Overridden in this layout in order to let the top left coordinate be affected by
    215      * the resize operation too. In other words, dragging the top left corner to resize a
    216      * widget will not only change the size of the widget, it will also move it (though in
    217      * this case, the bottom right corner will stay fixed).
    218      */
    219     @Override
    220     protected void setNewSizeBounds(ResizeState resizeState, INode node, INode layout,
    221             Rect previousBounds, Rect newBounds, SegmentType horizontalEdge,
    222             SegmentType verticalEdge) {
    223         super.setNewSizeBounds(resizeState, node, layout, previousBounds, newBounds,
    224                 horizontalEdge, verticalEdge);
    225         if (verticalEdge != null && newBounds.x != previousBounds.x) {
    226             node.setAttribute(ANDROID_URI, ATTR_LAYOUT_X,
    227                     String.format(VALUE_N_DP,
    228                             mRulesEngine.pxToDp(newBounds.x - node.getParent().getBounds().x)));
    229         }
    230         if (horizontalEdge != null && newBounds.y != previousBounds.y) {
    231             node.setAttribute(ANDROID_URI, ATTR_LAYOUT_Y,
    232                     String.format(VALUE_N_DP,
    233                             mRulesEngine.pxToDp(newBounds.y - node.getParent().getBounds().y)));
    234         }
    235     }
    236 
    237     @Override
    238     protected String getResizeUpdateMessage(ResizeState resizeState, INode child, INode parent,
    239             Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) {
    240         Rect parentBounds = parent.getBounds();
    241         if (horizontalEdge == SegmentType.BOTTOM && verticalEdge == SegmentType.RIGHT) {
    242             return super.getResizeUpdateMessage(resizeState, child, parent, newBounds,
    243                     horizontalEdge, verticalEdge);
    244         }
    245         return String.format("x=%d, y=%d\nwidth=%s, height=%s",
    246                 mRulesEngine.pxToDp(newBounds.x - parentBounds.x),
    247                 mRulesEngine.pxToDp(newBounds.y - parentBounds.y),
    248                 resizeState.getWidthAttribute(), resizeState.getHeightAttribute());
    249     }
    250 }
    251