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