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