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