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