1 /*
2 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package javax.swing.plaf.basic;
27
28 import sun.swing.SwingUtilities2;
29 import sun.swing.DefaultLookup;
30 import sun.swing.UIAction;
31 import javax.swing;
32 import javax.swing.plaf;
33 import javax.swing.text.View;
34
35 import java.awt.event.ActionEvent;
36 import java.awt.event.ActionListener;
37 import java.awt.event.KeyEvent;
38 import java.awt.Component;
39 import java.awt.Container;
40 import java.awt.Dimension;
41 import java.awt.Rectangle;
42 import java.awt.Insets;
43 import java.awt.Color;
44 import java.awt.Graphics;
45 import java.awt.Font;
46 import java.awt.FontMetrics;
47 import java.beans.PropertyChangeEvent;
48 import java.beans.PropertyChangeListener;
49
50 /**
51 * A Windows L&F implementation of LabelUI. This implementation
52 * is completely static, i.e. there's only one UIView implementation
53 * that's shared by all JLabel objects.
54 *
55 * @author Hans Muller
56 */
57 public class BasicLabelUI extends LabelUI implements PropertyChangeListener
58 {
59 /**
60 * The default <code>BasicLabelUI</code> instance. This field might
61 * not be used. To change the default instance use a subclass which
62 * overrides the <code>createUI</code> method, and place that class
63 * name in defaults table under the key "LabelUI".
64 */
65 protected static BasicLabelUI labelUI = new BasicLabelUI();
66 private final static BasicLabelUI SAFE_BASIC_LABEL_UI = new BasicLabelUI();
67
68 static void loadActionMap(LazyActionMap map) {
69 map.put(new Actions(Actions.PRESS));
70 map.put(new Actions(Actions.RELEASE));
71 }
72
73 /**
74 * Forwards the call to SwingUtilities.layoutCompoundLabel().
75 * This method is here so that a subclass could do Label specific
76 * layout and to shorten the method name a little.
77 *
78 * @see SwingUtilities#layoutCompoundLabel
79 */
80 protected String layoutCL(
81 JLabel label,
82 FontMetrics fontMetrics,
83 String text,
84 Icon icon,
85 Rectangle viewR,
86 Rectangle iconR,
87 Rectangle textR)
88 {
89 return SwingUtilities.layoutCompoundLabel(
90 (JComponent) label,
91 fontMetrics,
92 text,
93 icon,
94 label.getVerticalAlignment(),
95 label.getHorizontalAlignment(),
96 label.getVerticalTextPosition(),
97 label.getHorizontalTextPosition(),
98 viewR,
99 iconR,
100 textR,
101 label.getIconTextGap());
102 }
103
104 /**
105 * Paint clippedText at textX, textY with the labels foreground color.
106 *
107 * @see #paint
108 * @see #paintDisabledText
109 */
110 protected void paintEnabledText(JLabel l, Graphics g, String s, int textX, int textY)
111 {
112 int mnemIndex = l.getDisplayedMnemonicIndex();
113 g.setColor(l.getForeground());
114 SwingUtilities2.drawStringUnderlineCharAt(l, g, s, mnemIndex,
115 textX, textY);
116 }
117
118
119 /**
120 * Paint clippedText at textX, textY with background.lighter() and then
121 * shifted down and to the right by one pixel with background.darker().
122 *
123 * @see #paint
124 * @see #paintEnabledText
125 */
126 protected void paintDisabledText(JLabel l, Graphics g, String s, int textX, int textY)
127 {
128 int accChar = l.getDisplayedMnemonicIndex();
129 Color background = l.getBackground();
130 g.setColor(background.brighter());
131 SwingUtilities2.drawStringUnderlineCharAt(l, g, s, accChar,
132 textX + 1, textY + 1);
133 g.setColor(background.darker());
134 SwingUtilities2.drawStringUnderlineCharAt(l, g, s, accChar,
135 textX, textY);
136 }
137
138
139 /* These rectangles/insets are allocated once for this shared LabelUI
140 * implementation. Re-using rectangles rather than allocating
141 * them in each paint call halved the time it took paint to run.
142 */
143 private static Rectangle paintIconR = new Rectangle();
144 private static Rectangle paintTextR = new Rectangle();
145 private static Rectangle paintViewR = new Rectangle();
146 private static Insets paintViewInsets = new Insets(0, 0, 0, 0);
147
148
149 /**
150 * Paint the label text in the foreground color, if the label
151 * is opaque then paint the entire background with the background
152 * color. The Label text is drawn by paintEnabledText() or
153 * paintDisabledText(). The locations of the label parts are computed
154 * by layoutCL.
155 *
156 * @see #paintEnabledText
157 * @see #paintDisabledText
158 * @see #layoutCL
159 */
160 public void paint(Graphics g, JComponent c)
161 {
162 JLabel label = (JLabel)c;
163 String text = label.getText();
164 Icon icon = (label.isEnabled()) ? label.getIcon() : label.getDisabledIcon();
165
166 if ((icon == null) && (text == null)) {
167 return;
168 }
169
170 FontMetrics fm = SwingUtilities2.getFontMetrics(label, g);
171 String clippedText = layout(label, fm, c.getWidth(), c.getHeight());
172
173 if (icon != null) {
174 icon.paintIcon(c, g, paintIconR.x, paintIconR.y);
175 }
176
177 if (text != null) {
178 View v = (View) c.getClientProperty(BasicHTML.propertyKey);
179 if (v != null) {
180 v.paint(g, paintTextR);
181 } else {
182 int textX = paintTextR.x;
183 int textY = paintTextR.y + fm.getAscent();
184
185 if (label.isEnabled()) {
186 paintEnabledText(label, g, clippedText, textX, textY);
187 }
188 else {
189 paintDisabledText(label, g, clippedText, textX, textY);
190 }
191 }
192 }
193 }
194
195 private String layout(JLabel label, FontMetrics fm,
196 int width, int height) {
197 Insets insets = label.getInsets(paintViewInsets);
198 String text = label.getText();
199 Icon icon = (label.isEnabled()) ? label.getIcon() :
200 label.getDisabledIcon();
201 paintViewR.x = insets.left;
202 paintViewR.y = insets.top;
203 paintViewR.width = width - (insets.left + insets.right);
204 paintViewR.height = height - (insets.top + insets.bottom);
205 paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0;
206 paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0;
207 return layoutCL(label, fm, text, icon, paintViewR, paintIconR,
208 paintTextR);
209 }
210
211
212 /* These rectangles/insets are allocated once for this shared LabelUI
213 * implementation. Re-using rectangles rather than allocating
214 * them in each getPreferredSize call sped up the method substantially.
215 */
216 private static Rectangle iconR = new Rectangle();
217 private static Rectangle textR = new Rectangle();
218 private static Rectangle viewR = new Rectangle();
219 private static Insets viewInsets = new Insets(0, 0, 0, 0);
220
221
222 public Dimension getPreferredSize(JComponent c)
223 {
224 JLabel label = (JLabel)c;
225 String text = label.getText();
226 Icon icon = (label.isEnabled()) ? label.getIcon() :
227 label.getDisabledIcon();
228 Insets insets = label.getInsets(viewInsets);
229 Font font = label.getFont();
230
231 int dx = insets.left + insets.right;
232 int dy = insets.top + insets.bottom;
233
234 if ((icon == null) &&
235 ((text == null) ||
236 ((text != null) && (font == null)))) {
237 return new Dimension(dx, dy);
238 }
239 else if ((text == null) || ((icon != null) && (font == null))) {
240 return new Dimension(icon.getIconWidth() + dx,
241 icon.getIconHeight() + dy);
242 }
243 else {
244 FontMetrics fm = label.getFontMetrics(font);
245
246 iconR.x = iconR.y = iconR.width = iconR.height = 0;
247 textR.x = textR.y = textR.width = textR.height = 0;
248 viewR.x = dx;
249 viewR.y = dy;
250 viewR.width = viewR.height = Short.MAX_VALUE;
251
252 layoutCL(label, fm, text, icon, viewR, iconR, textR);
253 int x1 = Math.min(iconR.x, textR.x);
254 int x2 = Math.max(iconR.x + iconR.width, textR.x + textR.width);
255 int y1 = Math.min(iconR.y, textR.y);
256 int y2 = Math.max(iconR.y + iconR.height, textR.y + textR.height);
257 Dimension rv = new Dimension(x2 - x1, y2 - y1);
258
259 rv.width += dx;
260 rv.height += dy;
261 return rv;
262 }
263 }
264
265
266 /**
267 * @return getPreferredSize(c)
268 */
269 public Dimension getMinimumSize(JComponent c) {
270 Dimension d = getPreferredSize(c);
271 View v = (View) c.getClientProperty(BasicHTML.propertyKey);
272 if (v != null) {
273 d.width -= v.getPreferredSpan(View.X_AXIS) - v.getMinimumSpan(View.X_AXIS);
274 }
275 return d;
276 }
277
278 /**
279 * @return getPreferredSize(c)
280 */
281 public Dimension getMaximumSize(JComponent c) {
282 Dimension d = getPreferredSize(c);
283 View v = (View) c.getClientProperty(BasicHTML.propertyKey);
284 if (v != null) {
285 d.width += v.getMaximumSpan(View.X_AXIS) - v.getPreferredSpan(View.X_AXIS);
286 }
287 return d;
288 }
289
290 /**
291 * Returns the baseline.
292 *
293 * @throws NullPointerException {@inheritDoc}
294 * @throws IllegalArgumentException {@inheritDoc}
295 * @see javax.swing.JComponent#getBaseline(int, int)
296 * @since 1.6
297 */
298 public int getBaseline(JComponent c, int width, int height) {
299 super.getBaseline(c, width, height);
300 JLabel label = (JLabel)c;
301 String text = label.getText();
302 if (text == null || "".equals(text) || label.getFont() == null) {
303 return -1;
304 }
305 FontMetrics fm = label.getFontMetrics(label.getFont());
306 layout(label, fm, width, height);
307 return BasicHTML.getBaseline(label, paintTextR.y, fm.getAscent(),
308 paintTextR.width, paintTextR.height);
309 }
310
311 /**
312 * Returns an enum indicating how the baseline of the component
313 * changes as the size changes.
314 *
315 * @throws NullPointerException {@inheritDoc}
316 * @see javax.swing.JComponent#getBaseline(int, int)
317 * @since 1.6
318 */
319 public Component.BaselineResizeBehavior getBaselineResizeBehavior(
320 JComponent c) {
321 super.getBaselineResizeBehavior(c);
322 if (c.getClientProperty(BasicHTML.propertyKey) != null) {
323 return Component.BaselineResizeBehavior.OTHER;
324 }
325 switch(((JLabel)c).getVerticalAlignment()) {
326 case JLabel.TOP:
327 return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
328 case JLabel.BOTTOM:
329 return Component.BaselineResizeBehavior.CONSTANT_DESCENT;
330 case JLabel.CENTER:
331 return Component.BaselineResizeBehavior.CENTER_OFFSET;
332 }
333 return Component.BaselineResizeBehavior.OTHER;
334 }
335
336
337 public void installUI(JComponent c) {
338 installDefaults((JLabel)c);
339 installComponents((JLabel)c);
340 installListeners((JLabel)c);
341 installKeyboardActions((JLabel)c);
342 }
343
344
345 public void uninstallUI(JComponent c) {
346 uninstallDefaults((JLabel)c);
347 uninstallComponents((JLabel)c);
348 uninstallListeners((JLabel)c);
349 uninstallKeyboardActions((JLabel)c);
350 }
351
352 protected void installDefaults(JLabel c){
353 LookAndFeel.installColorsAndFont(c, "Label.background", "Label.foreground", "Label.font");
354 LookAndFeel.installProperty(c, "opaque", Boolean.FALSE);
355 }
356
357 protected void installListeners(JLabel c){
358 c.addPropertyChangeListener(this);
359 }
360
361 protected void installComponents(JLabel c){
362 BasicHTML.updateRenderer(c, c.getText());
363 c.setInheritsPopupMenu(true);
364 }
365
366 protected void installKeyboardActions(JLabel l) {
367 int dka = l.getDisplayedMnemonic();
368 Component lf = l.getLabelFor();
369 if ((dka != 0) && (lf != null)) {
370 LazyActionMap.installLazyActionMap(l, BasicLabelUI.class,
371 "Label.actionMap");
372 InputMap inputMap = SwingUtilities.getUIInputMap
373 (l, JComponent.WHEN_IN_FOCUSED_WINDOW);
374 if (inputMap == null) {
375 inputMap = new ComponentInputMapUIResource(l);
376 SwingUtilities.replaceUIInputMap(l,
377 JComponent.WHEN_IN_FOCUSED_WINDOW, inputMap);
378 }
379 inputMap.clear();
380 inputMap.put(KeyStroke.getKeyStroke(dka, ActionEvent.ALT_MASK,
381 false), "press");
382 }
383 else {
384 InputMap inputMap = SwingUtilities.getUIInputMap
385 (l, JComponent.WHEN_IN_FOCUSED_WINDOW);
386 if (inputMap != null) {
387 inputMap.clear();
388 }
389 }
390 }
391
392 protected void uninstallDefaults(JLabel c){
393 }
394
395 protected void uninstallListeners(JLabel c){
396 c.removePropertyChangeListener(this);
397 }
398
399 protected void uninstallComponents(JLabel c){
400 BasicHTML.updateRenderer(c, "");
401 }
402
403 protected void uninstallKeyboardActions(JLabel c) {
404 SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, null);
405 SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_IN_FOCUSED_WINDOW,
406 null);
407 SwingUtilities.replaceUIActionMap(c, null);
408 }
409
410 public static ComponentUI createUI(JComponent c) {
411 if (System.getSecurityManager() != null) {
412 return SAFE_BASIC_LABEL_UI;
413 } else {
414 return labelUI;
415 }
416 }
417
418 public void propertyChange(PropertyChangeEvent e) {
419 String name = e.getPropertyName();
420 if (name == "text" || "font" == name || "foreground" == name) {
421 // remove the old html view client property if one
422 // existed, and install a new one if the text installed
423 // into the JLabel is html source.
424 JLabel lbl = ((JLabel) e.getSource());
425 String text = lbl.getText();
426 BasicHTML.updateRenderer(lbl, text);
427 }
428 else if (name == "labelFor" || name == "displayedMnemonic") {
429 installKeyboardActions((JLabel) e.getSource());
430 }
431 }
432
433 // When the accelerator is pressed, temporarily make the JLabel
434 // focusTraversable by registering a WHEN_FOCUSED action for the
435 // release of the accelerator. Then give it focus so it can
436 // prevent unwanted keyTyped events from getting to other components.
437 private static class Actions extends UIAction {
438 private static final String PRESS = "press";
439 private static final String RELEASE = "release";
440
441 Actions(String key) {
442 super(key);
443 }
444
445 public void actionPerformed(ActionEvent e) {
446 JLabel label = (JLabel)e.getSource();
447 String key = getName();
448 if (key == PRESS) {
449 doPress(label);
450 }
451 else if (key == RELEASE) {
452 doRelease(label);
453 }
454 }
455
456 private void doPress(JLabel label) {
457 Component labelFor = label.getLabelFor();
458 if (labelFor != null && labelFor.isEnabled()) {
459 InputMap inputMap = SwingUtilities.getUIInputMap(label, JComponent.WHEN_FOCUSED);
460 if (inputMap == null) {
461 inputMap = new InputMapUIResource();
462 SwingUtilities.replaceUIInputMap(label, JComponent.WHEN_FOCUSED, inputMap);
463 }
464 int dka = label.getDisplayedMnemonic();
465 inputMap.put(KeyStroke.getKeyStroke(dka, ActionEvent.ALT_MASK, true), RELEASE);
466 // Need this if ALT is released before the accelerator
467 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ALT, 0, true), RELEASE);
468 label.requestFocus();
469 }
470 }
471
472 private void doRelease(JLabel label) {
473 Component labelFor = label.getLabelFor();
474 if (labelFor != null && labelFor.isEnabled()) {
475 InputMap inputMap = SwingUtilities.getUIInputMap(label, JComponent.WHEN_FOCUSED);
476 if (inputMap != null) {
477 // inputMap should never be null.
478 inputMap.remove(KeyStroke.getKeyStroke(label.getDisplayedMnemonic(), ActionEvent.ALT_MASK, true));
479 inputMap.remove(KeyStroke.getKeyStroke(KeyEvent.VK_ALT, 0, true));
480 }
481 if (labelFor instanceof Container &&
482 ((Container) labelFor).isFocusCycleRoot()) {
483 labelFor.requestFocus();
484 } else {
485 SwingUtilities2.compositeRequestFocus(labelFor);
486 }
487 }
488 }
489 }
490 }