| /* | |
| * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package javax.swing.plaf.nimbus; | |
| import java.awt.BorderLayout; | |
| import static java.awt.BorderLayout.*; | |
| import javax.swing.JComponent; | |
| import javax.swing.UIDefaults; | |
| import javax.swing.UIManager; | |
| import javax.swing.plaf.synth.Region; | |
| import javax.swing.plaf.synth.SynthLookAndFeel; | |
| import javax.swing.plaf.synth.SynthStyle; | |
| import javax.swing.plaf.synth.SynthStyleFactory; | |
| import javax.swing.plaf.UIResource; | |
| import java.awt.Color; | |
| import java.awt.Container; | |
| import java.awt.Graphics2D; | |
| import java.awt.LayoutManager; | |
| import java.awt.image.BufferedImage; | |
| import java.beans.PropertyChangeEvent; | |
| import java.beans.PropertyChangeListener; | |
| import java.util.*; | |
| import javax.swing.GrayFilter; | |
| import javax.swing.Icon; | |
| import javax.swing.JToolBar; | |
| import javax.swing.border.TitledBorder; | |
| import javax.swing.plaf.BorderUIResource; | |
| import javax.swing.plaf.ColorUIResource; | |
| import sun.awt.OSInfo; | |
| import sun.swing.ImageIconUIResource; | |
| import javax.swing.plaf.synth.SynthIcon; | |
| import sun.swing.plaf.GTKKeybindings; | |
| import sun.swing.plaf.WindowsKeybindings; | |
| /** | |
| * <p>The NimbusLookAndFeel class.</p> | |
| * | |
| * @author Jasper Potts | |
| * @author Richard Bair | |
| */ | |
| @SuppressWarnings("serial") // Superclass is not serializable across versions | |
| public class NimbusLookAndFeel extends SynthLookAndFeel { | |
| /** Set of standard region names for UIDefaults Keys */ | |
| private static final String[] COMPONENT_KEYS = new String[]{"ArrowButton", "Button", | |
| "CheckBox", "CheckBoxMenuItem", "ColorChooser", "ComboBox", | |
| "DesktopPane", "DesktopIcon", "EditorPane", "FileChooser", | |
| "FormattedTextField", "InternalFrame", | |
| "InternalFrameTitlePane", "Label", "List", "Menu", | |
| "MenuBar", "MenuItem", "OptionPane", "Panel", | |
| "PasswordField", "PopupMenu", "PopupMenuSeparator", | |
| "ProgressBar", "RadioButton", "RadioButtonMenuItem", | |
| "RootPane", "ScrollBar", "ScrollBarTrack", "ScrollBarThumb", | |
| "ScrollPane", "Separator", "Slider", "SliderTrack", | |
| "SliderThumb", "Spinner", "SplitPane", "TabbedPane", | |
| "Table", "TableHeader", "TextArea", "TextField", "TextPane", | |
| "ToggleButton", "ToolBar", "ToolTip", "Tree", "Viewport"}; | |
| /** | |
| * A reference to the auto-generated file NimbusDefaults. This file contains | |
| * the default mappings and values for the look and feel as specified in the | |
| * visual designer. | |
| */ | |
| private NimbusDefaults defaults; | |
| /** | |
| * Reference to populated LAD uidefaults | |
| */ | |
| private UIDefaults uiDefaults; | |
| private DefaultsListener defaultsListener = new DefaultsListener(); | |
| /** | |
| * Create a new NimbusLookAndFeel. | |
| */ | |
| public NimbusLookAndFeel() { | |
| super(); | |
| defaults = new NimbusDefaults(); | |
| } | |
| /** Called by UIManager when this look and feel is installed. */ | |
| @Override public void initialize() { | |
| super.initialize(); | |
| defaults.initialize(); | |
| // create synth style factory | |
| setStyleFactory(new SynthStyleFactory() { | |
| @Override | |
| public SynthStyle getStyle(JComponent c, Region r) { | |
| return defaults.getStyle(c, r); | |
| } | |
| }); | |
| } | |
| /** Called by UIManager when this look and feel is uninstalled. */ | |
| @Override public void uninitialize() { | |
| super.uninitialize(); | |
| defaults.uninitialize(); | |
| // clear all cached images to free memory | |
| ImageCache.getInstance().flush(); | |
| UIManager.getDefaults().removePropertyChangeListener(defaultsListener); | |
| } | |
| /** | |
| * {@inheritDoc} | |
| */ | |
| @Override public UIDefaults getDefaults() { | |
| if (uiDefaults == null){ | |
| // Detect platform | |
| boolean isWindows = OSInfo.getOSType() == OSInfo.OSType.WINDOWS; | |
| // We need to call super for basic's properties file. | |
| uiDefaults = super.getDefaults(); | |
| defaults.initializeDefaults(uiDefaults); | |
| // Install Keybindings | |
| if (isWindows) { | |
| WindowsKeybindings.installKeybindings(uiDefaults); | |
| } else { | |
| GTKKeybindings.installKeybindings(uiDefaults); | |
| } | |
| // Add Titled Border | |
| uiDefaults.put("TitledBorder.titlePosition", | |
| TitledBorder.ABOVE_TOP); | |
| uiDefaults.put("TitledBorder.border", new BorderUIResource( | |
| new LoweredBorder())); | |
| uiDefaults.put("TitledBorder.titleColor", | |
| getDerivedColor("text",0.0f,0.0f,0.23f,0,true)); | |
| uiDefaults.put("TitledBorder.font", | |
| new NimbusDefaults.DerivedFont("defaultFont", | |
| 1f, true, null)); | |
| // Choose Dialog button positions | |
| uiDefaults.put("OptionPane.isYesLast", !isWindows); | |
| // Store Table ScrollPane Corner Component | |
| uiDefaults.put("Table.scrollPaneCornerComponent", | |
| new UIDefaults.ActiveValue() { | |
| @Override | |
| public Object createValue(UIDefaults table) { | |
| return new TableScrollPaneCorner(); | |
| } | |
| }); | |
| // Setup the settings for ToolBarSeparator which is custom | |
| // installed for Nimbus | |
| uiDefaults.put("ToolBarSeparator[Enabled].backgroundPainter", | |
| new ToolBarSeparatorPainter()); | |
| // Populate UIDefaults with a standard set of properties | |
| for (String componentKey : COMPONENT_KEYS) { | |
| String key = componentKey+".foreground"; | |
| if (!uiDefaults.containsKey(key)){ | |
| uiDefaults.put(key, | |
| new NimbusProperty(componentKey,"textForeground")); | |
| } | |
| key = componentKey+".background"; | |
| if (!uiDefaults.containsKey(key)){ | |
| uiDefaults.put(key, | |
| new NimbusProperty(componentKey,"background")); | |
| } | |
| key = componentKey+".font"; | |
| if (!uiDefaults.containsKey(key)){ | |
| uiDefaults.put(key, | |
| new NimbusProperty(componentKey,"font")); | |
| } | |
| key = componentKey+".disabledText"; | |
| if (!uiDefaults.containsKey(key)){ | |
| uiDefaults.put(key, | |
| new NimbusProperty(componentKey,"Disabled", | |
| "textForeground")); | |
| } | |
| key = componentKey+".disabled"; | |
| if (!uiDefaults.containsKey(key)){ | |
| uiDefaults.put(key, | |
| new NimbusProperty(componentKey,"Disabled", | |
| "background")); | |
| } | |
| } | |
| // FileView icon keys are used by some applications, we don't have | |
| // a computer icon at the moment so using home icon for now | |
| uiDefaults.put("FileView.computerIcon", | |
| new LinkProperty("FileChooser.homeFolderIcon")); | |
| uiDefaults.put("FileView.directoryIcon", | |
| new LinkProperty("FileChooser.directoryIcon")); | |
| uiDefaults.put("FileView.fileIcon", | |
| new LinkProperty("FileChooser.fileIcon")); | |
| uiDefaults.put("FileView.floppyDriveIcon", | |
| new LinkProperty("FileChooser.floppyDriveIcon")); | |
| uiDefaults.put("FileView.hardDriveIcon", | |
| new LinkProperty("FileChooser.hardDriveIcon")); | |
| } | |
| return uiDefaults; | |
| } | |
| /** | |
| * Gets the style associated with the given component and region. This | |
| * will never return null. If an appropriate component and region cannot | |
| * be determined, then a default style is returned. | |
| * | |
| * @param c a non-null reference to a JComponent | |
| * @param r a non-null reference to the region of the component c | |
| * @return a non-null reference to a NimbusStyle. | |
| */ | |
| public static NimbusStyle getStyle(JComponent c, Region r) { | |
| return (NimbusStyle)SynthLookAndFeel.getStyle(c, r); | |
| } | |
| /** | |
| * Return a short string that identifies this look and feel. This | |
| * String will be the unquoted String "Nimbus". | |
| * | |
| * @return a short string identifying this look and feel. | |
| */ | |
| @Override public String getName() { | |
| return "Nimbus"; | |
| } | |
| /** | |
| * Return a string that identifies this look and feel. This String will | |
| * be the unquoted String "Nimbus". | |
| * | |
| * @return a short string identifying this look and feel. | |
| */ | |
| @Override public String getID() { | |
| return "Nimbus"; | |
| } | |
| /** | |
| * Returns a textual description of this look and feel. | |
| * | |
| * @return textual description of this look and feel. | |
| */ | |
| @Override public String getDescription() { | |
| return "Nimbus Look and Feel"; | |
| } | |
| /** | |
| * {@inheritDoc} | |
| * @return {@code true} | |
| */ | |
| @Override public boolean shouldUpdateStyleOnAncestorChanged() { | |
| return true; | |
| } | |
| /** | |
| * {@inheritDoc} | |
| * | |
| * <p>Overridden to return {@code true} when one of the following | |
| * properties change: | |
| * <ul> | |
| * <li>{@code "Nimbus.Overrides"} | |
| * <li>{@code "Nimbus.Overrides.InheritDefaults"} | |
| * <li>{@code "JComponent.sizeVariant"} | |
| * </ul> | |
| * | |
| * @since 1.7 | |
| */ | |
| @Override | |
| protected boolean shouldUpdateStyleOnEvent(PropertyChangeEvent ev) { | |
| String eName = ev.getPropertyName(); | |
| // These properties affect style cached inside NimbusDefaults (6860433) | |
| if ("name" == eName || | |
| "ancestor" == eName || | |
| "Nimbus.Overrides" == eName || | |
| "Nimbus.Overrides.InheritDefaults" == eName || | |
| "JComponent.sizeVariant" == eName) { | |
| JComponent c = (JComponent) ev.getSource(); | |
| defaults.clearOverridesCache(c); | |
| return true; | |
| } | |
| return super.shouldUpdateStyleOnEvent(ev); | |
| } | |
| /** | |
| * <p>Registers a third party component with the NimbusLookAndFeel.</p> | |
| * | |
| * <p>Regions represent Components and areas within Components that act as | |
| * independent painting areas. Once registered with the NimbusLookAndFeel, | |
| * NimbusStyles for these Regions can be retrieved via the | |
| * <code>getStyle</code> method.</p> | |
| * | |
| * <p>The NimbusLookAndFeel uses a standard naming scheme for entries in the | |
| * UIDefaults table. The key for each property, state, painter, and other | |
| * default registered in UIDefaults for a specific Region will begin with | |
| * the specified <code>prefix</code></p> | |
| * | |
| * <p>For example, suppose I had a component named JFoo. Suppose I then registered | |
| * this component with the NimbusLookAndFeel in this manner:</p> | |
| * | |
| * <pre><code> | |
| * laf.register(NimbusFooUI.FOO_REGION, "Foo"); | |
| * </code></pre> | |
| * | |
| * <p>In this case, I could then register properties for this component with | |
| * UIDefaults in the following manner:</p> | |
| * | |
| * <pre><code> | |
| * UIManager.put("Foo.background", new ColorUIResource(Color.BLACK)); | |
| * UIManager.put("Foo.Enabled.backgroundPainter", new FooBackgroundPainter()); | |
| * </code></pre> | |
| * | |
| * <p>It is also possible to register a named component with Nimbus. | |
| * For example, suppose you wanted to style the background of a JPanel | |
| * named "MyPanel" differently from other JPanels. You could accomplish this | |
| * by doing the following:</p> | |
| * | |
| * <pre><code> | |
| * laf.register(Region.PANEL, "\"MyPanel\""); | |
| * UIManager.put("\"MyPanel\".background", new ColorUIResource(Color.RED)); | |
| * </code></pre> | |
| * | |
| * @param region The Synth Region that is being registered. Such as Button, or | |
| * ScrollBarThumb, or NimbusFooUI.FOO_REGION. | |
| * @param prefix The UIDefault prefix. For example, could be ComboBox, or if | |
| * a named components, "MyComboBox", or even something like | |
| * ToolBar."MyComboBox"."ComboBox.arrowButton" | |
| */ | |
| public void register(Region region, String prefix) { | |
| defaults.register(region, prefix); | |
| } | |
| @Override | |
| public Icon getDisabledIcon(JComponent component, Icon icon) { | |
| if (icon instanceof SynthIcon) { | |
| SynthIcon si = (SynthIcon)icon; | |
| BufferedImage img = EffectUtils.createCompatibleTranslucentImage( | |
| si.getIconWidth(), si.getIconHeight()); | |
| Graphics2D gfx = img.createGraphics(); | |
| si.paintIcon(component, gfx, 0, 0); | |
| gfx.dispose(); | |
| return new ImageIconUIResource(GrayFilter.createDisabledImage(img)); | |
| } else { | |
| return super.getDisabledIcon(component, icon); | |
| } | |
| } | |
| /** | |
| * Get a derived color, derived colors are shared instances and is color | |
| * value will change when its parent UIDefault color changes. | |
| * | |
| * @param uiDefaultParentName The parent UIDefault key | |
| * @param hOffset The hue offset | |
| * @param sOffset The saturation offset | |
| * @param bOffset The brightness offset | |
| * @param aOffset The alpha offset | |
| * @param uiResource True if the derived color should be a | |
| * UIResource, false if it should not be | |
| * @return The stored derived color | |
| */ | |
| public Color getDerivedColor(String uiDefaultParentName, | |
| float hOffset, float sOffset, | |
| float bOffset, int aOffset, | |
| boolean uiResource) { | |
| return defaults.getDerivedColor(uiDefaultParentName, hOffset, sOffset, | |
| bOffset, aOffset, uiResource); | |
| } | |
| /** | |
| * Decodes and returns a color, which is derived from an offset between two | |
| * other colors. | |
| * | |
| * @param color1 The first color | |
| * @param color2 The second color | |
| * @param midPoint The offset between color 1 and color 2, a value of 0.0 is | |
| * color 1 and 1.0 is color 2; | |
| * @param uiResource True if the derived color should be a UIResource | |
| * @return The derived color | |
| */ | |
| protected final Color getDerivedColor(Color color1, Color color2, | |
| float midPoint, boolean uiResource) { | |
| int argb = deriveARGB(color1, color2, midPoint); | |
| if (uiResource) { | |
| return new ColorUIResource(argb); | |
| } else { | |
| return new Color(argb); | |
| } | |
| } | |
| /** | |
| * Decodes and returns a color, which is derived from a offset between two | |
| * other colors. | |
| * | |
| * @param color1 The first color | |
| * @param color2 The second color | |
| * @param midPoint The offset between color 1 and color 2, a value of 0.0 is | |
| * color 1 and 1.0 is color 2; | |
| * @return The derived color, which will be a UIResource | |
| */ | |
| protected final Color getDerivedColor(Color color1, Color color2, | |
| float midPoint) { | |
| return getDerivedColor(color1, color2, midPoint, true); | |
| } | |
| /** | |
| * Package private method which returns either BorderLayout.NORTH, | |
| * BorderLayout.SOUTH, BorderLayout.EAST, or BorderLayout.WEST depending | |
| * on the location of the toolbar in its parent. The toolbar might be | |
| * in PAGE_START, PAGE_END, CENTER, or some other position, but will be | |
| * resolved to either NORTH,SOUTH,EAST, or WEST based on where the toolbar | |
| * actually IS, with CENTER being NORTH. | |
| * | |
| * This code is used to determine where the border line should be drawn | |
| * by the custom toolbar states, and also used by NimbusIcon to determine | |
| * whether the handle icon needs to be shifted to look correct. | |
| * | |
| * Toollbars are unfortunately odd in the way these things are handled, | |
| * and so this code exists to unify the logic related to toolbars so it can | |
| * be shared among the static files such as NimbusIcon and generated files | |
| * such as the ToolBar state classes. | |
| */ | |
| static Object resolveToolbarConstraint(JToolBar toolbar) { | |
| //NOTE: we don't worry about component orientation or PAGE_END etc | |
| //because the BasicToolBarUI always uses an absolute position of | |
| //NORTH/SOUTH/EAST/WEST. | |
| if (toolbar != null) { | |
| Container parent = toolbar.getParent(); | |
| if (parent != null) { | |
| LayoutManager m = parent.getLayout(); | |
| if (m instanceof BorderLayout) { | |
| BorderLayout b = (BorderLayout)m; | |
| Object con = b.getConstraints(toolbar); | |
| if (con == SOUTH || con == EAST || con == WEST) { | |
| return con; | |
| } | |
| return NORTH; | |
| } | |
| } | |
| } | |
| return NORTH; | |
| } | |
| /** | |
| * Derives the ARGB value for a color based on an offset between two | |
| * other colors. | |
| * | |
| * @param color1 The first color | |
| * @param color2 The second color | |
| * @param midPoint The offset between color 1 and color 2, a value of 0.0 is | |
| * color 1 and 1.0 is color 2; | |
| * @return the ARGB value for a new color based on this derivation | |
| */ | |
| static int deriveARGB(Color color1, Color color2, float midPoint) { | |
| int r = color1.getRed() + | |
| Math.round((color2.getRed() - color1.getRed()) * midPoint); | |
| int g = color1.getGreen() + | |
| Math.round((color2.getGreen() - color1.getGreen()) * midPoint); | |
| int b = color1.getBlue() + | |
| Math.round((color2.getBlue() - color1.getBlue()) * midPoint); | |
| int a = color1.getAlpha() + | |
| Math.round((color2.getAlpha() - color1.getAlpha()) * midPoint); | |
| return ((a & 0xFF) << 24) | | |
| ((r & 0xFF) << 16) | | |
| ((g & 0xFF) << 8) | | |
| (b & 0xFF); | |
| } | |
| /** | |
| * Simple Symbolic Link style UIDefaults Property | |
| */ | |
| private static class LinkProperty implements UIDefaults.ActiveValue, UIResource{ | |
| private String dstPropName; | |
| private LinkProperty(String dstPropName) { | |
| this.dstPropName = dstPropName; | |
| } | |
| @Override | |
| public Object createValue(UIDefaults table) { | |
| return UIManager.get(dstPropName); | |
| } | |
| } | |
| /** | |
| * Nimbus Property that looks up Nimbus keys for standard key names. For | |
| * example "Button.background" --> "Button[Enabled].background" | |
| */ | |
| private class NimbusProperty implements UIDefaults.ActiveValue, UIResource { | |
| private String prefix; | |
| private String state = null; | |
| private String suffix; | |
| private boolean isFont; | |
| private NimbusProperty(String prefix, String suffix) { | |
| this.prefix = prefix; | |
| this.suffix = suffix; | |
| isFont = "font".equals(suffix); | |
| } | |
| private NimbusProperty(String prefix, String state, String suffix) { | |
| this(prefix,suffix); | |
| this.state = state; | |
| } | |
| /** | |
| * Creates the value retrieved from the <code>UIDefaults</code> table. | |
| * The object is created each time it is accessed. | |
| * | |
| * @param table a <code>UIDefaults</code> table | |
| * @return the created <code>Object</code> | |
| */ | |
| @Override | |
| public Object createValue(UIDefaults table) { | |
| Object obj = null; | |
| // check specified state | |
| if (state!=null){ | |
| obj = uiDefaults.get(prefix+"["+state+"]."+suffix); | |
| } | |
| // check enabled state | |
| if (obj==null){ | |
| obj = uiDefaults.get(prefix+"[Enabled]."+suffix); | |
| } | |
| // check for defaults | |
| if (obj==null){ | |
| if (isFont) { | |
| obj = uiDefaults.get("defaultFont"); | |
| } else { | |
| obj = uiDefaults.get(suffix); | |
| } | |
| } | |
| return obj; | |
| } | |
| } | |
| private Map<String, Map<String, Object>> compiledDefaults = null; | |
| private boolean defaultListenerAdded = false; | |
| static String parsePrefix(String key) { | |
| if (key == null) { | |
| return null; | |
| } | |
| boolean inquotes = false; | |
| for (int i = 0; i < key.length(); i++) { | |
| char c = key.charAt(i); | |
| if (c == '"') { | |
| inquotes = !inquotes; | |
| } else if ((c == '[' || c == '.') && !inquotes) { | |
| return key.substring(0, i); | |
| } | |
| } | |
| return null; | |
| } | |
| Map<String, Object> getDefaultsForPrefix(String prefix) { | |
| if (compiledDefaults == null) { | |
| compiledDefaults = new HashMap<String, Map<String, Object>>(); | |
| for (Map.Entry<Object, Object> entry: UIManager.getDefaults().entrySet()) { | |
| if (entry.getKey() instanceof String) { | |
| addDefault((String) entry.getKey(), entry.getValue()); | |
| } | |
| } | |
| if (! defaultListenerAdded) { | |
| UIManager.getDefaults().addPropertyChangeListener(defaultsListener); | |
| defaultListenerAdded = true; | |
| } | |
| } | |
| return compiledDefaults.get(prefix); | |
| } | |
| private void addDefault(String key, Object value) { | |
| if (compiledDefaults == null) { | |
| return; | |
| } | |
| String prefix = parsePrefix(key); | |
| if (prefix != null) { | |
| Map<String, Object> keys = compiledDefaults.get(prefix); | |
| if (keys == null) { | |
| keys = new HashMap<String, Object>(); | |
| compiledDefaults.put(prefix, keys); | |
| } | |
| keys.put(key, value); | |
| } | |
| } | |
| private class DefaultsListener implements PropertyChangeListener { | |
| @Override public void propertyChange(PropertyChangeEvent ev) { | |
| String key = ev.getPropertyName(); | |
| if ("UIDefaults".equals(key)) { | |
| compiledDefaults = null; | |
| } else { | |
| addDefault(key, ev.getNewValue()); | |
| } | |
| } | |
| } | |
| } |