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