1 /*
2 * Copyright (c) 2002, 2014, Oracle and/or its affiliates. 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package javax.swing.plaf.synth;
27
28 import javax.swing.*;
29 import java.awt.*;
30 import java.beans.*;
31 import javax.swing.plaf.*;
32 import javax.swing.plaf.basic.BasicButtonUI;
33 import javax.swing.plaf.basic.BasicHTML;
34 import javax.swing.text.View;
35
36 /**
37 * Provides the Synth L&F UI delegate for
38 * {@link javax.swing.JButton}.
39 *
40 * @author Scott Violet
41 * @since 1.7
42 */
43 public class SynthButtonUI extends BasicButtonUI implements
44 PropertyChangeListener, SynthUI {
45 private SynthStyle style;
46
47 /**
48 * Creates a new UI object for the given component.
49 *
50 * @param c component to create UI object for
51 * @return the UI object
52 */
53 public static ComponentUI createUI(JComponent c) {
54 return new SynthButtonUI();
55 }
56
57 /**
58 * {@inheritDoc}
59 */
60 @Override
61 protected void installDefaults(AbstractButton b) {
62 updateStyle(b);
63
64 LookAndFeel.installProperty(b, "rolloverEnabled", Boolean.TRUE);
65 }
66
67 /**
68 * {@inheritDoc}
69 */
70 @Override
71 protected void installListeners(AbstractButton b) {
72 super.installListeners(b);
73 b.addPropertyChangeListener(this);
74 }
75
76 void updateStyle(AbstractButton b) {
77 SynthContext context = getContext(b, SynthConstants.ENABLED);
78 SynthStyle oldStyle = style;
79 style = SynthLookAndFeel.updateStyle(context, this);
80 if (style != oldStyle) {
81 if (b.getMargin() == null ||
82 (b.getMargin() instanceof UIResource)) {
83 Insets margin = (Insets)style.get(context,getPropertyPrefix() +
84 "margin");
85
86 if (margin == null) {
87 // Some places assume margins are non-null.
88 margin = SynthLookAndFeel.EMPTY_UIRESOURCE_INSETS;
89 }
90 b.setMargin(margin);
91 }
92
93 Object value = style.get(context, getPropertyPrefix() + "iconTextGap");
94 if (value != null) {
95 LookAndFeel.installProperty(b, "iconTextGap", value);
96 }
97
98 value = style.get(context, getPropertyPrefix() + "contentAreaFilled");
99 LookAndFeel.installProperty(b, "contentAreaFilled",
100 value != null? value : Boolean.TRUE);
101
102 if (oldStyle != null) {
103 uninstallKeyboardActions(b);
104 installKeyboardActions(b);
105 }
106
107 }
108 context.dispose();
109 }
110
111 /**
112 * {@inheritDoc}
113 */
114 @Override
115 protected void uninstallListeners(AbstractButton b) {
116 super.uninstallListeners(b);
117 b.removePropertyChangeListener(this);
118 }
119
120 /**
121 * {@inheritDoc}
122 */
123 @Override
124 protected void uninstallDefaults(AbstractButton b) {
125 SynthContext context = getContext(b, ENABLED);
126
127 style.uninstallDefaults(context);
128 context.dispose();
129 style = null;
130 }
131
132 /**
133 * {@inheritDoc}
134 */
135 @Override
136 public SynthContext getContext(JComponent c) {
137 return getContext(c, getComponentState(c));
138 }
139
140 SynthContext getContext(JComponent c, int state) {
141 return SynthContext.getContext(c, style, state);
142 }
143
144 /**
145 * Returns the current state of the passed in {@code AbstractButton}.
146 */
147 private int getComponentState(JComponent c) {
148 int state = ENABLED;
149
150 if (!c.isEnabled()) {
151 state = DISABLED;
152 }
153 if (SynthLookAndFeel.getSelectedUI() == this) {
154 return SynthLookAndFeel.getSelectedUIState() | SynthConstants.ENABLED;
155 }
156 AbstractButton button = (AbstractButton) c;
157 ButtonModel model = button.getModel();
158
159 if (model.isPressed()) {
160 if (model.isArmed()) {
161 state = PRESSED;
162 }
163 else {
164 state = MOUSE_OVER;
165 }
166 }
167 if (model.isRollover()) {
168 state |= MOUSE_OVER;
169 }
170 if (model.isSelected()) {
171 state |= SELECTED;
172 }
173 if (c.isFocusOwner() && button.isFocusPainted()) {
174 state |= FOCUSED;
175 }
176 if ((c instanceof JButton) && ((JButton)c).isDefaultButton()) {
177 state |= DEFAULT;
178 }
179 return state;
180 }
181
182 /**
183 * {@inheritDoc}
184 */
185 @Override
186 public int getBaseline(JComponent c, int width, int height) {
187 if (c == null) {
188 throw new NullPointerException("Component must be non-null");
189 }
190 if (width < 0 || height < 0) {
191 throw new IllegalArgumentException(
192 "Width and height must be >= 0");
193 }
194 AbstractButton b = (AbstractButton)c;
195 String text = b.getText();
196 if (text == null || "".equals(text)) {
197 return -1;
198 }
199 Insets i = b.getInsets();
200 Rectangle viewRect = new Rectangle();
201 Rectangle textRect = new Rectangle();
202 Rectangle iconRect = new Rectangle();
203 viewRect.x = i.left;
204 viewRect.y = i.top;
205 viewRect.width = width - (i.right + viewRect.x);
206 viewRect.height = height - (i.bottom + viewRect.y);
207
208 // layout the text and icon
209 SynthContext context = getContext(b);
210 FontMetrics fm = context.getComponent().getFontMetrics(
211 context.getStyle().getFont(context));
212 context.getStyle().getGraphicsUtils(context).layoutText(
213 context, fm, b.getText(), b.getIcon(),
214 b.getHorizontalAlignment(), b.getVerticalAlignment(),
215 b.getHorizontalTextPosition(), b.getVerticalTextPosition(),
216 viewRect, iconRect, textRect, b.getIconTextGap());
217 View view = (View)b.getClientProperty(BasicHTML.propertyKey);
218 int baseline;
219 if (view != null) {
220 baseline = BasicHTML.getHTMLBaseline(view, textRect.width,
221 textRect.height);
222 if (baseline >= 0) {
223 baseline += textRect.y;
224 }
225 }
226 else {
227 baseline = textRect.y + fm.getAscent();
228 }
229 context.dispose();
230 return baseline;
231 }
232
233 // ********************************
234 // Paint Methods
235 // ********************************
236
237 /**
238 * Notifies this UI delegate to repaint the specified component.
239 * This method paints the component background, then calls
240 * the {@link #paint(SynthContext,Graphics)} method.
241 *
242 * <p>In general, this method does not need to be overridden by subclasses.
243 * All Look and Feel rendering code should reside in the {@code paint} method.
244 *
245 * @param g the {@code Graphics} object used for painting
246 * @param c the component being painted
247 * @see #paint(SynthContext,Graphics)
248 */
249 @Override
250 public void update(Graphics g, JComponent c) {
251 SynthContext context = getContext(c);
252
253 SynthLookAndFeel.update(context, g);
254 paintBackground(context, g, c);
255 paint(context, g);
256 context.dispose();
257 }
258
259 /**
260 * Paints the specified component according to the Look and Feel.
261 * <p>This method is not used by Synth Look and Feel.
262 * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
263 *
264 * @param g the {@code Graphics} object used for painting
265 * @param c the component being painted
266 * @see #paint(SynthContext,Graphics)
267 */
268 @Override
269 public void paint(Graphics g, JComponent c) {
270 SynthContext context = getContext(c);
271
272 paint(context, g);
273 context.dispose();
274 }
275
276 /**
277 * Paints the specified component.
278 *
279 * @param context context for the component being painted
280 * @param g the {@code Graphics} object used for painting
281 * @see #update(Graphics,JComponent)
282 */
283 protected void paint(SynthContext context, Graphics g) {
284 AbstractButton b = (AbstractButton)context.getComponent();
285
286 g.setColor(context.getStyle().getColor(context,
287 ColorType.TEXT_FOREGROUND));
288 g.setFont(style.getFont(context));
289 context.getStyle().getGraphicsUtils(context).paintText(
290 context, g, b.getText(), getIcon(b),
291 b.getHorizontalAlignment(), b.getVerticalAlignment(),
292 b.getHorizontalTextPosition(), b.getVerticalTextPosition(),
293 b.getIconTextGap(), b.getDisplayedMnemonicIndex(),
294 getTextShiftOffset(context));
295 }
296
297 void paintBackground(SynthContext context, Graphics g, JComponent c) {
298 if (((AbstractButton) c).isContentAreaFilled()) {
299 context.getPainter().paintButtonBackground(context, g, 0, 0,
300 c.getWidth(),
301 c.getHeight());
302 }
303 }
304
305 /**
306 * {@inheritDoc}
307 */
308 @Override
309 public void paintBorder(SynthContext context, Graphics g, int x,
310 int y, int w, int h) {
311 context.getPainter().paintButtonBorder(context, g, x, y, w, h);
312 }
313
314 /**
315 * Returns the default icon. This should not callback
316 * to the JComponent.
317 *
318 * @param b button the icon is associated with
319 * @return default icon
320 */
321 protected Icon getDefaultIcon(AbstractButton b) {
322 SynthContext context = getContext(b);
323 Icon icon = context.getStyle().getIcon(context, getPropertyPrefix() + "icon");
324 context.dispose();
325 return icon;
326 }
327
328 /**
329 * Returns the Icon to use for painting the button. The icon is chosen with
330 * respect to the current state of the button.
331 *
332 * @param b button the icon is associated with
333 * @return an icon
334 */
335 protected Icon getIcon(AbstractButton b) {
336 Icon icon = b.getIcon();
337 ButtonModel model = b.getModel();
338
339 if (!model.isEnabled()) {
340 icon = getSynthDisabledIcon(b, icon);
341 } else if (model.isPressed() && model.isArmed()) {
342 icon = getPressedIcon(b, getSelectedIcon(b, icon));
343 } else if (b.isRolloverEnabled() && model.isRollover()) {
344 icon = getRolloverIcon(b, getSelectedIcon(b, icon));
345 } else if (model.isSelected()) {
346 icon = getSelectedIcon(b, icon);
347 } else {
348 icon = getEnabledIcon(b, icon);
349 }
350 if(icon == null) {
351 return getDefaultIcon(b);
352 }
353 return icon;
354 }
355
356 /**
357 * This method will return the icon that should be used for a button. We
358 * only want to use the synth icon defined by the style if the specific
359 * icon has not been defined for the button state and the backup icon is a
360 * UIResource (we set it, not the developer).
361 *
362 * @param b button
363 * @param specificIcon icon returned from the button for the specific state
364 * @param defaultIcon fallback icon
365 * @param state the synth state of the button
366 */
367 private Icon getIcon(AbstractButton b, Icon specificIcon, Icon defaultIcon,
368 int state) {
369 Icon icon = specificIcon;
370 if (icon == null) {
371 if (defaultIcon instanceof UIResource) {
372 icon = getSynthIcon(b, state);
373 if (icon == null) {
374 icon = defaultIcon;
375 }
376 } else {
377 icon = defaultIcon;
378 }
379 }
380 return icon;
381 }
382
383 private Icon getSynthIcon(AbstractButton b, int synthConstant) {
384 return style.getIcon(getContext(b, synthConstant), getPropertyPrefix() + "icon");
385 }
386
387 private Icon getEnabledIcon(AbstractButton b, Icon defaultIcon) {
388 if (defaultIcon == null) {
389 defaultIcon = getSynthIcon(b, SynthConstants.ENABLED);
390 }
391 return defaultIcon;
392 }
393
394 private Icon getSelectedIcon(AbstractButton b, Icon defaultIcon) {
395 return getIcon(b, b.getSelectedIcon(), defaultIcon,
396 SynthConstants.SELECTED);
397 }
398
399 private Icon getRolloverIcon(AbstractButton b, Icon defaultIcon) {
400 ButtonModel model = b.getModel();
401 Icon icon;
402 if (model.isSelected()) {
403 icon = getIcon(b, b.getRolloverSelectedIcon(), defaultIcon,
404 SynthConstants.MOUSE_OVER | SynthConstants.SELECTED);
405 } else {
406 icon = getIcon(b, b.getRolloverIcon(), defaultIcon,
407 SynthConstants.MOUSE_OVER);
408 }
409 return icon;
410 }
411
412 private Icon getPressedIcon(AbstractButton b, Icon defaultIcon) {
413 return getIcon(b, b.getPressedIcon(), defaultIcon,
414 SynthConstants.PRESSED);
415 }
416
417 private Icon getSynthDisabledIcon(AbstractButton b, Icon defaultIcon) {
418 ButtonModel model = b.getModel();
419 Icon icon;
420 if (model.isSelected()) {
421 icon = getIcon(b, b.getDisabledSelectedIcon(), defaultIcon,
422 SynthConstants.DISABLED | SynthConstants.SELECTED);
423 } else {
424 icon = getIcon(b, b.getDisabledIcon(), defaultIcon,
425 SynthConstants.DISABLED);
426 }
427 return icon;
428 }
429
430 /**
431 * Returns the amount to shift the text/icon when painting.
432 */
433 private int getTextShiftOffset(SynthContext state) {
434 AbstractButton button = (AbstractButton)state.getComponent();
435 ButtonModel model = button.getModel();
436
437 if (model.isArmed() && model.isPressed() &&
438 button.getPressedIcon() == null) {
439 return state.getStyle().getInt(state, getPropertyPrefix() +
440 "textShiftOffset", 0);
441 }
442 return 0;
443 }
444
445 // ********************************
446 // Layout Methods
447 // ********************************
448
449 /**
450 * {@inheritDoc}
451 */
452 @Override
453 public Dimension getMinimumSize(JComponent c) {
454 if (c.getComponentCount() > 0 && c.getLayout() != null) {
455 return null;
456 }
457 AbstractButton b = (AbstractButton)c;
458 SynthContext ss = getContext(c);
459 Dimension size = ss.getStyle().getGraphicsUtils(ss).getMinimumSize(
460 ss, ss.getStyle().getFont(ss), b.getText(), getSizingIcon(b),
461 b.getHorizontalAlignment(), b.getVerticalAlignment(),
462 b.getHorizontalTextPosition(),
463 b.getVerticalTextPosition(), b.getIconTextGap(),
464 b.getDisplayedMnemonicIndex());
465
466 ss.dispose();
467 return size;
468 }
469
470 /**
471 * {@inheritDoc}
472 */
473 @Override
474 public Dimension getPreferredSize(JComponent c) {
475 if (c.getComponentCount() > 0 && c.getLayout() != null) {
476 return null;
477 }
478 AbstractButton b = (AbstractButton)c;
479 SynthContext ss = getContext(c);
480 Dimension size = ss.getStyle().getGraphicsUtils(ss).getPreferredSize(
481 ss, ss.getStyle().getFont(ss), b.getText(), getSizingIcon(b),
482 b.getHorizontalAlignment(), b.getVerticalAlignment(),
483 b.getHorizontalTextPosition(),
484 b.getVerticalTextPosition(), b.getIconTextGap(),
485 b.getDisplayedMnemonicIndex());
486
487 ss.dispose();
488 return size;
489 }
490
491 /**
492 * {@inheritDoc}
493 */
494 @Override
495 public Dimension getMaximumSize(JComponent c) {
496 if (c.getComponentCount() > 0 && c.getLayout() != null) {
497 return null;
498 }
499
500 AbstractButton b = (AbstractButton)c;
501 SynthContext ss = getContext(c);
502 Dimension size = ss.getStyle().getGraphicsUtils(ss).getMaximumSize(
503 ss, ss.getStyle().getFont(ss), b.getText(), getSizingIcon(b),
504 b.getHorizontalAlignment(), b.getVerticalAlignment(),
505 b.getHorizontalTextPosition(),
506 b.getVerticalTextPosition(), b.getIconTextGap(),
507 b.getDisplayedMnemonicIndex());
508
509 ss.dispose();
510 return size;
511 }
512
513 /**
514 * Returns the Icon used in calculating the
515 * preferred/minimum/maximum size.
516 *
517 * @param b specifies the {@code AbstractButton}
518 * used when calculating the preferred/minimum/maximum
519 * size.
520 *
521 * @return the Icon used in calculating the
522 * preferred/minimum/maximum size.
523 */
524 protected Icon getSizingIcon(AbstractButton b) {
525 Icon icon = getEnabledIcon(b, b.getIcon());
526 if (icon == null) {
527 icon = getDefaultIcon(b);
528 }
529 return icon;
530 }
531
532 /**
533 * {@inheritDoc}
534 */
535 @Override
536 public void propertyChange(PropertyChangeEvent e) {
537 if (SynthLookAndFeel.shouldUpdateStyle(e)) {
538 updateStyle((AbstractButton)e.getSource());
539 }
540 }
541 }
--- EOF ---