1 /* 2 * Copyright (c) 2002, 2010, 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 Region region = SynthLookAndFeel.getRegion(c); 142 return SynthContext.getContext(SynthContext.class, c, region, 143 style, state); 144 } 145 146 /** 147 * Returns the current state of the passed in <code>AbstractButton</code>. 148 */ 149 private int getComponentState(JComponent c) { 150 int state = ENABLED; 151 152 if (!c.isEnabled()) { 153 state = DISABLED; 154 } 155 if (SynthLookAndFeel.selectedUI == this) { 156 return SynthLookAndFeel.selectedUIState | SynthConstants.ENABLED; 157 } 158 AbstractButton button = (AbstractButton) c; 159 ButtonModel model = button.getModel(); 160 161 if (model.isPressed()) { 162 if (model.isArmed()) { 163 state = PRESSED; 164 } 165 else { 166 state = MOUSE_OVER; 167 } 168 } 169 if (model.isRollover()) { 170 state |= MOUSE_OVER; 171 } 172 if (model.isSelected()) { 173 state |= SELECTED; 174 } 175 if (c.isFocusOwner() && button.isFocusPainted()) { 176 state |= FOCUSED; 177 } 178 if ((c instanceof JButton) && ((JButton)c).isDefaultButton()) { 179 state |= DEFAULT; 180 } 181 return state; 182 } 183 184 /** 185 * @inheritDoc 186 */ 187 @Override 188 public int getBaseline(JComponent c, int width, int height) { 189 if (c == null) { 190 throw new NullPointerException("Component must be non-null"); 191 } 192 if (width < 0 || height < 0) { 193 throw new IllegalArgumentException( 194 "Width and height must be >= 0"); 195 } 196 AbstractButton b = (AbstractButton)c; 197 String text = b.getText(); 198 if (text == null || "".equals(text)) { 199 return -1; 200 } 201 Insets i = b.getInsets(); 202 Rectangle viewRect = new Rectangle(); 203 Rectangle textRect = new Rectangle(); 204 Rectangle iconRect = new Rectangle(); 205 viewRect.x = i.left; 206 viewRect.y = i.top; 207 viewRect.width = width - (i.right + viewRect.x); 208 viewRect.height = height - (i.bottom + viewRect.y); 209 210 // layout the text and icon 211 SynthContext context = getContext(b); 212 FontMetrics fm = context.getComponent().getFontMetrics( 213 context.getStyle().getFont(context)); 214 context.getStyle().getGraphicsUtils(context).layoutText( 215 context, fm, b.getText(), b.getIcon(), 216 b.getHorizontalAlignment(), b.getVerticalAlignment(), 217 b.getHorizontalTextPosition(), b.getVerticalTextPosition(), 218 viewRect, iconRect, textRect, b.getIconTextGap()); 219 View view = (View)b.getClientProperty(BasicHTML.propertyKey); 220 int baseline; 221 if (view != null) { 222 baseline = BasicHTML.getHTMLBaseline(view, textRect.width, 223 textRect.height); 224 if (baseline >= 0) { 225 baseline += textRect.y; 226 } 227 } 228 else { 229 baseline = textRect.y + fm.getAscent(); 230 } 231 context.dispose(); 232 return baseline; 233 } 234 235 // ******************************** 236 // Paint Methods 237 // ******************************** 238 239 /** 240 * Notifies this UI delegate to repaint the specified component. 241 * This method paints the component background, then calls 242 * the {@link #paint(SynthContext,Graphics)} method. 243 * 244 * <p>In general, this method does not need to be overridden by subclasses. 245 * All Look and Feel rendering code should reside in the {@code paint} method. 246 * 247 * @param g the {@code Graphics} object used for painting 248 * @param c the component being painted 249 * @see #paint(SynthContext,Graphics) 250 */ 251 @Override 252 public void update(Graphics g, JComponent c) { 253 SynthContext context = getContext(c); 254 255 SynthLookAndFeel.update(context, g); 256 paintBackground(context, g, c); 257 paint(context, g); 258 context.dispose(); 259 } 260 261 /** 262 * Paints the specified component according to the Look and Feel. 263 * <p>This method is not used by Synth Look and Feel. 264 * Painting is handled by the {@link #paint(SynthContext,Graphics)} method. 265 * 266 * @param g the {@code Graphics} object used for painting 267 * @param c the component being painted 268 * @see #paint(SynthContext,Graphics) 269 */ 270 @Override 271 public void paint(Graphics g, JComponent c) { 272 SynthContext context = getContext(c); 273 274 paint(context, g); 275 context.dispose(); 276 } 277 278 /** 279 * Paints the specified component. 280 * 281 * @param context context for the component being painted 282 * @param g the {@code Graphics} object used for painting 283 * @see #update(Graphics,JComponent) 284 */ 285 protected void paint(SynthContext context, Graphics g) { 286 AbstractButton b = (AbstractButton)context.getComponent(); 287 288 g.setColor(context.getStyle().getColor(context, 289 ColorType.TEXT_FOREGROUND)); 290 g.setFont(style.getFont(context)); 291 context.getStyle().getGraphicsUtils(context).paintText( 292 context, g, b.getText(), getIcon(b), 293 b.getHorizontalAlignment(), b.getVerticalAlignment(), 294 b.getHorizontalTextPosition(), b.getVerticalTextPosition(), 295 b.getIconTextGap(), b.getDisplayedMnemonicIndex(), 296 getTextShiftOffset(context)); 297 } 298 299 void paintBackground(SynthContext context, Graphics g, JComponent c) { 300 if (((AbstractButton) c).isContentAreaFilled()) { 301 context.getPainter().paintButtonBackground(context, g, 0, 0, 302 c.getWidth(), 303 c.getHeight()); 304 } 305 } 306 307 /** 308 * @inheritDoc 309 */ 310 @Override 311 public void paintBorder(SynthContext context, Graphics g, int x, 312 int y, int w, int h) { 313 context.getPainter().paintButtonBorder(context, g, x, y, w, h); 314 } 315 316 /** 317 * Returns the default icon. This should not callback 318 * to the JComponent. 319 * 320 * @param b button the icon is associated with 321 * @return default icon 322 */ 323 protected Icon getDefaultIcon(AbstractButton b) { 324 SynthContext context = getContext(b); 325 Icon icon = context.getStyle().getIcon(context, getPropertyPrefix() + "icon"); 326 context.dispose(); 327 return icon; 328 } 329 330 /** 331 * Returns the Icon to use for painting the button. The icon is chosen with 332 * respect to the current state of the button. 333 * 334 * @param b button the icon is associated with 335 * @return an icon 336 */ 337 protected Icon getIcon(AbstractButton b) { 338 Icon icon = b.getIcon(); 339 ButtonModel model = b.getModel(); 340 341 if (!model.isEnabled()) { 342 icon = getSynthDisabledIcon(b, icon); 343 } else if (model.isPressed() && model.isArmed()) { 344 icon = getPressedIcon(b, getSelectedIcon(b, icon)); 345 } else if (b.isRolloverEnabled() && model.isRollover()) { 346 icon = getRolloverIcon(b, getSelectedIcon(b, icon)); 347 } else if (model.isSelected()) { 348 icon = getSelectedIcon(b, icon); 349 } else { 350 icon = getEnabledIcon(b, icon); 351 } 352 if(icon == null) { 353 return getDefaultIcon(b); 354 } 355 return icon; 356 } 357 358 /** 359 * This method will return the icon that should be used for a button. We 360 * only want to use the synth icon defined by the style if the specific 361 * icon has not been defined for the button state and the backup icon is a 362 * UIResource (we set it, not the developer). 363 * 364 * @param b button 365 * @param specificIcon icon returned from the button for the specific state 366 * @param defaultIcon fallback icon 367 * @param state the synth state of the button 368 */ 369 private Icon getIcon(AbstractButton b, Icon specificIcon, Icon defaultIcon, 370 int state) { 371 Icon icon = specificIcon; 372 if (icon == null) { 373 if (defaultIcon instanceof UIResource) { 374 icon = getSynthIcon(b, state); 375 if (icon == null) { 376 icon = defaultIcon; 377 } 378 } else { 379 icon = defaultIcon; 380 } 381 } 382 return icon; 383 } 384 385 private Icon getSynthIcon(AbstractButton b, int synthConstant) { 386 return style.getIcon(getContext(b, synthConstant), getPropertyPrefix() + "icon"); 387 } 388 389 private Icon getEnabledIcon(AbstractButton b, Icon defaultIcon) { 390 if (defaultIcon == null) { 391 defaultIcon = getSynthIcon(b, SynthConstants.ENABLED); 392 } 393 return defaultIcon; 394 } 395 396 private Icon getSelectedIcon(AbstractButton b, Icon defaultIcon) { 397 return getIcon(b, b.getSelectedIcon(), defaultIcon, 398 SynthConstants.SELECTED); 399 } 400 401 private Icon getRolloverIcon(AbstractButton b, Icon defaultIcon) { 402 ButtonModel model = b.getModel(); 403 Icon icon; 404 if (model.isSelected()) { 405 icon = getIcon(b, b.getRolloverSelectedIcon(), defaultIcon, 406 SynthConstants.MOUSE_OVER | SynthConstants.SELECTED); 407 } else { 408 icon = getIcon(b, b.getRolloverIcon(), defaultIcon, 409 SynthConstants.MOUSE_OVER); 410 } 411 return icon; 412 } 413 414 private Icon getPressedIcon(AbstractButton b, Icon defaultIcon) { 415 return getIcon(b, b.getPressedIcon(), defaultIcon, 416 SynthConstants.PRESSED); 417 } 418 419 private Icon getSynthDisabledIcon(AbstractButton b, Icon defaultIcon) { 420 ButtonModel model = b.getModel(); 421 Icon icon; 422 if (model.isSelected()) { 423 icon = getIcon(b, b.getDisabledSelectedIcon(), defaultIcon, 424 SynthConstants.DISABLED | SynthConstants.SELECTED); 425 } else { 426 icon = getIcon(b, b.getDisabledIcon(), defaultIcon, 427 SynthConstants.DISABLED); 428 } 429 return icon; 430 } 431 432 /** 433 * Returns the amount to shift the text/icon when painting. 434 */ 435 private int getTextShiftOffset(SynthContext state) { 436 AbstractButton button = (AbstractButton)state.getComponent(); 437 ButtonModel model = button.getModel(); 438 439 if (model.isArmed() && model.isPressed() && 440 button.getPressedIcon() == null) { 441 return state.getStyle().getInt(state, getPropertyPrefix() + 442 "textShiftOffset", 0); 443 } 444 return 0; 445 } 446 447 // ******************************** 448 // Layout Methods 449 // ******************************** 450 451 /** 452 * @inheritDoc 453 */ 454 @Override 455 public Dimension getMinimumSize(JComponent c) { 456 if (c.getComponentCount() > 0 && c.getLayout() != null) { 457 return null; 458 } 459 AbstractButton b = (AbstractButton)c; 460 SynthContext ss = getContext(c); 461 Dimension size = ss.getStyle().getGraphicsUtils(ss).getMinimumSize( 462 ss, ss.getStyle().getFont(ss), b.getText(), getSizingIcon(b), 463 b.getHorizontalAlignment(), b.getVerticalAlignment(), 464 b.getHorizontalTextPosition(), 465 b.getVerticalTextPosition(), b.getIconTextGap(), 466 b.getDisplayedMnemonicIndex()); 467 468 ss.dispose(); 469 return size; 470 } 471 472 /** 473 * @inheritDoc 474 */ 475 @Override 476 public Dimension getPreferredSize(JComponent c) { 477 if (c.getComponentCount() > 0 && c.getLayout() != null) { 478 return null; 479 } 480 AbstractButton b = (AbstractButton)c; 481 SynthContext ss = getContext(c); 482 Dimension size = ss.getStyle().getGraphicsUtils(ss).getPreferredSize( 483 ss, ss.getStyle().getFont(ss), b.getText(), getSizingIcon(b), 484 b.getHorizontalAlignment(), b.getVerticalAlignment(), 485 b.getHorizontalTextPosition(), 486 b.getVerticalTextPosition(), b.getIconTextGap(), 487 b.getDisplayedMnemonicIndex()); 488 489 ss.dispose(); 490 return size; 491 } 492 493 /** 494 * @inheritDoc 495 */ 496 @Override 497 public Dimension getMaximumSize(JComponent c) { 498 if (c.getComponentCount() > 0 && c.getLayout() != null) { 499 return null; 500 } 501 502 AbstractButton b = (AbstractButton)c; 503 SynthContext ss = getContext(c); 504 Dimension size = ss.getStyle().getGraphicsUtils(ss).getMaximumSize( 505 ss, ss.getStyle().getFont(ss), b.getText(), getSizingIcon(b), 506 b.getHorizontalAlignment(), b.getVerticalAlignment(), 507 b.getHorizontalTextPosition(), 508 b.getVerticalTextPosition(), b.getIconTextGap(), 509 b.getDisplayedMnemonicIndex()); 510 511 ss.dispose(); 512 return size; 513 } 514 515 /** 516 * Returns the Icon used in calculating the 517 * preferred/minimum/maximum size. 518 */ 519 protected Icon getSizingIcon(AbstractButton b) { 520 Icon icon = getEnabledIcon(b, b.getIcon()); 521 if (icon == null) { 522 icon = getDefaultIcon(b); 523 } 524 return icon; 525 } 526 527 /** 528 * @inheritDoc 529 */ 530 @Override 531 public void propertyChange(PropertyChangeEvent e) { 532 if (SynthLookAndFeel.shouldUpdateStyle(e)) { 533 updateStyle((AbstractButton)e.getSource()); 534 } 535 } 536 }