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