aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureToolTip.java
blob: a49e79cbf8524b1a965a3a640534eb421bf57f91 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.eclipse.org/org/documents/epl-v10.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.adt.internal.editors.layout.gle2;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

/**
 * A dedicated tooltip used during gestures, for example to show the resize dimensions.
 * <p>
 * This is necessary because {@link org.eclipse.jface.window.ToolTip} causes flicker when
 * used to dynamically update the position and text of the tip, and it does not seem to
 * have setter methods to update the text or position without recreating the tip.
 */
public class GestureToolTip {
    /** Minimum number of milliseconds to wait between alignment changes */
    private static final int TIMEOUT_MS = 750;

    /**
     * The alpha to use for the tooltip window (which sadly will apply to the tooltip text
     * as well.)
     */
    private static final int SHELL_TRANSPARENCY = 220;

    /** The size of the font displayed in the tooltip */
    private static final int FONT_SIZE = 9;

    /** Horizontal delta from the mouse cursor to shift the tooltip by */
    private static final int OFFSET_X = 20;

    /** Vertical delta from the mouse cursor to shift the tooltip by */
    private static final int OFFSET_Y = 20;

    /** The label which displays the tooltip */
    private CLabel mLabel;

    /** The shell holding the tooltip */
    private Shell mShell;

    /** The font shown in the label; held here such that it can be disposed of after use */
    private Font mFont;

    /** Is the tooltip positioned below the given anchor? */
    private boolean mBelow;

    /** Is the tooltip positioned to the right of the given anchor? */
    private boolean mToRightOf;

    /** Is an alignment change pending? */
    private boolean mTimerPending;

    /** The new value for {@link #mBelow} when the timer expires */
    private boolean mPendingBelow;

    /** The new value for {@link #mToRightOf} when the timer expires */
    private boolean mPendingRight;

    /** The time stamp (from {@link System#currentTimeMillis()} of the last alignment change */
    private long mLastAlignmentTime;

    /**
     * Creates a new tooltip over the given parent with the given relative position.
     *
     * @param parent the parent control
     * @param below if true, display the tooltip below the mouse cursor otherwise above
     * @param toRightOf if true, display the tooltip to the right of the mouse cursor,
     *            otherwise to the left
     */
    public GestureToolTip(Composite parent, boolean below, boolean toRightOf) {
        mBelow = below;
        mToRightOf = toRightOf;
        mLastAlignmentTime = System.currentTimeMillis();

        mShell = new Shell(parent.getShell(), SWT.ON_TOP | SWT.TOOL | SWT.NO_FOCUS);
        mShell.setLayout(new FillLayout());
        mShell.setAlpha(SHELL_TRANSPARENCY);

        Display display = parent.getDisplay();
        mLabel = new CLabel(mShell, SWT.SHADOW_NONE);
        mLabel.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
        mLabel.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND));

        Font systemFont = display.getSystemFont();
        FontData[] fd = systemFont.getFontData();
        for (int i = 0; i < fd.length; i++) {
            fd[i].setHeight(FONT_SIZE);
        }
        mFont = new Font(display, fd);
        mLabel.setFont(mFont);

        mShell.setVisible(false);
    }

    /**
     * Show the tooltip at the given position and with the given text. Note that the
     * position may not be applied immediately; to prevent flicker alignment changes
     * are queued up with a timer (unless it's been a while since the last change, in
     * which case the update is applied immediately.)
     *
     * @param text the new text to be displayed
     * @param below if true, display the tooltip below the mouse cursor otherwise above
     * @param toRightOf if true, display the tooltip to the right of the mouse cursor,
     *            otherwise to the left
     */
    public void update(final String text, boolean below, boolean toRightOf) {
        // If the alignment has not changed recently, just apply the change immediately
        // instead of within a delay
        if (!mTimerPending && (below != mBelow || toRightOf != mToRightOf)
                && (System.currentTimeMillis() - mLastAlignmentTime >= TIMEOUT_MS)) {
            mBelow = below;
            mToRightOf = toRightOf;
            mLastAlignmentTime = System.currentTimeMillis();
        }

        Point location = mShell.getDisplay().getCursorLocation();

        mLabel.setText(text);

        // Pack the label to its minimum size -- unless we are positioning the tooltip
        // on the left. Because of the way SWT works (at least on the OSX) this sometimes
        // creates flicker, because when we switch to a longer string (such as when
        // switching from "52dp" to "wrap_content" during a resize) the window size will
        // change first, and then the location will update later - so there will be a
        // brief flash of the longer label before it is moved to the right position on the
        // left. To work around this, we simply pass false to pack such that it will reuse
        // its cached size, which in practice means that for labels on the right, the
        // label will grow but not shrink.
        // This workaround is disabled because it doesn't work well in Eclipse 3.5; the
        // labels don't grow when they should. Re-enable when we drop 3.5 support.
        //boolean changed = mToRightOf;
        boolean changed = true;

        mShell.pack(changed);
        Point size = mShell.getSize();

        // Position the tooltip to the left or right, and above or below, according
        // to the saved state of these flags, not the current parameters. We don't want
        // to flicker, instead we react on a timer to changes in alignment below.
        if (mBelow) {
            location.y += OFFSET_Y;
        } else {
            location.y -= OFFSET_Y;
            location.y -= size.y;
        }

        if (mToRightOf) {
            location.x += OFFSET_X;
        } else {
            location.x -= OFFSET_X;
            location.x -= size.x;
        }

        mShell.setLocation(location);

        if (!mShell.isVisible()) {
            mShell.setVisible(true);
        }

        // Has the orientation changed?
        mPendingBelow = below;
        mPendingRight = toRightOf;
        if (below != mBelow || toRightOf != mToRightOf) {
            // Yes, so schedule a timer (unless one is already scheduled)
            if (!mTimerPending) {
                mTimerPending = true;
                final Runnable timer = new Runnable() {
                    @Override
                    public void run() {
                        mTimerPending = false;
                        // Check whether the alignment is still different than the target
                        // (since we may change back and forth repeatedly during the timeout)
                        if (mBelow != mPendingBelow || mToRightOf != mPendingRight) {
                            mBelow = mPendingBelow;
                            mToRightOf = mPendingRight;
                            mLastAlignmentTime = System.currentTimeMillis();
                            if (mShell != null && mShell.isVisible()) {
                                update(text, mBelow, mToRightOf);
                            }
                        }
                    }
                };
                mShell.getDisplay().timerExec(TIMEOUT_MS, timer);
            }
        }
    }

    /** Hide the tooltip and dispose of any associated resources */
    public void dispose() {
        mShell.dispose();
        mFont.dispose();

        mShell = null;
        mFont = null;
        mLabel = null;
    }
}