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 }