1 /*
2  * Copyright (C) 2012 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 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
17 
18 import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LintOverlay.ICON_SIZE;
19 
20 import com.android.annotations.Nullable;
21 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
22 import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
23 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
24 
25 import org.eclipse.swt.SWT;
26 import org.eclipse.swt.graphics.Point;
27 import org.eclipse.swt.graphics.Rectangle;
28 import org.eclipse.swt.widgets.Event;
29 import org.eclipse.swt.widgets.Listener;
30 import org.eclipse.swt.widgets.Shell;
31 import org.w3c.dom.Node;
32 
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.List;
36 
37 /** Tooltip in the layout editor showing lint errors under the cursor */
38 class LintTooltipManager implements Listener {
39     private final LayoutCanvas mCanvas;
40     private Shell mTip = null;
41     private List<UiViewElementNode> mShowingNodes;
42 
43     /**
44      * Sets up a custom tooltip when hovering over tree items. It currently displays the error
45      * message for the lint warning associated with each node, if any (and only if the hover
46      * is over the icon portion).
47      */
LintTooltipManager(LayoutCanvas canvas)48     LintTooltipManager(LayoutCanvas canvas) {
49         mCanvas = canvas;
50     }
51 
register()52     void register() {
53         mCanvas.addListener(SWT.Dispose, this);
54         mCanvas.addListener(SWT.KeyDown, this);
55         mCanvas.addListener(SWT.MouseMove, this);
56         mCanvas.addListener(SWT.MouseHover, this);
57     }
58 
unregister()59     void unregister() {
60         if (!mCanvas.isDisposed()) {
61             mCanvas.removeListener(SWT.Dispose, this);
62             mCanvas.removeListener(SWT.KeyDown, this);
63             mCanvas.removeListener(SWT.MouseMove, this);
64             mCanvas.removeListener(SWT.MouseHover, this);
65         }
66     }
67 
68     @Override
handleEvent(Event event)69     public void handleEvent(Event event) {
70         switch(event.type) {
71         case SWT.MouseMove:
72             // See if we're still overlapping this or *other* errors; if so, keep the
73             // tip up (or update it).
74             if (mShowingNodes != null) {
75                 List<UiViewElementNode> nodes = computeNodes(event);
76                 if (nodes != null && !nodes.isEmpty()) {
77                     if (nodes.equals(mShowingNodes)) {
78                         return;
79                     } else {
80                         show(nodes);
81                     }
82                     break;
83                 }
84             }
85 
86             // If not, fall through and hide the tooltip
87 
88             //$FALL-THROUGH$
89         case SWT.Dispose:
90         case SWT.FocusOut:
91         case SWT.KeyDown:
92         case SWT.MouseExit:
93         case SWT.MouseDown:
94             hide();
95             break;
96         case SWT.MouseHover:
97             hide();
98             show(event);
99             break;
100         }
101     }
102 
hide()103     void hide() {
104         if (mTip != null) {
105             mTip.dispose();
106             mTip = null;
107         }
108         mShowingNodes = null;
109     }
110 
show(Event event)111     private void show(Event event) {
112         List<UiViewElementNode> nodes = computeNodes(event);
113         if (nodes != null && !nodes.isEmpty()) {
114             show(nodes);
115         }
116     }
117 
118     /** Show a tooltip listing the lint errors for the given nodes */
show(List<UiViewElementNode> nodes)119     private void show(List<UiViewElementNode> nodes) {
120         hide();
121 
122         if (!AdtPrefs.getPrefs().isLintOnSave()) {
123             return;
124         }
125 
126         mTip = new LintTooltip(mCanvas, nodes);
127         Rectangle rect = mCanvas.getBounds();
128         Point size = mTip.computeSize(SWT.DEFAULT, SWT.DEFAULT);
129         Point pos = mCanvas.toDisplay(rect.x, rect.y + rect.height);
130         if (size.x > rect.width) {
131             size = mTip.computeSize(rect.width, SWT.DEFAULT);
132         }
133         mTip.setBounds(pos.x, pos.y, size.x, size.y);
134 
135         mShowingNodes = nodes;
136         mTip.setVisible(true);
137     }
138 
139     /**
140      * Compute the list of nodes which have lint warnings near the given mouse
141      * coordinates
142      *
143      * @param event the mouse cursor event
144      * @return a list of nodes, possibly empty
145      */
146     @Nullable
computeNodes(Event event)147     private List<UiViewElementNode> computeNodes(Event event) {
148         LayoutPoint p = ControlPoint.create(mCanvas, event.x, event.y).toLayout();
149         LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
150         ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy();
151         CanvasTransform mHScale = mCanvas.getHorizontalTransform();
152         CanvasTransform mVScale = mCanvas.getVerticalTransform();
153 
154         int layoutIconSize = mHScale.inverseScale(ICON_SIZE);
155         int slop = mVScale.inverseScale(10); // extra space around icon where tip triggers
156 
157         Collection<Node> xmlNodes = delegate.getLintNodes();
158         if (xmlNodes == null) {
159             return null;
160         }
161         List<UiViewElementNode> nodes = new ArrayList<UiViewElementNode>();
162         for (Node xmlNode : xmlNodes) {
163             CanvasViewInfo v = viewHierarchy.findViewInfoFor(xmlNode);
164             if (v != null) {
165                 Rectangle b = v.getAbsRect();
166                 int x2 = b.x + b.width;
167                 int y2 = b.y + b.height;
168                 if (p.x < x2 - layoutIconSize - slop
169                         || p.x > x2 + slop
170                         || p.y < y2 - layoutIconSize - slop
171                         || p.y > y2 + slop) {
172                     continue;
173                 }
174 
175                 nodes.add(v.getUiViewNode());
176             }
177         }
178 
179         return nodes;
180     }
181 }
182