Home | History | Annotate | Download | only in gle2
      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      */
     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      */
    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 */
    209     public void dispose() {
    210         mShell.dispose();
    211         mFont.dispose();
    212 
    213         mShell = null;
    214         mFont = null;
    215         mLabel = null;
    216     }
    217 }
    218