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      */
SelectionOverlay(LayoutCanvas canvas)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      */
setHidden(boolean hidden)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      */
paint(SelectionManager selectionManager, GCWrapper gcWrapper, GC gc, RulesEngine rulesEngine)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 */
paintHints(GCWrapper gcWrapper, NodeProxy node, RulesEngine rulesEngine)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 
paintSelectionFeedback(GCWrapper gcWrapper, List<NodeProxy> nodes, RulesEngine rulesEngine)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. */
paintSelection(IGraphics gc, GC swtGc, SelectionItem item, boolean isMultipleSelection)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