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