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