1 /* 2 * Copyright (c) 2005, 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 package javax.swing.plaf.nimbus; 26 27 import java.awt.BorderLayout; 28 import static java.awt.BorderLayout.*; 29 import javax.swing.JComponent; 30 import javax.swing.UIDefaults; 31 import javax.swing.UIManager; 32 import javax.swing.plaf.synth.Region; 33 import javax.swing.plaf.synth.SynthLookAndFeel; 34 import javax.swing.plaf.synth.SynthStyle; 35 import javax.swing.plaf.synth.SynthStyleFactory; 36 import javax.swing.plaf.UIResource; 37 import java.security.AccessController; 38 import java.awt.Color; 39 import java.awt.Container; 40 import java.awt.Graphics2D; 41 import java.awt.LayoutManager; 42 import java.awt.image.BufferedImage; 43 import java.beans.PropertyChangeEvent; 44 import java.beans.PropertyChangeListener; 45 import java.util.*; 46 import javax.swing.GrayFilter; 47 import javax.swing.Icon; 48 import javax.swing.JToolBar; 49 import javax.swing.border.TitledBorder; 50 import javax.swing.plaf.BorderUIResource; 51 import javax.swing.plaf.ColorUIResource; 52 import sun.swing.ImageIconUIResource; 53 import sun.swing.plaf.synth.SynthIcon; 54 import sun.swing.plaf.GTKKeybindings; 55 import sun.swing.plaf.WindowsKeybindings; 56 import sun.security.action.GetPropertyAction; 57 58 /** 59 * <p>The NimbusLookAndFeel class.</p> 60 * 61 * @author Jasper Potts 62 * @author Richard Bair 63 */ 64 @SuppressWarnings("serial") // Superclass is not serializable across versions 65 public class NimbusLookAndFeel extends SynthLookAndFeel { 66 67 /** Set of standard region names for UIDefaults Keys */ 68 private static final String[] COMPONENT_KEYS = new String[]{"ArrowButton", "Button", 69 "CheckBox", "CheckBoxMenuItem", "ColorChooser", "ComboBox", 70 "DesktopPane", "DesktopIcon", "EditorPane", "FileChooser", 71 "FormattedTextField", "InternalFrame", 72 "InternalFrameTitlePane", "Label", "List", "Menu", 73 "MenuBar", "MenuItem", "OptionPane", "Panel", 74 "PasswordField", "PopupMenu", "PopupMenuSeparator", 75 "ProgressBar", "RadioButton", "RadioButtonMenuItem", 76 "RootPane", "ScrollBar", "ScrollBarTrack", "ScrollBarThumb", 77 "ScrollPane", "Separator", "Slider", "SliderTrack", 78 "SliderThumb", "Spinner", "SplitPane", "TabbedPane", 79 "Table", "TableHeader", "TextArea", "TextField", "TextPane", 80 "ToggleButton", "ToolBar", "ToolTip", "Tree", "Viewport"}; 81 82 /** 83 * A reference to the auto-generated file NimbusDefaults. This file contains 84 * the default mappings and values for the look and feel as specified in the 85 * visual designer. 86 */ 87 private NimbusDefaults defaults; 88 89 /** 90 * Reference to populated LAD uidefaults 91 */ 92 private UIDefaults uiDefaults; 93 94 private DefaultsListener defaultsListener = new DefaultsListener(); 95 96 /** 97 * Create a new NimbusLookAndFeel. 98 */ 99 public NimbusLookAndFeel() { 100 super(); 101 defaults = new NimbusDefaults(); 102 } 103 104 /** Called by UIManager when this look and feel is installed. */ 105 @Override public void initialize() { 106 super.initialize(); 107 defaults.initialize(); 108 // create synth style factory 109 setStyleFactory(new SynthStyleFactory() { 110 @Override 111 public SynthStyle getStyle(JComponent c, Region r) { 112 return defaults.getStyle(c, r); 113 } 114 }); 115 } 116 117 118 /** Called by UIManager when this look and feel is uninstalled. */ 119 @Override public void uninitialize() { 120 super.uninitialize(); 121 defaults.uninitialize(); 122 // clear all cached images to free memory 123 ImageCache.getInstance().flush(); 124 UIManager.getDefaults().removePropertyChangeListener(defaultsListener); 125 } 126 127 /** 128 * {@inheritDoc} 129 */ 130 @Override public UIDefaults getDefaults() { 131 if (uiDefaults == null){ 132 // Detect platform 133 String osName = getSystemProperty("os.name"); 134 boolean isWindows = osName != null && osName.contains("Windows"); 135 136 // We need to call super for basic's properties file. 137 uiDefaults = super.getDefaults(); 138 defaults.initializeDefaults(uiDefaults); 139 140 // Install Keybindings 141 if (isWindows) { 142 WindowsKeybindings.installKeybindings(uiDefaults); 143 } else { 144 GTKKeybindings.installKeybindings(uiDefaults); 145 } 146 147 // Add Titled Border 148 uiDefaults.put("TitledBorder.titlePosition", 149 TitledBorder.ABOVE_TOP); 150 uiDefaults.put("TitledBorder.border", new BorderUIResource( 151 new LoweredBorder())); 152 uiDefaults.put("TitledBorder.titleColor", 153 getDerivedColor("text",0.0f,0.0f,0.23f,0,true)); 154 uiDefaults.put("TitledBorder.font", 155 new NimbusDefaults.DerivedFont("defaultFont", 156 1f, true, null)); 157 158 // Choose Dialog button positions 159 uiDefaults.put("OptionPane.isYesLast", !isWindows); 160 161 // Store Table ScrollPane Corner Component 162 uiDefaults.put("Table.scrollPaneCornerComponent", 163 new UIDefaults.ActiveValue() { 164 @Override 165 public Object createValue(UIDefaults table) { 166 return new TableScrollPaneCorner(); 167 } 168 }); 169 170 // Setup the settings for ToolBarSeparator which is custom 171 // installed for Nimbus 172 uiDefaults.put("ToolBarSeparator[Enabled].backgroundPainter", 173 new ToolBarSeparatorPainter()); 174 175 // Populate UIDefaults with a standard set of properties 176 for (String componentKey : COMPONENT_KEYS) { 177 String key = componentKey+".foreground"; 178 if (!uiDefaults.containsKey(key)){ 179 uiDefaults.put(key, 180 new NimbusProperty(componentKey,"textForeground")); 181 } 182 key = componentKey+".background"; 183 if (!uiDefaults.containsKey(key)){ 184 uiDefaults.put(key, 185 new NimbusProperty(componentKey,"background")); 186 } 187 key = componentKey+".font"; 188 if (!uiDefaults.containsKey(key)){ 189 uiDefaults.put(key, 190 new NimbusProperty(componentKey,"font")); 191 } 192 key = componentKey+".disabledText"; 193 if (!uiDefaults.containsKey(key)){ 194 uiDefaults.put(key, 195 new NimbusProperty(componentKey,"Disabled", 196 "textForeground")); 197 } 198 key = componentKey+".disabled"; 199 if (!uiDefaults.containsKey(key)){ 200 uiDefaults.put(key, 201 new NimbusProperty(componentKey,"Disabled", 202 "background")); 203 } 204 } 205 206 // FileView icon keys are used by some applications, we don't have 207 // a computer icon at the moment so using home icon for now 208 uiDefaults.put("FileView.computerIcon", 209 new LinkProperty("FileChooser.homeFolderIcon")); 210 uiDefaults.put("FileView.directoryIcon", 211 new LinkProperty("FileChooser.directoryIcon")); 212 uiDefaults.put("FileView.fileIcon", 213 new LinkProperty("FileChooser.fileIcon")); 214 uiDefaults.put("FileView.floppyDriveIcon", 215 new LinkProperty("FileChooser.floppyDriveIcon")); 216 uiDefaults.put("FileView.hardDriveIcon", 217 new LinkProperty("FileChooser.hardDriveIcon")); 218 } 219 return uiDefaults; 220 } 221 222 /** 223 * Gets the style associated with the given component and region. This 224 * will never return null. If an appropriate component and region cannot 225 * be determined, then a default style is returned. 226 * 227 * @param c a non-null reference to a JComponent 228 * @param r a non-null reference to the region of the component c 229 * @return a non-null reference to a NimbusStyle. 230 */ 231 public static NimbusStyle getStyle(JComponent c, Region r) { 232 return (NimbusStyle)SynthLookAndFeel.getStyle(c, r); 233 } 234 235 /** 236 * Return a short string that identifies this look and feel. This 237 * String will be the unquoted String "Nimbus". 238 * 239 * @return a short string identifying this look and feel. 240 */ 241 @Override public String getName() { 242 return "Nimbus"; 243 } 244 245 /** 246 * Return a string that identifies this look and feel. This String will 247 * be the unquoted String "Nimbus". 248 * 249 * @return a short string identifying this look and feel. 250 */ 251 @Override public String getID() { 252 return "Nimbus"; 253 } 254 255 /** 256 * Returns a textual description of this look and feel. 257 * 258 * @return textual description of this look and feel. 259 */ 260 @Override public String getDescription() { 261 return "Nimbus Look and Feel"; 262 } 263 264 /** 265 * {@inheritDoc} 266 * @return {@code true} 267 */ 268 @Override public boolean shouldUpdateStyleOnAncestorChanged() { 269 return true; 270 } 271 272 /** 273 * {@inheritDoc} 274 * 275 * <p>Overridden to return {@code true} when one of the following 276 * properties change: 277 * <ul> 278 * <li>{@code "Nimbus.Overrides"} 279 * <li>{@code "Nimbus.Overrides.InheritDefaults"} 280 * <li>{@code "JComponent.sizeVariant"} 281 * </ul> 282 * 283 * @since 1.7 284 */ 285 @Override 286 protected boolean shouldUpdateStyleOnEvent(PropertyChangeEvent ev) { 287 String eName = ev.getPropertyName(); 288 289 // These properties affect style cached inside NimbusDefaults (6860433) 290 if ("name" == eName || 291 "ancestor" == eName || 292 "Nimbus.Overrides" == eName || 293 "Nimbus.Overrides.InheritDefaults" == eName || 294 "JComponent.sizeVariant" == eName) { 295 296 JComponent c = (JComponent) ev.getSource(); 297 defaults.clearOverridesCache(c); 298 return true; 299 } 300 301 return super.shouldUpdateStyleOnEvent(ev); 302 } 303 304 /** 305 * <p>Registers a third party component with the NimbusLookAndFeel.</p> 306 * 307 * <p>Regions represent Components and areas within Components that act as 308 * independent painting areas. Once registered with the NimbusLookAndFeel, 309 * NimbusStyles for these Regions can be retrieved via the 310 * <code>getStyle</code> method.</p> 311 * 312 * <p>The NimbusLookAndFeel uses a standard naming scheme for entries in the 313 * UIDefaults table. The key for each property, state, painter, and other 314 * default registered in UIDefaults for a specific Region will begin with 315 * the specified <code>prefix</code></p> 316 * 317 * <p>For example, suppose I had a component named JFoo. Suppose I then registered 318 * this component with the NimbusLookAndFeel in this manner:</p> 319 * 320 * <pre><code> 321 * laf.register(NimbusFooUI.FOO_REGION, "Foo"); 322 * </code></pre> 323 * 324 * <p>In this case, I could then register properties for this component with 325 * UIDefaults in the following manner:</p> 326 * 327 * <pre><code> 328 * UIManager.put("Foo.background", new ColorUIResource(Color.BLACK)); 329 * UIManager.put("Foo.Enabled.backgroundPainter", new FooBackgroundPainter()); 330 * </code></pre> 331 * 332 * <p>It is also possible to register a named component with Nimbus. 333 * For example, suppose you wanted to style the background of a JPanel 334 * named "MyPanel" differently from other JPanels. You could accomplish this 335 * by doing the following:</p> 336 * 337 * <pre><code> 338 * laf.register(Region.PANEL, "\"MyPanel\""); 339 * UIManager.put("\"MyPanel\".background", new ColorUIResource(Color.RED)); 340 * </code></pre> 341 * 342 * @param region The Synth Region that is being registered. Such as Button, or 343 * ScrollBarThumb, or NimbusFooUI.FOO_REGION. 344 * @param prefix The UIDefault prefix. For example, could be ComboBox, or if 345 * a named components, "MyComboBox", or even something like 346 * ToolBar."MyComboBox"."ComboBox.arrowButton" 347 */ 348 public void register(Region region, String prefix) { 349 defaults.register(region, prefix); 350 } 351 352 /** 353 * Simple utility method that reads system keys. 354 */ 355 private String getSystemProperty(String key) { 356 return AccessController.doPrivileged(new GetPropertyAction(key)); 357 } 358 359 @Override 360 public Icon getDisabledIcon(JComponent component, Icon icon) { 361 if (icon instanceof SynthIcon) { 362 SynthIcon si = (SynthIcon)icon; 363 BufferedImage img = EffectUtils.createCompatibleTranslucentImage( 364 si.getIconWidth(), si.getIconHeight()); 365 Graphics2D gfx = img.createGraphics(); 366 si.paintIcon(component, gfx, 0, 0); 367 gfx.dispose(); 368 return new ImageIconUIResource(GrayFilter.createDisabledImage(img)); 369 } else { 370 return super.getDisabledIcon(component, icon); 371 } 372 } 373 374 /** 375 * Get a derived color, derived colors are shared instances and is color 376 * value will change when its parent UIDefault color changes. 377 * 378 * @param uiDefaultParentName The parent UIDefault key 379 * @param hOffset The hue offset 380 * @param sOffset The saturation offset 381 * @param bOffset The brightness offset 382 * @param aOffset The alpha offset 383 * @param uiResource True if the derived color should be a 384 * UIResource, false if it should not be 385 * @return The stored derived color 386 */ 387 public Color getDerivedColor(String uiDefaultParentName, 388 float hOffset, float sOffset, 389 float bOffset, int aOffset, 390 boolean uiResource) { 391 return defaults.getDerivedColor(uiDefaultParentName, hOffset, sOffset, 392 bOffset, aOffset, uiResource); 393 } 394 395 /** 396 * Decodes and returns a color, which is derived from an offset between two 397 * other colors. 398 * 399 * @param color1 The first color 400 * @param color2 The second color 401 * @param midPoint The offset between color 1 and color 2, a value of 0.0 is 402 * color 1 and 1.0 is color 2; 403 * @param uiResource True if the derived color should be a UIResource 404 * @return The derived color 405 */ 406 protected final Color getDerivedColor(Color color1, Color color2, 407 float midPoint, boolean uiResource) { 408 int argb = deriveARGB(color1, color2, midPoint); 409 if (uiResource) { 410 return new ColorUIResource(argb); 411 } else { 412 return new Color(argb); 413 } 414 } 415 416 /** 417 * Decodes and returns a color, which is derived from a offset between two 418 * other colors. 419 * 420 * @param color1 The first color 421 * @param color2 The second color 422 * @param midPoint The offset between color 1 and color 2, a value of 0.0 is 423 * color 1 and 1.0 is color 2; 424 * @return The derived color, which will be a UIResource 425 */ 426 protected final Color getDerivedColor(Color color1, Color color2, 427 float midPoint) { 428 return getDerivedColor(color1, color2, midPoint, true); 429 } 430 431 /** 432 * Package private method which returns either BorderLayout.NORTH, 433 * BorderLayout.SOUTH, BorderLayout.EAST, or BorderLayout.WEST depending 434 * on the location of the toolbar in its parent. The toolbar might be 435 * in PAGE_START, PAGE_END, CENTER, or some other position, but will be 436 * resolved to either NORTH,SOUTH,EAST, or WEST based on where the toolbar 437 * actually IS, with CENTER being NORTH. 438 * 439 * This code is used to determine where the border line should be drawn 440 * by the custom toolbar states, and also used by NimbusIcon to determine 441 * whether the handle icon needs to be shifted to look correct. 442 * 443 * Toollbars are unfortunately odd in the way these things are handled, 444 * and so this code exists to unify the logic related to toolbars so it can 445 * be shared among the static files such as NimbusIcon and generated files 446 * such as the ToolBar state classes. 447 */ 448 static Object resolveToolbarConstraint(JToolBar toolbar) { 449 //NOTE: we don't worry about component orientation or PAGE_END etc 450 //because the BasicToolBarUI always uses an absolute position of 451 //NORTH/SOUTH/EAST/WEST. 452 if (toolbar != null) { 453 Container parent = toolbar.getParent(); 454 if (parent != null) { 455 LayoutManager m = parent.getLayout(); 456 if (m instanceof BorderLayout) { 457 BorderLayout b = (BorderLayout)m; 458 Object con = b.getConstraints(toolbar); 459 if (con == SOUTH || con == EAST || con == WEST) { 460 return con; 461 } 462 return NORTH; 463 } 464 } 465 } 466 return NORTH; 467 } 468 469 /** 470 * Derives the ARGB value for a color based on an offset between two 471 * other colors. 472 * 473 * @param color1 The first color 474 * @param color2 The second color 475 * @param midPoint The offset between color 1 and color 2, a value of 0.0 is 476 * color 1 and 1.0 is color 2; 477 * @return the ARGB value for a new color based on this derivation 478 */ 479 static int deriveARGB(Color color1, Color color2, float midPoint) { 480 int r = color1.getRed() + 481 Math.round((color2.getRed() - color1.getRed()) * midPoint); 482 int g = color1.getGreen() + 483 Math.round((color2.getGreen() - color1.getGreen()) * midPoint); 484 int b = color1.getBlue() + 485 Math.round((color2.getBlue() - color1.getBlue()) * midPoint); 486 int a = color1.getAlpha() + 487 Math.round((color2.getAlpha() - color1.getAlpha()) * midPoint); 488 return ((a & 0xFF) << 24) | 489 ((r & 0xFF) << 16) | 490 ((g & 0xFF) << 8) | 491 (b & 0xFF); 492 } 493 494 /** 495 * Simple Symbolic Link style UIDefalts Property 496 */ 497 private class LinkProperty implements UIDefaults.ActiveValue, UIResource{ 498 private String dstPropName; 499 500 private LinkProperty(String dstPropName) { 501 this.dstPropName = dstPropName; 502 } 503 504 @Override 505 public Object createValue(UIDefaults table) { 506 return UIManager.get(dstPropName); 507 } 508 } 509 510 /** 511 * Nimbus Property that looks up Nimbus keys for standard key names. For 512 * example "Button.background" --> "Button[Enabled].backgound" 513 */ 514 private class NimbusProperty implements UIDefaults.ActiveValue, UIResource { 515 private String prefix; 516 private String state = null; 517 private String suffix; 518 private boolean isFont; 519 520 private NimbusProperty(String prefix, String suffix) { 521 this.prefix = prefix; 522 this.suffix = suffix; 523 isFont = "font".equals(suffix); 524 } 525 526 private NimbusProperty(String prefix, String state, String suffix) { 527 this(prefix,suffix); 528 this.state = state; 529 } 530 531 /** 532 * Creates the value retrieved from the <code>UIDefaults</code> table. 533 * The object is created each time it is accessed. 534 * 535 * @param table a <code>UIDefaults</code> table 536 * @return the created <code>Object</code> 537 */ 538 @Override 539 public Object createValue(UIDefaults table) { 540 Object obj = null; 541 // check specified state 542 if (state!=null){ 543 obj = uiDefaults.get(prefix+"["+state+"]."+suffix); 544 } 545 // check enabled state 546 if (obj==null){ 547 obj = uiDefaults.get(prefix+"[Enabled]."+suffix); 548 } 549 // check for defaults 550 if (obj==null){ 551 if (isFont) { 552 obj = uiDefaults.get("defaultFont"); 553 } else { 554 obj = uiDefaults.get(suffix); 555 } 556 } 557 return obj; 558 } 559 } 560 561 private Map<String, Map<String, Object>> compiledDefaults = null; 562 private boolean defaultListenerAdded = false; 563 564 static String parsePrefix(String key) { 565 if (key == null) { 566 return null; 567 } 568 boolean inquotes = false; 569 for (int i = 0; i < key.length(); i++) { 570 char c = key.charAt(i); 571 if (c == '"') { 572 inquotes = !inquotes; 573 } else if ((c == '[' || c == '.') && !inquotes) { 574 return key.substring(0, i); 575 } 576 } 577 return null; 578 } 579 580 Map<String, Object> getDefaultsForPrefix(String prefix) { 581 if (compiledDefaults == null) { 582 compiledDefaults = new HashMap<String, Map<String, Object>>(); 583 for (Map.Entry<Object, Object> entry: UIManager.getDefaults().entrySet()) { 584 if (entry.getKey() instanceof String) { 585 addDefault((String) entry.getKey(), entry.getValue()); 586 } 587 } 588 if (! defaultListenerAdded) { 589 UIManager.getDefaults().addPropertyChangeListener(defaultsListener); 590 defaultListenerAdded = true; 591 } 592 } 593 return compiledDefaults.get(prefix); 594 } 595 596 private void addDefault(String key, Object value) { 597 if (compiledDefaults == null) { 598 return; 599 } 600 601 String prefix = parsePrefix(key); 602 if (prefix != null) { 603 Map<String, Object> keys = compiledDefaults.get(prefix); 604 if (keys == null) { 605 keys = new HashMap<String, Object>(); 606 compiledDefaults.put(prefix, keys); 607 } 608 keys.put(key, value); 609 } 610 } 611 612 private class DefaultsListener implements PropertyChangeListener { 613 @Override public void propertyChange(PropertyChangeEvent ev) { 614 String key = ev.getPropertyName(); 615 if ("UIDefaults".equals(key)) { 616 compiledDefaults = null; 617 } else { 618 addDefault(key, ev.getNewValue()); 619 } 620 } 621 } 622 }