Source code: com/flexstor/common/awt/ToolTip.java
1 /*
2 * ToolTip.java
3 *
4 * Copyright $Date: 2003/08/11 02:22:32 $ FLEXSTOR.net Inc.
5 *
6 * This work is licensed for use and distribution under license terms found at
7 * http://www.flexstor.org/license.html
8 *
9 */
10
11 package com.flexstor.common.awt;
12
13 import java.awt.Color;
14 import java.awt.Font;
15 import java.awt.FontMetrics;
16 import java.awt.Frame;
17 import java.awt.Graphics;
18 import java.util.Vector;
19
20 /**
21 * This class is used for displaying tool tips.
22 * It is a window without title bar nor border.
23 * The paint method draws directly on the graphic context.
24 */
25 public class ToolTip
26 extends java.awt.Window
27 //implements FocusListener
28 {
29 static final protected int MAX_WIDTH = 300;
30 static final protected int INSETS_X = 2;
31 static final protected Color TIP_COLOR = new Color(16447424);
32 static protected int CURSOR_OFFSET = -2; // default
33
34 String sText = null;
35 FontMetrics fm = null;
36 Vector vTextLines = new Vector();
37
38 public ToolTip(Frame f)
39 {
40 super(f);
41 setLayout(null);
42 setBackground(TIP_COLOR);
43 //addFocusListener(this);
44 //setEnabled(false);
45 }
46 /*
47 public void requestFocus()
48 {
49 }
50 */
51 /**
52 * Gets the default font and reduces it by 1 (one).
53 * Gets the font metrics for the tool tip font.
54 * Analyzes text and enforces location before first show.
55 */
56 public void addNotify()
57 {
58 Font tipFont;
59
60 super.addNotify();
61
62 //System.out.println("Original font: " + getFont());
63 int nFontSize = Math.max(9, getFont().getSize() - 1);
64 //System.out.println("New FontSize: " + nFontSize);
65 tipFont = new Font(getFont().getName(), getFont().getStyle(), nFontSize);
66 setFont(tipFont);
67 //System.out.println("Set font: " + tipFont);
68 fm = getFontMetrics(tipFont);
69 CURSOR_OFFSET = fm.getHeight() / -2;
70 vTextLines = analyze(sText);
71 // call this.setLocation to enforce on-screen display for first show
72 setLocation(getLocation().x, getLocation().y - CURSOR_OFFSET);
73 }
74
75 // Must be true to set focus to the tool tip
76 /*
77 public boolean isFocusTraversable()
78 {
79 return false;
80 }
81 */
82
83 /**
84 * To work properly, the instance MUST get focus when
85 * set to visible.
86 */
87 /*
88 public void focusLost(FocusEvent e)
89 {
90 //System.out.println(this + " focusLost");
91 setVisible(false);
92 }
93 */
94
95 /**
96 * Implements FocusListener
97 */
98 /*
99 public void focusGained(FocusEvent e)
100 {
101 //System.out.println(this + " focusGained");
102 }
103 */
104
105 /**
106 */
107 public void paint(Graphics g)
108 {
109 int i;
110 int nSize = vTextLines.size();
111
112 int y = fm.getMaxAscent();
113
114 for (i = 0; i < nSize; i++)
115 {
116 g.drawString((String)vTextLines.elementAt(i), INSETS_X, y);
117 y += fm.getHeight();
118 }
119 //super.paint(g); // not needed, just paints children which this class does not have
120 g.drawRect(0, 0, getSize().width - 1, getSize().height - 1);
121 }
122
123 /**
124 * This methods is modified from Symantec's WrappingLabel.
125 * It splits up the string into lines based on the font's metrics
126 * which are set in addNotify.
127 * The constant MAX_WIDTH specifies the maximum width in pixels for this window.
128 * @param SText the string to analyze
129 * @return a vector with each element representing a line of text
130 */
131 protected Vector analyze(String sText)
132 {
133 Vector vText = new Vector();
134
135 //if (fm == null)
136 // System.out.println("fm == null");
137
138 if (sText != null && fm != null)
139 {
140 int y; // keeps track of height
141 int fromIndex = 0;
142 int pos = 0;
143 int bestpos;
144 String largestString;
145 String s;
146
147 y = fm.getMaxAscent();
148
149 // set max width
150 int nWidth = fm.stringWidth(sText) + 4; // sum of individual characters != stringWidth
151 if (nWidth > MAX_WIDTH)
152 {
153 nWidth = MAX_WIDTH;
154 }
155
156 // While we haven't passed the bottom of the label and we
157 // haven't run past the end of the string...
158 while (fromIndex != -1)
159 {
160 // Automatically skip any spaces at the beginning of the line
161 while (fromIndex < sText.length() && sText.charAt(fromIndex) == ' ')
162 {
163 ++fromIndex;
164 // If we hit the end of line while skipping spaces, we're done.
165 if (fromIndex >= sText.length()) break;
166 }
167
168 // fromIndex represents the beginning of the line
169 pos = fromIndex;
170 bestpos = -1;
171 largestString = null;
172
173 while (pos >= fromIndex)
174 {
175 pos = sText.indexOf(' ', pos+1);
176
177 // Couldn't find another space?
178 if (pos == -1)
179 {
180 s = sText.substring(fromIndex);
181 }
182 else
183 {
184 s = sText.substring(fromIndex, pos);
185 }
186
187 // If the string fits, keep track of it.
188 if (fm.stringWidth(s) < nWidth)
189 {
190 largestString = s;
191 bestpos = pos;
192
193 // If we've hit the end of the string, use it.
194 if (pos == -1) break;
195 }
196 else
197 {
198 break;
199 }
200
201 ++pos;
202 }
203
204 if (largestString == null)
205 {
206 // Couldn't wrap at a space, so find the largest line
207 // that fits and print that. Note that this will be
208 // slightly off -- the width of a string will not necessarily
209 // be the sum of the width of its characters, due to kerning.
210 int totalWidth = 0;
211 int oneCharWidth = 0;
212
213 pos = fromIndex;
214
215 while (pos < sText.length())
216 {
217 oneCharWidth = fm.charWidth(sText.charAt(pos));
218 if ((totalWidth + oneCharWidth) >= nWidth) break;
219 totalWidth += oneCharWidth;
220 ++pos;
221 }
222
223 //g.drawString(sText.substring(fromIndex, pos), x, y);
224
225 vText.addElement(sText.substring(fromIndex, pos));
226
227 fromIndex = pos;
228 }
229 else
230 {
231 //g.drawString(largestString, x, y);
232
233 vText.addElement(largestString);
234
235 fromIndex = bestpos;
236 }
237 y += fm.getHeight();
238 }
239
240 // correct y for last line
241 y -= fm.getHeight();
242
243 setSize(nWidth + 2 * INSETS_X, y + fm.getMaxDescent() + 1);
244 }
245
246 return vText;
247 }
248
249 /**
250 * Sets the text of the tool tip
251 * @param sTip the new tip text
252 */
253 public void setText(String sTip)
254 {
255 if (sTip.equals(sText))
256 return;
257
258 sText = sTip;
259 if (fm != null)
260 {
261 vTextLines = analyze(sText);
262 /*
263 for (int i = 0; i < vTextLines.size(); i++)
264 {
265 System.out.println("[" + i + "]" + vTextLines.elementAt(i));
266 }
267 */
268 }
269 }
270
271 public void setVisible(boolean bShow)
272 {
273 //setLocation(0, -500);
274 super.setVisible(bShow);
275 if (bShow)
276 {
277 toFront();
278 //requestFocus(); // this call prevents it from working in Win95
279 // evidently something is screwed up!
280 }
281 }
282
283 /**
284 * This method ensures that the tooltip is completely on screen by
285 * modifying the x,y coordinates before calling super.setLocation.
286 */
287 public void setLocation(int x, int y)
288 {
289
290 y += CURSOR_OFFSET;
291
292 // ensure that tooltip is always on screen
293 int dx = x + getSize().width - getToolkit().getScreenSize().width;
294 int dy = y + getSize().height - getToolkit().getScreenSize().height;
295 if (dx > 0)
296 {
297 x -= dx;
298 }
299 if (dy > 0)
300 {
301 y -= (2 * CURSOR_OFFSET + getSize().height);
302 }
303
304 super.setLocation(x, y);
305 }
306 }