• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 org.eclipse.swt.SWT;
20 import org.eclipse.swt.custom.CLabel;
21 import org.eclipse.swt.graphics.Font;
22 import org.eclipse.swt.graphics.FontData;
23 import org.eclipse.swt.graphics.Point;
24 import org.eclipse.swt.layout.FillLayout;
25 import org.eclipse.swt.widgets.Composite;
26 import org.eclipse.swt.widgets.Display;
27 import org.eclipse.swt.widgets.Shell;
28 
29 /**
30  * A dedicated tooltip used during gestures, for example to show the resize dimensions.
31  * <p>
32  * This is necessary because {@link org.eclipse.jface.window.ToolTip} causes flicker when
33  * used to dynamically update the position and text of the tip, and it does not seem to
34  * have setter methods to update the text or position without recreating the tip.
35  */
36 public class GestureToolTip {
37     /** Minimum number of milliseconds to wait between alignment changes */
38     private static final int TIMEOUT_MS = 750;
39 
40     /**
41      * The alpha to use for the tooltip window (which sadly will apply to the tooltip text
42      * as well.)
43      */
44     private static final int SHELL_TRANSPARENCY = 220;
45 
46     /** The size of the font displayed in the tooltip */
47     private static final int FONT_SIZE = 9;
48 
49     /** Horizontal delta from the mouse cursor to shift the tooltip by */
50     private static final int OFFSET_X = 20;
51 
52     /** Vertical delta from the mouse cursor to shift the tooltip by */
53     private static final int OFFSET_Y = 20;
54 
55     /** The label which displays the tooltip */
56     private CLabel mLabel;
57 
58     /** The shell holding the tooltip */
59     private Shell mShell;
60 
61     /** The font shown in the label; held here such that it can be disposed of after use */
62     private Font mFont;
63 
64     /** Is the tooltip positioned below the given anchor? */
65     private boolean mBelow;
66 
67     /** Is the tooltip positioned to the right of the given anchor? */
68     private boolean mToRightOf;
69 
70     /** Is an alignment change pending? */
71     private boolean mTimerPending;
72 
73     /** The new value for {@link #mBelow} when the timer expires */
74     private boolean mPendingBelow;
75 
76     /** The new value for {@link #mToRightOf} when the timer expires */
77     private boolean mPendingRight;
78 
79     /** The time stamp (from {@link System#currentTimeMillis()} of the last alignment change */
80     private long mLastAlignmentTime;
81 
82     /**
83      * Creates a new tooltip over the given parent with the given relative position.
84      *
85      * @param parent the parent control
86      * @param below if true, display the tooltip below the mouse cursor otherwise above
87      * @param toRightOf if true, display the tooltip to the right of the mouse cursor,
88      *            otherwise to the left
89      */
GestureToolTip(Composite parent, boolean below, boolean toRightOf)90     public GestureToolTip(Composite parent, boolean below, boolean toRightOf) {
91         mBelow = below;
92         mToRightOf = toRightOf;
93         mLastAlignmentTime = System.currentTimeMillis();
94 
95         mShell = new Shell(parent.getShell(), SWT.ON_TOP | SWT.TOOL | SWT.NO_FOCUS);
96         mShell.setLayout(new FillLayout());
97         mShell.setAlpha(SHELL_TRANSPARENCY);
98 
99         Display display = parent.getDisplay();
100         mLabel = new CLabel(mShell, SWT.SHADOW_NONE);
101         mLabel.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
102         mLabel.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
103 
104         Font systemFont = display.getSystemFont();
105         FontData[] fd = systemFont.getFontData();
106         for (int i = 0; i < fd.length; i++) {
107             fd[i].setHeight(FONT_SIZE);
108         }
109         mFont = new Font(display, fd);
110         mLabel.setFont(mFont);
111 
112         mShell.setVisible(false);
113     }
114 
115     /**
116      * Show the tooltip at the given position and with the given text. Note that the
117      * position may not be applied immediately; to prevent flicker alignment changes
118      * are queued up with a timer (unless it's been a while since the last change, in
119      * which case the update is applied immediately.)
120      *
121      * @param text the new text to be displayed
122      * @param below if true, display the tooltip below the mouse cursor otherwise above
123      * @param toRightOf if true, display the tooltip to the right of the mouse cursor,
124      *            otherwise to the left
125      */
update(final String text, boolean below, boolean toRightOf)126     public void update(final String text, boolean below, boolean toRightOf) {
127         // If the alignment has not changed recently, just apply the change immediately
128         // instead of within a delay
129         if (!mTimerPending && (below != mBelow || toRightOf != mToRightOf)
130                 && (System.currentTimeMillis() - mLastAlignmentTime >= TIMEOUT_MS)) {
131             mBelow = below;
132             mToRightOf = toRightOf;
133             mLastAlignmentTime = System.currentTimeMillis();
134         }
135 
136         Point location = mShell.getDisplay().getCursorLocation();
137 
138         mLabel.setText(text);
139 
140         // Pack the label to its minimum size -- unless we are positioning the tooltip
141         // on the left. Because of the way SWT works (at least on the OSX) this sometimes
142         // creates flicker, because when we switch to a longer string (such as when
143         // switching from "52dp" to "wrap_content" during a resize) the window size will
144         // change first, and then the location will update later - so there will be a
145         // brief flash of the longer label before it is moved to the right position on the
146         // left. To work around this, we simply pass false to pack such that it will reuse
147         // its cached size, which in practice means that for labels on the right, the
148         // label will grow but not shrink.
149         // This workaround is disabled because it doesn't work well in Eclipse 3.5; the
150         // labels don't grow when they should. Re-enable when we drop 3.5 support.
151         //boolean changed = mToRightOf;
152         boolean changed = true;
153 
154         mShell.pack(changed);
155         Point size = mShell.getSize();
156 
157         // Position the tooltip to the left or right, and above or below, according
158         // to the saved state of these flags, not the current parameters. We don't want
159         // to flicker, instead we react on a timer to changes in alignment below.
160         if (mBelow) {
161             location.y += OFFSET_Y;
162         } else {
163             location.y -= OFFSET_Y;
164             location.y -= size.y;
165         }
166 
167         if (mToRightOf) {
168             location.x += OFFSET_X;
169         } else {
170             location.x -= OFFSET_X;
171             location.x -= size.x;
172         }
173 
174         mShell.setLocation(location);
175 
176         if (!mShell.isVisible()) {
177             mShell.setVisible(true);
178         }
179 
180         // Has the orientation changed?
181         mPendingBelow = below;
182         mPendingRight = toRightOf;
183         if (below != mBelow || toRightOf != mToRightOf) {
184             // Yes, so schedule a timer (unless one is already scheduled)
185             if (!mTimerPending) {
186                 mTimerPending = true;
187                 final Runnable timer = new Runnable() {
188                     @Override
189                     public void run() {
190                         mTimerPending = false;
191                         // Check whether the alignment is still different than the target
192                         // (since we may change back and forth repeatedly during the timeout)
193                         if (mBelow != mPendingBelow || mToRightOf != mPendingRight) {
194                             mBelow = mPendingBelow;
195                             mToRightOf = mPendingRight;
196                             mLastAlignmentTime = System.currentTimeMillis();
197                             if (mShell != null && mShell.isVisible()) {
198                                 update(text, mBelow, mToRightOf);
199                             }
200                         }
201                     }
202                 };
203                 mShell.getDisplay().timerExec(TIMEOUT_MS, timer);
204             }
205         }
206     }
207 
208     /** Hide the tooltip and dispose of any associated resources */
dispose()209     public void dispose() {
210         mShell.dispose();
211         mFont.dispose();
212 
213         mShell = null;
214         mFont = null;
215         mLabel = null;
216     }
217 }
218