blob: a49e79cbf8524b1a965a3a640534eb421bf57f91 [file] [log] [blame]
/*
* 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;
}
}