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 @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 drawFeedback( IGraphics gc, INode targetNode, IDragElement[] elements, DropFeedback feedback)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 onDropMove(INode targetNode, IDragElement[] elements, DropFeedback feedback, Point p)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 onDropLeave(INode targetNode, IDragElement[] elements, DropFeedback feedback)141 public void onDropLeave(INode targetNode, IDragElement[] elements, DropFeedback feedback) { 142 // Nothing to do. 143 } 144 145 @Override onDropped(final INode targetNode, final IDragElement[] elements, final DropFeedback feedback, final Point p)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 setNewSizeBounds(ResizeState resizeState, INode node, INode layout, Rect previousBounds, Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge)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 getResizeUpdateMessage(ResizeState resizeState, INode child, INode parent, Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge)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