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.eclipse.adt.internal.editors.layout.gle2; 18 19 import com.android.ide.common.api.DrawingStyle; 20 import com.android.ide.common.api.IGraphics; 21 import com.android.ide.common.api.INode; 22 import com.android.ide.common.api.Margins; 23 import com.android.ide.common.api.Rect; 24 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; 25 import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; 26 27 import org.eclipse.swt.graphics.GC; 28 29 import java.util.ArrayList; 30 import java.util.Collections; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Set; 34 35 /** 36 * The {@link SelectionOverlay} paints the current selection as an overlay. 37 */ 38 public class SelectionOverlay extends Overlay { 39 private final LayoutCanvas mCanvas; 40 private boolean mHidden; 41 42 /** 43 * Constructs a new {@link SelectionOverlay} tied to the given canvas. 44 * 45 * @param canvas the associated canvas 46 */ 47 public SelectionOverlay(LayoutCanvas canvas) { 48 mCanvas = canvas; 49 } 50 51 /** 52 * Set whether the selection overlay should be hidden. This is done during some 53 * gestures like resize where the new bounds could be confused with the current 54 * selection bounds. 55 * 56 * @param hidden when true, hide the selection bounds, when false, unhide. 57 */ 58 public void setHidden(boolean hidden) { 59 mHidden = hidden; 60 } 61 62 /** 63 * Paints the selection. 64 * 65 * @param selectionManager The {@link SelectionManager} holding the 66 * selection. 67 * @param gcWrapper The graphics context wrapper for the layout rules to use. 68 * @param gc The SWT graphics object 69 * @param rulesEngine The {@link RulesEngine} holding the rules. 70 */ 71 public void paint(SelectionManager selectionManager, GCWrapper gcWrapper, 72 GC gc, RulesEngine rulesEngine) { 73 if (mHidden) { 74 return; 75 } 76 77 List<SelectionItem> selections = selectionManager.getSelections(); 78 int n = selections.size(); 79 if (n > 0) { 80 List<NodeProxy> selectedNodes = new ArrayList<NodeProxy>(); 81 boolean isMultipleSelection = n > 1; 82 for (SelectionItem s : selections) { 83 if (s.isRoot()) { 84 // The root selection is never painted 85 continue; 86 } 87 88 NodeProxy node = s.getNode(); 89 if (node != null) { 90 paintSelection(gcWrapper, gc, s, isMultipleSelection); 91 selectedNodes.add(node); 92 } 93 } 94 95 if (selectedNodes.size() > 0) { 96 paintSelectionFeedback(gcWrapper, selectedNodes, rulesEngine); 97 } else { 98 CanvasViewInfo root = mCanvas.getViewHierarchy().getRoot(); 99 if (root != null) { 100 NodeProxy parent = mCanvas.getNodeFactory().create(root); 101 rulesEngine.callPaintSelectionFeedback(gcWrapper, 102 parent, Collections.<INode>emptyList(), root.getViewObject()); 103 } 104 } 105 106 if (n == 1) { 107 NodeProxy node = selections.get(0).getNode(); 108 if (node != null) { 109 paintHints(gcWrapper, node, rulesEngine); 110 } 111 } 112 } else { 113 CanvasViewInfo root = mCanvas.getViewHierarchy().getRoot(); 114 if (root != null) { 115 NodeProxy parent = mCanvas.getNodeFactory().create(root); 116 rulesEngine.callPaintSelectionFeedback(gcWrapper, 117 parent, Collections.<INode>emptyList(), root.getViewObject()); 118 } 119 } 120 } 121 122 /** Paint hint for current selection */ 123 private void paintHints(GCWrapper gcWrapper, NodeProxy node, RulesEngine rulesEngine) { 124 INode parent = node.getParent(); 125 if (parent instanceof NodeProxy) { 126 NodeProxy parentNode = (NodeProxy) parent; 127 List<String> infos = rulesEngine.callGetSelectionHint(parentNode, node); 128 if (infos != null && infos.size() > 0) { 129 gcWrapper.useStyle(DrawingStyle.HELP); 130 131 Rect b = mCanvas.getImageOverlay().getImageBounds(); 132 if (b == null) { 133 return; 134 } 135 136 // Compute the location to display the help. This is done in 137 // layout coordinates, so we need to apply the scale in reverse 138 // when making pixel margins 139 // TODO: We could take the Canvas dimensions into account to see 140 // where there is more room. 141 // TODO: The scrollbars should take the presence of hint text 142 // into account. 143 double scale = mCanvas.getScale(); 144 int x, y; 145 if (b.w > b.h) { 146 x = (int) (b.x + 3 / scale); 147 y = (int) (b.y + b.h + 6 / scale); 148 } else { 149 x = (int) (b.x + b.w + 6 / scale); 150 y = (int) (b.y + 3 / scale); 151 } 152 gcWrapper.drawBoxedStrings(x, y, infos); 153 } 154 } 155 } 156 157 private void paintSelectionFeedback(GCWrapper gcWrapper, List<NodeProxy> nodes, 158 RulesEngine rulesEngine) { 159 // Add fastpath for n=1 160 161 // Group nodes into parent/child groups 162 Set<INode> parents = new HashSet<INode>(); 163 for (INode node : nodes) { 164 INode parent = node.getParent(); 165 if (/*parent == null || */parent instanceof NodeProxy) { 166 NodeProxy parentNode = (NodeProxy) parent; 167 parents.add(parentNode); 168 } 169 } 170 ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy(); 171 for (INode parent : parents) { 172 List<INode> children = new ArrayList<INode>(); 173 for (INode node : nodes) { 174 INode nodeParent = node.getParent(); 175 if (nodeParent == parent) { 176 children.add(node); 177 } 178 } 179 CanvasViewInfo viewInfo = viewHierarchy.findViewInfoFor((NodeProxy) parent); 180 Object view = viewInfo != null ? viewInfo.getViewObject() : null; 181 182 rulesEngine.callPaintSelectionFeedback(gcWrapper, 183 (NodeProxy) parent, children, view); 184 } 185 } 186 187 /** Called by the canvas when a view is being selected. */ 188 private void paintSelection(IGraphics gc, GC swtGc, SelectionItem item, 189 boolean isMultipleSelection) { 190 CanvasViewInfo view = item.getViewInfo(); 191 if (view.isHidden()) { 192 return; 193 } 194 195 NodeProxy selectedNode = item.getNode(); 196 Rect r = selectedNode.getBounds(); 197 if (!r.isValid()) { 198 return; 199 } 200 201 gc.useStyle(DrawingStyle.SELECTION); 202 203 Margins insets = mCanvas.getInsets(selectedNode.getFqcn()); 204 int x1 = r.x; 205 int y1 = r.y; 206 int x2 = r.x2() + 1; 207 int y2 = r.y2() + 1; 208 209 if (insets != null) { 210 x1 += insets.left; 211 x2 -= insets.right; 212 y1 += insets.top; 213 y2 -= insets.bottom; 214 } 215 216 gc.drawRect(x1, y1, x2, y2); 217 218 // Paint sibling rectangles, if applicable 219 List<CanvasViewInfo> siblings = view.getNodeSiblings(); 220 if (siblings != null) { 221 for (CanvasViewInfo sibling : siblings) { 222 if (sibling != view) { 223 r = SwtUtils.toRect(sibling.getSelectionRect()); 224 gc.fillRect(r); 225 gc.drawRect(r); 226 } 227 } 228 } 229 230 // Paint selection handles. These are painted in control coordinates on the 231 // real SWT GC object rather than in layout coordinates on the GCWrapper, 232 // since we want them to have a fixed size that is independent of the 233 // screen zoom. 234 CanvasTransform horizontalTransform = mCanvas.getHorizontalTransform(); 235 CanvasTransform verticalTransform = mCanvas.getVerticalTransform(); 236 int radius = SelectionHandle.PIXEL_RADIUS; 237 int doubleRadius = 2 * radius; 238 for (SelectionHandle handle : item.getSelectionHandles()) { 239 int cx = horizontalTransform.translate(handle.centerX); 240 int cy = verticalTransform.translate(handle.centerY); 241 242 SwtDrawingStyle style = SwtDrawingStyle.of(DrawingStyle.SELECTION); 243 gc.setAlpha(style.getStrokeAlpha()); 244 swtGc.fillRectangle(cx - radius, cy - radius, doubleRadius, doubleRadius); 245 } 246 } 247 } 248