diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTableModel.java index 910857a601..3caf8da13f 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTableModel.java @@ -23,7 +23,6 @@ import java.util.function.Supplier; import javax.swing.Icon; import javax.swing.JLabel; -import docking.theme.*; import docking.widgets.table.*; import generic.theme.*; import ghidra.docking.settings.Settings; @@ -194,13 +193,13 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel"; } if (resolvedColor.refId() != null) { - return resolvedColor.refId(); + return "[" + resolvedColor.refId() + "]"; } Color color = resolvedColor.color(); String text = WebColors.toString(color, false); String name = WebColors.toWebColorName(color); if (name != null) { - text += " [" + name + "]"; + text += " (" + name + ")"; } return text; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeDialog.java index 69c8ad2c5e..c83b3c5bf6 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeDialog.java @@ -94,7 +94,7 @@ public class ThemeDialog extends DialogComponentProvider { if (result == OptionDialog.YES_OPTION) { return ThemeUtils.saveThemeChanges(); } - Gui.reloadGhidraDefaults(); + Gui.restoreThemeValues(); } return true; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeFontTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeFontTableModel.java index 0eba5f16bb..e2e8db7b08 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeFontTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeFontTableModel.java @@ -74,6 +74,19 @@ public class ThemeFontTableModel extends GDynamicColumnTableModel"; + } + Font font = resolvedFont.font(); + String fontString = ThemeWriter.fontToString(font); + + if (resolvedFont.refId() != null) { + return "[" + resolvedFont.refId() + "]";//+ " [" + fontString + "]"; + } + return fontString; + } + class IdColumn extends AbstractDynamicTableColumn { @Override @@ -138,7 +151,7 @@ public class ThemeFontTableModel extends GDynamicColumnTableModel"; - } - Font font = resolvedFont.font(); - String fontString = ThemeWriter.fontToString(font); - - if (resolvedFont.refId() != null) { - return resolvedFont.refId() + " [" + fontString + "]"; - } - return fontString; - } - @Override public String getFilterString(ResolvedFont fontValue, Settings settings) { return getValueText(fontValue); diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/ColorChangedThemeEvent.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/ColorChangedThemeEvent.java index cc94d5092d..4f92124123 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/ColorChangedThemeEvent.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/ColorChangedThemeEvent.java @@ -20,18 +20,28 @@ package generic.theme; */ public class ColorChangedThemeEvent extends ThemeEvent { private final ColorValue color; + private final GThemeValueMap values; /** * Constructor + * @param values the set of theme values used to resolve indirect references * @param color the new {@link ColorValue} for the color id that changed */ - public ColorChangedThemeEvent(ColorValue color) { + public ColorChangedThemeEvent(GThemeValueMap values, ColorValue color) { + this.values = values; this.color = color; } @Override public boolean isColorChanged(String id) { - return id.equals(color.getId()); + if (id.equals(color.getId())) { + return true; + } + ColorValue testValue = values.getColor(id); + if (testValue == null) { + return false; + } + return testValue.inheritsFrom(color.getId(), values); } @Override diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/ColorValue.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/ColorValue.java index 1f73f2d7ed..4068bdec98 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/ColorValue.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/ColorValue.java @@ -41,9 +41,6 @@ public class ColorValue extends ThemeValue { */ public ColorValue(String id, Color value) { super(id, getRefId(value), getRawColor(value)); - if (value instanceof GColor) { - throw new IllegalArgumentException("Can't use GColor as the value!"); - } } /** diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/FontChangedThemeEvent.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/FontChangedThemeEvent.java index 5583c6ab2e..0cebe15464 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/FontChangedThemeEvent.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/FontChangedThemeEvent.java @@ -20,18 +20,28 @@ package generic.theme; */ public class FontChangedThemeEvent extends ThemeEvent { private final FontValue font; + private final GThemeValueMap values; /** * Constructor + * @param values the set of theme values used to resolve indirect references * @param font the new {@link FontValue} for the font id that changed */ - public FontChangedThemeEvent(FontValue font) { + public FontChangedThemeEvent(GThemeValueMap values, FontValue font) { + this.values = values; this.font = font; } @Override public boolean isFontChanged(String id) { - return id.equals(font.getId()); + if (id.equals(font.getId())) { + return true; + } + FontValue testValue = values.getFont(id); + if (testValue == null) { + return false; + } + return testValue.inheritsFrom(font.getId(), values); } @Override diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/Gui.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/Gui.java index bb17369aab..085fac5389 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/Gui.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/Gui.java @@ -39,6 +39,18 @@ import utilities.util.reflection.ReflectionUtilities; /** * Provides a static set of methods for globally managing application themes and their values. + *

+ * The basic idea is that all the colors, fonts, and icons used in an application should be + * accessed indirectly via an "id" string. Then the actual color, font, or icon can be changed + * without changing the source code. The default mapping of the id strings to a value is defined + * in .theme.properties files which are dynamically discovered by searching the module's + * data directory. Also, these files can optionally define a dark default value for an id which + * would replace the standard default value in the event that the current theme specifies that it + * is a dark theme. Themes are used to specify the application's {@link LookAndFeel}, whether or + * not it is dark, and any customized values for colors, fonts, or icons. There are several + * "built-in" themes, one for each supported {@link LookAndFeel}, but additional themes can + * be defined and stored in the users application home directory as a .theme file. + * */ public class Gui { public static final String THEME_DIR = "themes"; @@ -90,7 +102,7 @@ public class Gui { public static void reloadGhidraDefaults() { loadThemeDefaults(); buildCurrentValues(); - lookAndFeelManager.update(); + lookAndFeelManager.resetAll(javaDefaults); notifyThemeChanged(new AllValuesChangedThemeEvent(false)); } @@ -100,7 +112,7 @@ public class Gui { */ public static void restoreThemeValues() { buildCurrentValues(); - lookAndFeelManager.update(); + lookAndFeelManager.resetAll(javaDefaults); notifyThemeChanged(new AllValuesChangedThemeEvent(false)); } @@ -264,13 +276,13 @@ public class Gui { updateChangedValuesMap(currentValue, newValue); currentValues.addFont(newValue); - // all fonts are direct (there is no GFont), so to we need to update the - // UiDefaults for java fonts. Ghidra fonts are expected to be "on the fly" (they - // call Gui.getFont(id) for every use. + notifyThemeChanged(new FontChangedThemeEvent(currentValues, newValue)); + + // update all java LookAndFeel fonts affected by this changed String id = newValue.getId(); - boolean isJavaFont = javaDefaults.containsFont(id); - lookAndFeelManager.updateFont(id, newValue.get(currentValues), isJavaFont); - notifyThemeChanged(new FontChangedThemeEvent(newValue)); + Set affectedJavaFontIds = findAffectedJavaFontIds(id); + Font newFont = newValue.get(currentValues); + lookAndFeelManager.updateFonts(id, affectedJavaFontIds, newFont); } /** @@ -292,12 +304,11 @@ public class Gui { return; } updateChangedValuesMap(currentValue, newValue); - currentValues.addColor(newValue); - String id = newValue.getId(); - boolean isJavaColor = javaDefaults.containsColor(id); - lookAndFeelManager.updateColor(id, newValue.get(currentValues), isJavaColor); - notifyThemeChanged(new ColorChangedThemeEvent(newValue)); + notifyThemeChanged(new ColorChangedThemeEvent(currentValues, newValue)); + + // now update the ui + lookAndFeelManager.updateColors(); } /** @@ -321,10 +332,14 @@ public class Gui { updateChangedValuesMap(currentValue, newValue); currentValues.addIcon(newValue); + notifyThemeChanged(new IconChangedThemeEvent(currentValues, newValue)); + + // now update the ui + // update all java LookAndFeel icons affected by this changed String id = newValue.getId(); - boolean isJavaIcon = javaDefaults.containsIcon(id); - lookAndFeelManager.updateIcon(id, newValue.get(currentValues), isJavaIcon); - notifyThemeChanged(new IconChangedThemeEvent(newValue)); + Set affectedJavaIconIds = findAffectedJavaIconIds(id); + Icon newIcon = newValue.get(currentValues); + lookAndFeelManager.updateIcons(id, affectedJavaIconIds, newIcon); } /** @@ -378,14 +393,8 @@ public class Gui { * @return a fixed up version of the given map with relationships restored where possible */ public static GThemeValueMap fixupJavaDefaultsInheritence(GThemeValueMap map) { - List colors = map.getColors(); - JavaColorMapping mapping = new JavaColorMapping(); - for (ColorValue value : colors) { - ColorValue mapped = mapping.map(map, value); - if (mapped != null) { - map.addColor(mapped); - } - } + JavaColorMapping.fixupJavaDefaultsInheritence(map); + JavaFontMapping.fixupJavaDefaultsInheritence(map); return map; } @@ -717,4 +726,30 @@ public class Gui { changedValuesMap.addIcon(currentValue); } } + + private static Set findAffectedJavaFontIds(String id) { + Set affectedIds = new HashSet<>(); + List fonts = javaDefaults.getFonts(); + for (FontValue fontValue : fonts) { + String fontId = fontValue.getId(); + FontValue currentFontValue = currentValues.getFont(fontId); + if (fontId.equals(id) || currentFontValue.inheritsFrom(id, currentValues)) { + affectedIds.add(fontId); + } + } + return affectedIds; + } + + private static Set findAffectedJavaIconIds(String id) { + Set affectedIds = new HashSet<>(); + List icons = javaDefaults.getIcons(); + for (IconValue iconValue : icons) { + String iconId = iconValue.getId(); + if (iconId.equals(id) || iconValue.inheritsFrom(id, currentValues)) { + affectedIds.add(iconId); + } + } + return affectedIds; + } + } diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/IconChangedThemeEvent.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/IconChangedThemeEvent.java index 9ef103fa19..78524c0da7 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/IconChangedThemeEvent.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/IconChangedThemeEvent.java @@ -19,19 +19,28 @@ package generic.theme; * {@link ThemeEvent} for when an icon changes for exactly one icon id. */ public class IconChangedThemeEvent extends ThemeEvent { + private final GThemeValueMap values; private final IconValue icon; /** * Constructor * @param icon the new {@link IconValue} for the icon id that changed */ - public IconChangedThemeEvent(IconValue icon) { + public IconChangedThemeEvent(GThemeValueMap values, IconValue icon) { + this.values = values; this.icon = icon; } @Override public boolean isIconChanged(String id) { - return id.equals(icon.getId()); + if (id.equals(icon.getId())) { + return true; + } + IconValue testValue = values.getIcon(id); + if (testValue == null) { + return false; + } + return testValue.inheritsFrom(icon.getId(), values); } @Override diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/IconValue.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/IconValue.java index 790ab2fc13..4495c1da25 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/IconValue.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/IconValue.java @@ -29,7 +29,7 @@ import resources.ResourceManager; public class IconValue extends ThemeValue { static final String ICON_ID_PREFIX = "icon."; - public static final String LAST_RESORT_DEFAULT = "images/bomb.gif"; + public static final Icon LAST_RESORT_DEFAULT = ResourceManager.getDefaultIcon(); private static final String EXTERNAL_PREFIX = "[icon]"; @@ -42,10 +42,6 @@ public class IconValue extends ThemeValue { */ public IconValue(String id, Icon icon) { super(id, getRefId(icon), getRawIcon(icon)); - if (icon instanceof GIcon) { - throw new IllegalArgumentException("Can't use GIcon as the value!"); - } - } /** @@ -67,7 +63,7 @@ public class IconValue extends ThemeValue { protected Icon getUnresolvedReferenceValue(String id) { Msg.warn(this, "Could not resolve indirect icon path for" + id + ", using last resort default"); - return ResourceManager.getDefaultIcon(); + return LAST_RESORT_DEFAULT; } @Override diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/ThemeValue.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/ThemeValue.java index 10850bced3..b0953fc714 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/ThemeValue.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/ThemeValue.java @@ -71,33 +71,63 @@ public abstract class ThemeValue implements Comparable> { /** * Returns the T value for this instance, following references as needed. Uses the given * preferredValues map to resolve references. - * @param preferredValues the {@link GThemeValueMap} used to resolve references if this + * @param values the {@link GThemeValueMap} used to resolve references if this * instance doesn't have an actual value. * @return the T value for this instance, following references as needed. */ - public T get(GThemeValueMap preferredValues) { - return doGetValue(preferredValues); - } + public T get(GThemeValueMap values) { + if (value != null) { + return value; + } - private T doGetValue(GThemeValueMap values) { - ThemeValue result = this; Set visitedKeys = new HashSet<>(); - visitedKeys.add(id); // seed with my id, we don't want to see that key again + visitedKeys.add(id); + ThemeValue parent = getReferredValue(values, refId); // loop resolving indirect references - while (result != null) { - if (result.value != null) { - return result.value; + while (parent != null) { + if (parent.value != null) { + return parent.value; } - if (visitedKeys.contains(result.refId)) { + visitedKeys.add(parent.id); + if (visitedKeys.contains(parent.refId)) { Msg.warn(this, "Theme value reference loop detected for key: " + id); return getUnresolvedReferenceValue(id); } - result = getReferredValue(values, result.refId); + parent = getReferredValue(values, parent.refId); } return getUnresolvedReferenceValue(id); } + public boolean inheritsFrom(String ancestorId, GThemeValueMap values) { + if (refId == null) { + return false; + } + if (refId.equals(ancestorId)) { + return true; + } + + Set visitedKeys = new HashSet<>(); + visitedKeys.add(id); + ThemeValue parent = getReferredValue(values, refId); + + // loop resolving indirect references + while (parent != null) { + if (parent.refId == null) { + return false; + } + if (parent.refId.equals(ancestorId)) { + return true; + } + visitedKeys.add(parent.id); + if (visitedKeys.contains(parent.refId)) { + return false; + } + parent = getReferredValue(values, parent.refId); + } + return false; + } + /** * Returns the T to be used if the indirect reference couldn't be resolved. * @param unresolvedId the id that couldn't be resolved diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/builtin/JavaColorMapping.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/builtin/JavaColorMapping.java index 936dc4ef83..864e168a4b 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/builtin/JavaColorMapping.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/builtin/JavaColorMapping.java @@ -15,8 +15,10 @@ */ package generic.theme.builtin; +import static java.util.Map.*; + import java.awt.Color; -import java.util.HashMap; +import java.util.List; import java.util.Map; import generic.theme.ColorValue; @@ -26,203 +28,208 @@ import generic.theme.GThemeValueMap; * Maps Java UIDefaults color ids to parent color ids */ public class JavaColorMapping { - private Map map = new HashMap<>(); - - public JavaColorMapping() { - // color relationships mined from BasicLookAndFeel - map.put("Button.background", "control"); - map.put("Button.foreground", "controlText"); - map.put("Button.shadow", "controlShadow"); - map.put("Button.darkShadow", "controlDkShadow"); - map.put("Button.light", "controlHighlight"); - map.put("Button.highlight", "controlLtHighlight"); - map.put("ToggleButton.background", "control"); - map.put("ToggleButton.foreground", "controlText"); - map.put("ToggleButton.shadow", "controlShadow"); - map.put("ToggleButton.darkShadow", "controlDkShadow"); - map.put("ToggleButton.light", "controlHighlight"); - map.put("ToggleButton.highlight", "controlLtHighlight"); - map.put("RadioButton.background", "control"); - map.put("RadioButton.foreground", "controlText"); - map.put("RadioButton.shadow", "controlShadow"); - map.put("RadioButton.darkShadow", "controlDkShadow"); - map.put("RadioButton.light", "controlHighlight"); - map.put("RadioButton.highlight", "controlLtHighlight"); - map.put("CheckBox.background", "control"); - map.put("CheckBox.foreground", "controlText"); - map.put("ColorChooser.background", "control"); - map.put("ColorChooser.foreground", "controlText"); - map.put("ColorChooser.swatchesDefaultRecentColor", "control"); - map.put("ComboBox.background", "window"); - map.put("ComboBox.foreground", "textText"); - map.put("ComboBox.buttonBackground", "control"); - map.put("ComboBox.buttonShadow", "controlShadow"); - map.put("ComboBox.buttonDarkShadow", "controlDkShadow"); - map.put("ComboBox.buttonHighlight", "controlLtHighlight"); - map.put("ComboBox.selectionBackground", "textHighlight"); - map.put("ComboBox.selectionForeground", "textHighlightText"); - map.put("ComboBox.disabledBackground", "control"); - map.put("ComboBox.disabledForeground", "textHInactiveText"); - map.put("InternalFrame.borderColor", "control"); - map.put("InternalFrame.borderShadow", "controlShadow"); - map.put("InternalFrame.borderDarkShadow", "controlDkShadow"); - map.put("InternalFrame.borderHighlight", "controlLtHighlight"); - map.put("InternalFrame.borderLight", "controlHighlight"); - map.put("InternalFrame.activeTitleBackground", "activeCaption"); - map.put("InternalFrame.activeTitleForeground", "activeCaptionText"); - map.put("InternalFrame.inactiveTitleBackground", "inactiveCaption"); - map.put("InternalFrame.inactiveTitleForeground", "inactiveCaptionText"); - map.put("Label.background", "control"); - map.put("Label.foreground", "controlText"); - map.put("Label.disabledShadow", "controlShadow"); - map.put("List.background", "window"); - map.put("List.foreground", "textText"); - map.put("List.selectionBackground", "textHighlight"); - map.put("List.selectionForeground", "textHighlightText"); - map.put("List.dropLineColor", "controlShadow"); - map.put("MenuBar.background", "menu"); - map.put("MenuBar.foreground", "menuText"); - map.put("MenuBar.shadow", "controlShadow"); - map.put("MenuBar.highlight", "controlLtHighlight"); - map.put("MenuItem.background", "menu"); - map.put("MenuItem.foreground", "menuText"); - map.put("MenuItem.selectionForeground", "textHighlightText"); - map.put("MenuItem.selectionBackground", "textHighlight"); - map.put("MenuItem.acceleratorForeground", "menuText"); - map.put("MenuItem.acceleratorSelectionForeground", "textHighlightText"); - map.put("RadioButtonMenuItem.background", "menu"); - map.put("RadioButtonMenuItem.foreground", "menuText"); - map.put("RadioButtonMenuItem.selectionForeground", "textHighlightText"); - map.put("RadioButtonMenuItem.selectionBackground", "textHighlight"); - map.put("RadioButtonMenuItem.acceleratorForeground", "menuText"); - map.put("RadioButtonMenuItem.acceleratorSelectionForeground", "textHighlightText"); - map.put("CheckBoxMenuItem.background", "menu"); - map.put("CheckBoxMenuItem.foreground", "menuText"); - map.put("CheckBoxMenuItem.selectionForeground", "textHighlightText"); - map.put("CheckBoxMenuItem.selectionBackground", "textHighlight"); - map.put("CheckBoxMenuItem.acceleratorForeground", "menuText"); - map.put("CheckBoxMenuItem.acceleratorSelectionForeground", "textHighlightText"); - map.put("Menu.background", "menu"); - map.put("Menu.foreground", "menuText"); - map.put("Menu.selectionForeground", "textHighlightText"); - map.put("Menu.selectionBackground", "textHighlight"); - map.put("Menu.acceleratorForeground", "menuText"); - map.put("Menu.acceleratorSelectionForeground", "textHighlightText"); - map.put("PopupMenu.background", "menu"); - map.put("PopupMenu.foreground", "menuText"); - map.put("OptionPane.background", "control"); - map.put("OptionPane.foreground", "controlText"); - map.put("OptionPane.messageForeground", "controlText"); - map.put("Panel.background", "control"); - map.put("Panel.foreground", "textText"); - map.put("ProgressBar.foreground", "textHighlight"); - map.put("ProgressBar.background", "control"); - map.put("ProgressBar.selectionForeground", "control"); - map.put("ProgressBar.selectionBackground", "textHighlight"); - map.put("Separator.background", "controlLtHighlight"); - map.put("Separator.foreground", "controlShadow"); - map.put("ScrollBar.foreground", "control"); - map.put("ScrollBar.track", "scrollbar"); - map.put("ScrollBar.trackHighlight", "controlDkShadow"); - map.put("ScrollBar.thumb", "control"); - map.put("ScrollBar.thumbHighlight", "controlLtHighlight"); - map.put("ScrollBar.thumbDarkShadow", "controlDkShadow"); - map.put("ScrollBar.thumbShadow", "controlShadow"); - map.put("ScrollPane.background", "control"); - map.put("ScrollPane.foreground", "controlText"); - map.put("Viewport.background", "control"); - map.put("Viewport.foreground", "textText"); - map.put("Slider.foreground", "control"); - map.put("Slider.background", "control"); - map.put("Slider.highlight", "controlLtHighlight"); - map.put("Slider.shadow", "controlShadow"); - map.put("Slider.focus", "controlDkShadow"); - map.put("Spinner.background", "control"); - map.put("Spinner.foreground", "control"); - map.put("SplitPane.background", "control"); - map.put("SplitPane.highlight", "controlLtHighlight"); - map.put("SplitPane.shadow", "controlShadow"); - map.put("SplitPane.darkShadow", "controlDkShadow"); - map.put("TabbedPane.background", "control"); - map.put("TabbedPane.foreground", "controlText"); - map.put("TabbedPane.highlight", "controlLtHighlight"); - map.put("TabbedPane.light", "controlHighlight"); - map.put("TabbedPane.shadow", "controlShadow"); - map.put("TabbedPane.darkShadow", "controlDkShadow"); - map.put("TabbedPane.focus", "controlText"); - map.put("Table.foreground", "controlText"); - map.put("Table.background", "window"); - map.put("Table.selectionForeground", "textHighlightText"); - map.put("Table.selectionBackground", "textHighlight"); - map.put("Table.dropLineColor", "controlShadow"); - map.put("Table.focusCellBackground", "window"); - map.put("Table.focusCellForeground", "controlText"); - map.put("TableHeader.foreground", "controlText"); - map.put("TableHeader.background", "control"); - map.put("TableHeader.focusCellBackground", "text"); - map.put("TextField.background", "window"); - map.put("TextField.foreground", "textText"); - map.put("TextField.shadow", "controlShadow"); - map.put("TextField.darkShadow", "controlDkShadow"); - map.put("TextField.light", "controlHighlight"); - map.put("TextField.highlight", "controlLtHighlight"); - map.put("TextField.inactiveForeground", "textHInactiveText"); - map.put("TextField.inactiveBackground", "control"); - map.put("TextField.selectionBackground", "textHighlight"); - map.put("TextField.selectionForeground", "textHighlightText"); - map.put("TextField.caretForeground", "textText"); - map.put("FormattedTextField.background", "window"); - map.put("FormattedTextField.foreground", "textText"); - map.put("FormattedTextField.inactiveForeground", "textHInactiveText"); - map.put("FormattedTextField.inactiveBackground", "control"); - map.put("FormattedTextField.selectionBackground", "textHighlight"); - map.put("FormattedTextField.selectionForeground", "textHighlightText"); - map.put("FormattedTextField.caretForeground", "textText"); - map.put("PasswordField.background", "window"); - map.put("PasswordField.foreground", "textText"); - map.put("PasswordField.inactiveForeground", "textHInactiveText"); - map.put("PasswordField.inactiveBackground", "control"); - map.put("PasswordField.selectionBackground", "textHighlight"); - map.put("PasswordField.selectionForeground", "textHighlightText"); - map.put("PasswordField.caretForeground", "textText"); - map.put("TextArea.background", "window"); - map.put("TextArea.foreground", "textText"); - map.put("TextArea.inactiveForeground", "textHInactiveText"); - map.put("TextArea.selectionBackground", "textHighlight"); - map.put("TextArea.selectionForeground", "textHighlightText"); - map.put("TextArea.caretForeground", "textText"); - map.put("TextPane.foreground", "textText"); - map.put("TextPane.selectionBackground", "textHighlight"); - map.put("TextPane.selectionForeground", "textHighlightText"); - map.put("TextPane.caretForeground", "textText"); - map.put("TextPane.inactiveForeground", "textHInactiveText"); - map.put("EditorPane.foreground", "textText"); - map.put("EditorPane.selectionBackground", "textHighlight"); - map.put("EditorPane.selectionForeground", "textHighlightText"); - map.put("EditorPane.caretForeground", "textText"); - map.put("EditorPane.inactiveForeground", "textHInactiveText"); - map.put("TitledBorder.titleColor", "controlText"); - map.put("ToolBar.background", "control"); - map.put("ToolBar.foreground", "controlText"); - map.put("ToolBar.shadow", "controlShadow"); - map.put("ToolBar.darkShadow", "controlDkShadow"); - map.put("ToolBar.light", "controlHighlight"); - map.put("ToolBar.highlight", "controlLtHighlight"); - map.put("ToolBar.dockingBackground", "control"); - map.put("ToolBar.floatingBackground", "control"); - map.put("ToolTip.background", "info"); - map.put("ToolTip.foreground", "infoText"); - map.put("Tree.background", "window"); - map.put("Tree.foreground", "textText"); - map.put("Tree.textForeground", "textText"); - map.put("Tree.textBackground", "text"); - map.put("Tree.selectionForeground", "textHighlightText"); - map.put("Tree.selectionBackground", "textHighlight"); - map.put("Tree.dropLineColor", "controlShadow"); + private static Map map = Map.ofEntries( + entry("Button.background", "control"), + entry("Button.foreground", "controlText"), + entry("Button.shadow", "controlShadow"), + entry("Button.darkShadow", "controlDkShadow"), + entry("Button.light", "controlHighlight"), + entry("Button.highlight", "controlLtHighlight"), + entry("ToggleButton.background", "control"), + entry("ToggleButton.foreground", "controlText"), + entry("ToggleButton.shadow", "controlShadow"), + entry("ToggleButton.darkShadow", "controlDkShadow"), + entry("ToggleButton.light", "controlHighlight"), + entry("ToggleButton.highlight", "controlLtHighlight"), + entry("RadioButton.background", "control"), + entry("RadioButton.foreground", "controlText"), + entry("RadioButton.shadow", "controlShadow"), + entry("RadioButton.darkShadow", "controlDkShadow"), + entry("RadioButton.light", "controlHighlight"), + entry("RadioButton.highlight", "controlLtHighlight"), + entry("CheckBox.background", "control"), + entry("CheckBox.foreground", "controlText"), + entry("ColorChooser.background", "control"), + entry("ColorChooser.foreground", "controlText"), + entry("ColorChooser.swatchesDefaultRecentColor", "control"), + entry("ComboBox.background", "window"), + entry("ComboBox.foreground", "textText"), + entry("ComboBox.buttonBackground", "control"), + entry("ComboBox.buttonShadow", "controlShadow"), + entry("ComboBox.buttonDarkShadow", "controlDkShadow"), + entry("ComboBox.buttonHighlight", "controlLtHighlight"), + entry("ComboBox.selectionBackground", "textHighlight"), + entry("ComboBox.selectionForeground", "textHighlightText"), + entry("ComboBox.disabledBackground", "control"), + entry("ComboBox.disabledForeground", "textHInactiveText"), + entry("InternalFrame.borderColor", "control"), + entry("InternalFrame.borderShadow", "controlShadow"), + entry("InternalFrame.borderDarkShadow", "controlDkShadow"), + entry("InternalFrame.borderHighlight", "controlLtHighlight"), + entry("InternalFrame.borderLight", "controlHighlight"), + entry("InternalFrame.activeTitleBackground", "activeCaption"), + entry("InternalFrame.activeTitleForeground", "activeCaptionText"), + entry("InternalFrame.inactiveTitleBackground", "inactiveCaption"), + entry("InternalFrame.inactiveTitleForeground", "inactiveCaptionText"), + entry("Label.background", "control"), + entry("Label.foreground", "controlText"), + entry("Label.disabledShadow", "controlShadow"), + entry("List.background", "window"), + entry("List.foreground", "textText"), + entry("List.selectionBackground", "textHighlight"), + entry("List.selectionForeground", "textHighlightText"), + entry("List.dropLineColor", "controlShadow"), + entry("MenuBar.background", "menu"), + entry("MenuBar.foreground", "menuText"), + entry("MenuBar.shadow", "controlShadow"), + entry("MenuBar.highlight", "controlLtHighlight"), + entry("MenuItem.background", "menu"), + entry("MenuItem.foreground", "menuText"), + entry("MenuItem.selectionForeground", "textHighlightText"), + entry("MenuItem.selectionBackground", "textHighlight"), + entry("MenuItem.acceleratorForeground", "menuText"), + entry("MenuItem.acceleratorSelectionForeground", "textHighlightText"), + entry("RadioButtonMenuItem.background", "menu"), + entry("RadioButtonMenuItem.foreground", "menuText"), + entry("RadioButtonMenuItem.selectionForeground", "textHighlightText"), + entry("RadioButtonMenuItem.selectionBackground", "textHighlight"), + entry("RadioButtonMenuItem.acceleratorForeground", "menuText"), + entry("RadioButtonMenuItem.acceleratorSelectionForeground", "textHighlightText"), + entry("CheckBoxMenuItem.background", "menu"), + entry("CheckBoxMenuItem.foreground", "menuText"), + entry("CheckBoxMenuItem.selectionForeground", "textHighlightText"), + entry("CheckBoxMenuItem.selectionBackground", "textHighlight"), + entry("CheckBoxMenuItem.acceleratorForeground", "menuText"), + entry("CheckBoxMenuItem.acceleratorSelectionForeground", "textHighlightText"), + entry("Menu.background", "menu"), + entry("Menu.foreground", "menuText"), + entry("Menu.selectionForeground", "textHighlightText"), + entry("Menu.selectionBackground", "textHighlight"), + entry("Menu.acceleratorForeground", "menuText"), + entry("Menu.acceleratorSelectionForeground", "textHighlightText"), + entry("PopupMenu.background", "menu"), + entry("PopupMenu.foreground", "menuText"), + entry("OptionPane.background", "control"), + entry("OptionPane.foreground", "controlText"), + entry("OptionPane.messageForeground", "controlText"), + entry("Panel.background", "control"), + entry("Panel.foreground", "textText"), + entry("ProgressBar.foreground", "textHighlight"), + entry("ProgressBar.background", "control"), + entry("ProgressBar.selectionForeground", "control"), + entry("ProgressBar.selectionBackground", "textHighlight"), + entry("Separator.background", "controlLtHighlight"), + entry("Separator.foreground", "controlShadow"), + entry("ScrollBar.foreground", "control"), + entry("ScrollBar.track", "scrollbar"), + entry("ScrollBar.trackHighlight", "controlDkShadow"), + entry("ScrollBar.thumb", "control"), + entry("ScrollBar.thumbHighlight", "controlLtHighlight"), + entry("ScrollBar.thumbDarkShadow", "controlDkShadow"), + entry("ScrollBar.thumbShadow", "controlShadow"), + entry("ScrollPane.background", "control"), + entry("ScrollPane.foreground", "controlText"), + entry("Viewport.background", "control"), + entry("Viewport.foreground", "textText"), + entry("Slider.foreground", "control"), + entry("Slider.background", "control"), + entry("Slider.highlight", "controlLtHighlight"), + entry("Slider.shadow", "controlShadow"), + entry("Slider.focus", "controlDkShadow"), + entry("Spinner.background", "control"), + entry("Spinner.foreground", "control"), + entry("SplitPane.background", "control"), + entry("SplitPane.highlight", "controlLtHighlight"), + entry("SplitPane.shadow", "controlShadow"), + entry("SplitPane.darkShadow", "controlDkShadow"), + entry("TabbedPane.background", "control"), + entry("TabbedPane.foreground", "controlText"), + entry("TabbedPane.highlight", "controlLtHighlight"), + entry("TabbedPane.light", "controlHighlight"), + entry("TabbedPane.shadow", "controlShadow"), + entry("TabbedPane.darkShadow", "controlDkShadow"), + entry("TabbedPane.focus", "controlText"), + entry("Table.foreground", "controlText"), + entry("Table.background", "window"), + entry("Table.selectionForeground", "textHighlightText"), + entry("Table.selectionBackground", "textHighlight"), + entry("Table.dropLineColor", "controlShadow"), + entry("Table.focusCellBackground", "window"), + entry("Table.focusCellForeground", "controlText"), + entry("TableHeader.foreground", "controlText"), + entry("TableHeader.background", "control"), + entry("TableHeader.focusCellBackground", "text"), + entry("TextField.background", "window"), + entry("TextField.foreground", "textText"), + entry("TextField.shadow", "controlShadow"), + entry("TextField.darkShadow", "controlDkShadow"), + entry("TextField.light", "controlHighlight"), + entry("TextField.highlight", "controlLtHighlight"), + entry("TextField.inactiveForeground", "textHInactiveText"), + entry("TextField.inactiveBackground", "control"), + entry("TextField.selectionBackground", "textHighlight"), + entry("TextField.selectionForeground", "textHighlightText"), + entry("TextField.caretForeground", "textText"), + entry("FormattedTextField.background", "window"), + entry("FormattedTextField.foreground", "textText"), + entry("FormattedTextField.inactiveForeground", "textHInactiveText"), + entry("FormattedTextField.inactiveBackground", "control"), + entry("FormattedTextField.selectionBackground", "textHighlight"), + entry("FormattedTextField.selectionForeground", "textHighlightText"), + entry("FormattedTextField.caretForeground", "textText"), + entry("PasswordField.background", "window"), + entry("PasswordField.foreground", "textText"), + entry("PasswordField.inactiveForeground", "textHInactiveText"), + entry("PasswordField.inactiveBackground", "control"), + entry("PasswordField.selectionBackground", "textHighlight"), + entry("PasswordField.selectionForeground", "textHighlightText"), + entry("PasswordField.caretForeground", "textText"), + entry("TextArea.background", "window"), + entry("TextArea.foreground", "textText"), + entry("TextArea.inactiveForeground", "textHInactiveText"), + entry("TextArea.selectionBackground", "textHighlight"), + entry("TextArea.selectionForeground", "textHighlightText"), + entry("TextArea.caretForeground", "textText"), + entry("TextPane.foreground", "textText"), + entry("TextPane.selectionBackground", "textHighlight"), + entry("TextPane.selectionForeground", "textHighlightText"), + entry("TextPane.caretForeground", "textText"), + entry("TextPane.inactiveForeground", "textHInactiveText"), + entry("EditorPane.foreground", "textText"), + entry("EditorPane.selectionBackground", "textHighlight"), + entry("EditorPane.selectionForeground", "textHighlightText"), + entry("EditorPane.caretForeground", "textText"), + entry("EditorPane.inactiveForeground", "textHInactiveText"), + entry("TitledBorder.titleColor", "controlText"), + entry("ToolBar.background", "control"), + entry("ToolBar.foreground", "controlText"), + entry("ToolBar.shadow", "controlShadow"), + entry("ToolBar.darkShadow", "controlDkShadow"), + entry("ToolBar.light", "controlHighlight"), + entry("ToolBar.highlight", "controlLtHighlight"), + entry("ToolBar.dockingBackground", "control"), + entry("ToolBar.floatingBackground", "control"), + entry("ToolTip.background", "info"), + entry("ToolTip.foreground", "infoText"), + entry("Tree.background", "window"), + entry("Tree.foreground", "textText"), + entry("Tree.textForeground", "textText"), + entry("Tree.textBackground", "text"), + entry("Tree.selectionForeground", "textHighlightText"), + entry("Tree.selectionBackground", "textHighlight"), + entry("Tree.dropLineColor", "controlShadow")); + public static void fixupJavaDefaultsInheritence(GThemeValueMap values) { + List colors = values.getColors(); + for (ColorValue value : colors) { + ColorValue mapped = map(values, value); + if (mapped != null) { + values.addColor(mapped); + } + } } - public ColorValue map(GThemeValueMap values, ColorValue value) { + private static ColorValue map(GThemeValueMap values, ColorValue value) { String id = value.getId(); String refId = map.get(id); if (refId == null) { diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/builtin/JavaFontMapping.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/builtin/JavaFontMapping.java new file mode 100644 index 0000000000..9d7c024534 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/builtin/JavaFontMapping.java @@ -0,0 +1,143 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme.builtin; + +import static java.util.Map.*; + +import java.awt.Font; +import java.util.List; +import java.util.Map; + +import generic.theme.FontValue; +import generic.theme.GThemeValueMap; + +/** + * Maps Java UIDefaults color ids to parent color ids + */ +public class JavaFontMapping { + private final static String BUTTON_GROUP = "ButtonComponents.font"; + private final static String TEXT_GROUP = "TextComponents.font"; + private final static String MENU_GROUP = "MenuComponents.font"; + private final static String MENU_ACCELERATOR_GROUP = "MenuComponents.acceleratorFont"; + private final static String DIALOG_GROUP = "Dialogs.font"; + private final static String WIDGET_GROUP = "Components.font"; + + private static Map map = Map.ofEntries( + entry("ArrowButton.font", BUTTON_GROUP), // nimbus + entry("Button.font", BUTTON_GROUP), + entry("CheckBox.font", BUTTON_GROUP), + entry("RadioButton.font", BUTTON_GROUP), + entry("ToggleButton.font", BUTTON_GROUP), + + entry("CheckBoxMenuItem.font", MENU_GROUP), + entry("Menu.font", MENU_GROUP), + entry("MenuBar.font", MENU_GROUP), + entry("MenuItem.font", MENU_GROUP), + entry("PopupMenu.font", MENU_GROUP), + entry("RadioButtonMenuItem.font", MENU_GROUP), + + entry("CheckBoxMenuItem.acceleratorFont", MENU_ACCELERATOR_GROUP), // metal, motif + entry("Menu.acceleratorFont", MENU_ACCELERATOR_GROUP), // metal, motif + entry("MenuItem.acceleratorFont", MENU_ACCELERATOR_GROUP), // metal, motfi + entry("RadioButtonMenuItem.acceleratorFont", MENU_ACCELERATOR_GROUP), // metal + + entry("EditorPane.font", TEXT_GROUP), + entry("FormattedTextField.font", TEXT_GROUP), + entry("PasswordField.font", TEXT_GROUP), + entry("TextArea.font", TEXT_GROUP), + entry("TextField.font", TEXT_GROUP), + entry("TextPane.font", TEXT_GROUP), + + entry("ColorChooser.font", DIALOG_GROUP), + entry("FileChooser.font", DIALOG_GROUP), // nimbus + + entry("ComboBox.font", WIDGET_GROUP), + entry("InternalFrame.titleFont", WIDGET_GROUP), // metal, motif, flat + entry("Label.font", WIDGET_GROUP), + entry("List.font", WIDGET_GROUP), + entry("OptionPane.font", DIALOG_GROUP), + entry("Panel.font", WIDGET_GROUP), + entry("ProgressBar.font", WIDGET_GROUP), + entry("RootPane.font", WIDGET_GROUP), + entry("Scrollbar.font", WIDGET_GROUP), + entry("ScrollBarThumb.font", WIDGET_GROUP), // nimbus + entry("ScrollBarTrack.font", WIDGET_GROUP), // nimbus + entry("ScrollPane.font", WIDGET_GROUP), + entry("Separator.font", WIDGET_GROUP), // nimbus + entry("Slider.font", WIDGET_GROUP), + entry("SliderThumb.font", WIDGET_GROUP), // nimbus + entry("SliderTrack.font", WIDGET_GROUP), // nimbus + entry("Spinner.font", WIDGET_GROUP), + entry("SplitPane.font", WIDGET_GROUP), // nimbus + entry("TabbedPane.font", WIDGET_GROUP), + entry("TitledBorder.font", WIDGET_GROUP), + entry("ToolBar.font", WIDGET_GROUP), + entry("ToolTip.font", TEXT_GROUP), + entry("Viewport.font", WIDGET_GROUP), + + entry("Tree.font", WIDGET_GROUP), + entry("Table.font", WIDGET_GROUP), + entry("TableHeader.font", "Table.font")); + + public static void fixupJavaDefaultsInheritence(GThemeValueMap values) { + createGroupDefaults(values); + List fonts = values.getFonts(); + for (FontValue value : fonts) { + FontValue mapped = map(values, value); + if (mapped != null) { + values.addFont(mapped); + } + } + } + + private static FontValue map(GThemeValueMap values, FontValue value) { + String id = value.getId(); + String refId = map.get(id); + if (refId == null) { + return null; + } + FontValue refValue = values.getFont(refId); + if (refValue == null) { + return null; + } + Font originalFont = value.get(values); + Font refFont = refValue.get(values); + if (originalFont == null || refFont == null) { + return null; + } + if (originalFont.equals(refFont)) { + return new FontValue(id, refId); + } + return null; + } + + public static void createGroupDefaults(GThemeValueMap valuesMap) { + addFontValue(valuesMap, BUTTON_GROUP, "Button.font"); + addFontValue(valuesMap, TEXT_GROUP, "TextField.font"); + addFontValue(valuesMap, MENU_GROUP, "Menu.font"); + addFontValue(valuesMap, MENU_ACCELERATOR_GROUP, "Menu.acceleratorFont"); + addFontValue(valuesMap, DIALOG_GROUP, "ColorChooser.font"); + addFontValue(valuesMap, WIDGET_GROUP, "Label.font"); + } + + private static void addFontValue(GThemeValueMap valuesMap, String groupId, String exemplarId) { + FontValue font = valuesMap.getFont(exemplarId); + if (font != null) { + valuesMap.addFont(new FontValue(groupId, font.get(valuesMap))); + } + } + +} diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/GNimbusLookAndFeel.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/GNimbusLookAndFeel.java new file mode 100644 index 0000000000..af1d41158f --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/GNimbusLookAndFeel.java @@ -0,0 +1,94 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme.laf; + +import java.awt.Color; +import java.awt.Font; +import java.util.List; + +import javax.swing.Icon; +import javax.swing.UIDefaults; +import javax.swing.plaf.nimbus.NimbusLookAndFeel; + +import generic.theme.*; + +/** + * Extends the NimbusLookAndFeel to intercept the {@link #getDefaults()}. To get Nimbus + * to use our indirect values, we have to get in early. + */ +public class GNimbusLookAndFeel extends NimbusLookAndFeel { + + @Override + public UIDefaults getDefaults() { + UIDefaults defaults = super.getDefaults(); + GThemeValueMap javaDefaults = extractJavaDefaults(defaults); + + // replace all colors with GColors + for (ColorValue colorValue : javaDefaults.getColors()) { + String id = colorValue.getId(); + defaults.put(id, Gui.getGColorUiResource(id)); + } + + // only replace fonts that have been changed by the theme + for (FontValue fontValue : javaDefaults.getFonts()) { + String id = fontValue.getId(); + Font font = Gui.getFont(id); + defaults.put(id, font); + } + + // only replace icons that have been changed by the theme + for (IconValue iconValue : javaDefaults.getIcons()) { + String id = iconValue.getId(); + Icon icon = Gui.getRawIcon(id, true); + defaults.put(id, icon); + } + + defaults.put("Label.textForeground", Gui.getGColorUiResource("Label.foreground")); + GColor.refreshAll(); + GIcon.refreshAll(); + return defaults; + } + + protected GThemeValueMap extractJavaDefaults(UIDefaults defaults) { + GThemeValueMap javaDefaults = new GThemeValueMap(); + + List colorIds = + LookAndFeelInstaller.getLookAndFeelIdsForType(defaults, Color.class); + for (String id : colorIds) { + Color color = defaults.getColor(id); + ColorValue value = new ColorValue(id, color); + javaDefaults.addColor(value); + } + List fontIds = + LookAndFeelInstaller.getLookAndFeelIdsForType(defaults, Font.class); + for (String id : fontIds) { + Font font = defaults.getFont(id); + FontValue value = new FontValue(id, font); + javaDefaults.addFont(value); + } + List iconIds = + LookAndFeelInstaller.getLookAndFeelIdsForType(defaults, Icon.class); + for (String id : iconIds) { + Icon icon = defaults.getIcon(id); + javaDefaults.addIcon(new IconValue(id, icon)); + } + // need to set javaDefalts now to trigger building currentValues so the when + // we create GColors below, they can be resolved. + Gui.setJavaDefaults(javaDefaults); + + return javaDefaults; + } +} diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/LookAndFeelManager.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/LookAndFeelManager.java index eb92e37dd2..c529a16a1c 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/LookAndFeelManager.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/LookAndFeelManager.java @@ -15,7 +15,10 @@ */ package generic.theme.laf; -import java.awt.*; +import java.awt.Font; +import java.awt.Window; +import java.util.List; +import java.util.Set; import javax.swing.*; @@ -46,43 +49,69 @@ public abstract class LookAndFeelManager { updateComponentUis(); } - public void update() { + public void resetAll(GThemeValueMap javaDefaults) { GColor.refreshAll(); GIcon.refreshAll(); + resetIcons(javaDefaults); + resetFonts(javaDefaults); updateComponentUis(); -// repaintAll(); } - public void updateColor(String id, Color color, boolean isJavaColor) { + private void resetFonts(GThemeValueMap javaDefaults) { + List fonts = javaDefaults.getFonts(); + UIDefaults defaults = UIManager.getDefaults(); + for (FontValue fontValue : fonts) { + String id = fontValue.getId(); + Font correctFont = Gui.getFont(id); + Font storedFont = defaults.getFont(id); + if (correctFont != null && !correctFont.equals(storedFont)) { + defaults.put(id, correctFont); + } + } + } + + private void resetIcons(GThemeValueMap javaDefaults) { + List icons = javaDefaults.getIcons(); + UIDefaults defaults = UIManager.getDefaults(); + for (IconValue iconValue : icons) { + String id = iconValue.getId(); + Icon correctIcon = Gui.getRawIcon(id, false); + Icon storedIcon = defaults.getIcon(id); + if (correctIcon != null && !correctIcon.equals(storedIcon)) { + defaults.put(id, correctIcon); + } + } + } + + public void updateColors() { GColor.refreshAll(); repaintAll(); } - public void updateIcon(String id, Icon icon, boolean isJavaIcon) { - // Icons are a mixed bag. Java Icons are direct and Ghidra Icons are indirect (to support static use) - // Mainly because Nimbus is buggy and can't handle non-nimbus Icons, so we can't wrap them - // So need to update UiDefaults for java icons. For Ghidra Icons, it is sufficient to refrech - // GIcons and repaint - if (isJavaIcon) { - UIManager.getDefaults().put(id, icon); + public void updateIcons(String id, Set affectedJavaIds, Icon newIcon) { + if (!affectedJavaIds.isEmpty()) { + UIDefaults defaults = UIManager.getDefaults(); + for (String javaIconId : affectedJavaIds) { + defaults.put(javaIconId, newIcon); + } updateComponentUis(); } GIcon.refreshAll(); repaintAll(); } - public void updateFont(String id, Font font, boolean isJavaFont) { - if (isJavaFont) { - UIManager.getDefaults().put(id, font); + public void updateFonts(String id, Set affectedJavaIds, Font newFont) { + if (!affectedJavaIds.isEmpty()) { + UIDefaults defaults = UIManager.getDefaults(); + for (String javaFontId : affectedJavaIds) { + defaults.put(javaFontId, newFont); + } updateComponentUis(); } - else { - repaintAll(); - } - + repaintAll(); } - private void updateComponentUis() { + protected void updateComponentUis() { for (Window window : Window.getWindows()) { SwingUtilities.updateComponentTreeUI(window); } diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/NimbusLookAndFeelInstaller.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/NimbusLookAndFeelInstaller.java index 520fcdb9e2..78c5b36ad6 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/NimbusLookAndFeelInstaller.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/NimbusLookAndFeelInstaller.java @@ -15,11 +15,9 @@ */ package generic.theme.laf; -import java.awt.*; -import java.util.List; +import java.awt.Dimension; import javax.swing.*; -import javax.swing.plaf.nimbus.NimbusLookAndFeel; import generic.theme.*; @@ -37,7 +35,7 @@ public class NimbusLookAndFeelInstaller extends LookAndFeelInstaller { @Override protected void installJavaDefaults() { // even though java defaults have been installed, we need to fix them up now - // that nimbus has finished initializing + // that Nimbus has finished initializing GColor.refreshAll(); Gui.setJavaDefaults(Gui.getJavaDefaults()); } @@ -53,77 +51,4 @@ public class NimbusLookAndFeelInstaller extends LookAndFeelInstaller { // (see NimbusDefaults for key values that can be changed here) } - - /** - * Extends the NimbusLookAndFeel to intercept the {@link #getDefaults()}. To get Nimbus - * to use our indirect values, we have to get in early. - */ - static class GNimbusLookAndFeel extends NimbusLookAndFeel { - - @Override - public UIDefaults getDefaults() { - UIDefaults defaults = super.getDefaults(); - GThemeValueMap javaDefaults = extractJavaDefaults(defaults); - - // need to set javaDefalts now to trigger building currentValues so the when - // we create GColors below, they can be resolved. - Gui.setJavaDefaults(javaDefaults); - - // replace all colors with GColors - for (ColorValue colorValue : javaDefaults.getColors()) { - String id = colorValue.getId(); - defaults.put(id, Gui.getGColorUiResource(id)); - } - - GTheme theme = Gui.getActiveTheme(); - - // only replace fonts that have been changed by the theme - for (FontValue fontValue : theme.getFonts()) { - String id = fontValue.getId(); - Font font = Gui.getFont(id); - defaults.put(id, font); - } - - // only replace icons that have been changed by the theme - for (IconValue iconValue : theme.getIcons()) { - String id = iconValue.getId(); - Icon icon = Gui.getRawIcon(id, true); - defaults.put(id, icon); - } - - defaults.put("Label.textForeground", Gui.getGColorUiResource("Label.foreground")); - GColor.refreshAll(); - GIcon.refreshAll(); - return defaults; - } - - private GThemeValueMap extractJavaDefaults(UIDefaults defaults) { - GThemeValueMap javaDefaults = new GThemeValueMap(); - - List colorIds = - LookAndFeelInstaller.getLookAndFeelIdsForType(defaults, Color.class); - for (String id : colorIds) { - Color color = defaults.getColor(id); - ColorValue value = new ColorValue(id, color); - javaDefaults.addColor(value); - } - List fontIds = - LookAndFeelInstaller.getLookAndFeelIdsForType(defaults, Font.class); - for (String id : fontIds) { - Font font = defaults.getFont(id); - FontValue value = new FontValue(id, font); - javaDefaults.addFont(value); - } - List iconIds = - LookAndFeelInstaller.getLookAndFeelIdsForType(defaults, Icon.class); - for (String id : iconIds) { - Icon icon = defaults.getIcon(id); - javaDefaults.addIcon(new IconValue(id, icon)); - } - - return javaDefaults; - } - - } - } diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/NimbusLookAndFeelManager.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/NimbusLookAndFeelManager.java index 79a7df090d..4645a8ef14 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/NimbusLookAndFeelManager.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/NimbusLookAndFeelManager.java @@ -15,14 +15,15 @@ */ package generic.theme.laf; -import java.awt.*; +import java.awt.Font; +import java.util.Set; import javax.swing.*; -import generic.theme.LafType; +import generic.theme.*; +import ghidra.util.exception.AssertException; public class NimbusLookAndFeelManager extends LookAndFeelManager { - private UIDefaults overrides = new UIDefaults(); public NimbusLookAndFeelManager() { super(LafType.NIMBUS); @@ -34,69 +35,39 @@ public class NimbusLookAndFeelManager extends LookAndFeelManager { } @Override - public void updateColor(String id, Color color, boolean isJavaColor) { - super.updateColor(id, color, isJavaColor); + public void resetAll(GThemeValueMap javaDefaults) { + GColor.refreshAll(); + GIcon.refreshAll(); + reinstallNimubus(); } - @Override - public void updateFont(String id, Font font, boolean isJavaFont) { - if (isJavaFont) { - overrides.put(id, font); - updateNimbusOverrides(); + public void updateFonts(String id, Set affectedJavaIds, Font newFont) { + if (!affectedJavaIds.isEmpty()) { + reinstallNimubus(); } repaintAll(); } - @Override - public void updateIcon(String id, Icon icon, boolean isJavaIcon) { - if (isJavaIcon) { - overrides.put(id, icon); - updateNimbusOverrides(); + public void updateIcons(String id, Set affectedJavaIds, Icon newIcon) { + if (!affectedJavaIds.isEmpty()) { + reinstallNimubus(); } + GIcon.refreshAll(); repaintAll(); } - private void updateNimbusOverrides() { - UIDefaults defaults = getNimbusOverrides(); - for (Window window : Window.getWindows()) { - updateNimbusUI(window, defaults); + private void reinstallNimubus() { + try { + UIManager.setLookAndFeel(new GNimbusLookAndFeel() { + protected GThemeValueMap extractJavaDefaults(UIDefaults defaults) { + return Gui.getJavaDefaults(); + } + }); } - } - - private void updateNimbusUI(Component c, UIDefaults defaults) { - updateNimbusUIComp(c, defaults); - c.invalidate(); - c.validate(); - c.repaint(); - } - - private UIDefaults getNimbusOverrides() { - UIDefaults defaults = new UIDefaults(); - defaults.putAll(overrides); - return defaults; - } - - private void updateNimbusUIComp(Component c, UIDefaults defaults) { - if (c instanceof JComponent) { - JComponent jc = (JComponent) c; - jc.putClientProperty("Nimbus.Overrides", defaults); - JPopupMenu jpm = jc.getComponentPopupMenu(); - if (jpm != null) { - updateNimbusUI(jpm, defaults); - } - } - Component[] children = null; - if (c instanceof JMenu) { - children = ((JMenu) c).getMenuComponents(); - } - else if (c instanceof Container) { - children = ((Container) c).getComponents(); - } - if (children != null) { - for (Component child : children) { - updateNimbusUIComp(child, defaults); - } + catch (UnsupportedLookAndFeelException e) { + throw new AssertException("This can't happen, we are just re-installing the same L&F"); } + updateComponentUis(); } } diff --git a/Ghidra/Framework/Generic/src/test/java/generic/theme/ColorValueTest.java b/Ghidra/Framework/Generic/src/test/java/generic/theme/ColorValueTest.java new file mode 100644 index 0000000000..b0fe8eb847 --- /dev/null +++ b/Ghidra/Framework/Generic/src/test/java/generic/theme/ColorValueTest.java @@ -0,0 +1,135 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme; + +import static org.junit.Assert.*; + +import java.awt.Color; + +import org.junit.Before; +import org.junit.Test; + +public class ColorValueTest { + + private GThemeValueMap values; + + @Before + public void setup() { + values = new GThemeValueMap(); + } + + @Test + public void testDirectValue() { + ColorValue value = new ColorValue("color.test", Color.RED); + values.addColor(value); + + assertEquals("color.test", value.getId()); + assertEquals(Color.RED, value.getRawValue()); + assertNull(value.getReferenceId()); + assertEquals(Color.RED, value.get(values)); + } + + @Test + public void testIndirectValue() { + values.addColor(new ColorValue("color.parent", Color.RED)); + ColorValue value = new ColorValue("color.test", "color.parent"); + values.addColor(value); + + assertEquals("color.test", value.getId()); + assertNull(value.getRawValue()); + assertEquals("color.parent", value.getReferenceId()); + assertEquals(Color.RED, value.get(values)); + } + + @Test + public void TestIndirectMultiHopValue() { + values.addColor(new ColorValue("color.grandparent", Color.RED)); + values.addColor(new ColorValue("color.parent", "color.grandparent")); + ColorValue value = new ColorValue("color.test", "color.parent"); + values.addColor(value); + + assertNull(value.getRawValue()); + assertEquals("color.parent", value.getReferenceId()); + assertEquals(Color.RED, value.get(values)); + } + + @Test + public void TestUnresolvedIndirectValue() { + ColorValue value = new ColorValue("color.test", "color.parent"); + values.addColor(value); + + assertNull(value.getRawValue()); + assertEquals("color.parent", value.getReferenceId()); + assertEquals(ColorValue.LAST_RESORT_DEFAULT, value.get(values)); + } + + @Test + public void testReferenceLoop() { + values.addColor(new ColorValue("color.grandparent", "color.test")); + values.addColor(new ColorValue("color.parent", "color.grandparent")); + ColorValue value = new ColorValue("color.test", "color.parent"); + assertEquals(ColorValue.LAST_RESORT_DEFAULT, value.get(values)); + } + + @Test + public void testToExernalId() { + ColorValue value = new ColorValue("color.test", Color.BLUE); + assertEquals("color.test", value.toExternalId("color.test")); + assertEquals("[color]foo.bar", value.toExternalId("foo.bar")); + } + + @Test + public void testFromExternalId() { + ColorValue value = new ColorValue("color.test", Color.BLUE); + assertEquals("color.test", value.fromExternalId("color.test")); + assertEquals("foo.bar", value.fromExternalId("[color]foo.bar")); + } + + @Test + public void testIsColorKey() { + assertTrue(ColorValue.isColorKey("color.a.b.c")); + assertTrue(ColorValue.isColorKey("[color]a.b.c")); + assertFalse(ColorValue.isColorKey("a.b.c")); + } + + @Test + public void testInheritsFrom() { + ColorValue grandParent = new ColorValue("color.grandparent", Color.RED); + values.addColor(grandParent); + ColorValue parent = new ColorValue("color.parent", "color.grandparent"); + values.addColor(parent); + ColorValue value = new ColorValue("color.test", "color.parent"); + values.addColor(value); + + assertTrue(value.inheritsFrom("color.parent", values)); + assertTrue(value.inheritsFrom("color.grandparent", values)); + assertTrue(parent.inheritsFrom("color.grandparent", values)); + + assertFalse(value.inheritsFrom("color.test", values)); + assertFalse(parent.inheritsFrom("color.test", values)); + assertFalse(grandParent.inheritsFrom("color.test", values)); + } + + @Test + public void testCreatingValueFromGColor() { + ColorValue parent = new ColorValue("color.parent", Color.RED); + values.addColor(parent); + Color gColor = new GColor("color.parent"); + ColorValue value = new ColorValue("color.value", gColor); + assertEquals("color.parent", value.getReferenceId()); + assertNull(value.getRawValue()); + } +} diff --git a/Ghidra/Framework/Generic/src/test/java/generic/theme/FontValueTest.java b/Ghidra/Framework/Generic/src/test/java/generic/theme/FontValueTest.java new file mode 100644 index 0000000000..c353544e39 --- /dev/null +++ b/Ghidra/Framework/Generic/src/test/java/generic/theme/FontValueTest.java @@ -0,0 +1,129 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme; + +import static org.junit.Assert.*; + +import java.awt.Font; + +import org.junit.Before; +import org.junit.Test; + +import generic.theme.FontValue; +import generic.theme.GThemeValueMap; + +public class FontValueTest { + private static Font FONT = new Font("Dialog", 12, Font.PLAIN); + private GThemeValueMap values; + + @Before + public void setup() { + values = new GThemeValueMap(); + } + + @Test + public void testDirectValue() { + FontValue value = new FontValue("font.test", FONT); + values.addFont(value); + + assertEquals("font.test", value.getId()); + assertEquals(FONT, value.getRawValue()); + assertNull(value.getReferenceId()); + assertEquals(FONT, value.get(values)); + } + + @Test + public void testIndirectValue() { + values.addFont(new FontValue("font.parent", FONT)); + FontValue value = new FontValue("font.test", "font.parent"); + values.addFont(value); + + assertEquals("font.test", value.getId()); + assertNull(value.getRawValue()); + assertEquals("font.parent", value.getReferenceId()); + assertEquals(FONT, value.get(values)); + } + + @Test + public void TestIndirectMultiHopValue() { + values.addFont(new FontValue("font.grandparent", FONT)); + values.addFont(new FontValue("font.parent", "font.grandparent")); + FontValue value = new FontValue("font.test", "font.parent"); + values.addFont(value); + + assertNull(value.getRawValue()); + assertEquals("font.parent", value.getReferenceId()); + assertEquals(FONT, value.get(values)); + } + + @Test + public void TestUnresolvedIndirectValue() { + FontValue value = new FontValue("font.test", "font.parent"); + values.addFont(value); + + assertNull(value.getRawValue()); + assertEquals("font.parent", value.getReferenceId()); + assertEquals(FontValue.LAST_RESORT_DEFAULT, value.get(values)); + } + + @Test + public void testReferenceLoop() { + values.addFont(new FontValue("font.grandparent", "font.test")); + values.addFont(new FontValue("font.parent", "font.grandparent")); + FontValue value = new FontValue("font.test", "font.parent"); + assertEquals(FontValue.LAST_RESORT_DEFAULT, value.get(values)); + } + + @Test + public void testToExernalId() { + FontValue value = new FontValue("font.test", FONT); + assertEquals("font.test", value.toExternalId("font.test")); + assertEquals("[font]foo.bar", value.toExternalId("foo.bar")); + } + + @Test + public void testFromExternalId() { + FontValue value = new FontValue("font.test", FONT); + assertEquals("font.test", value.fromExternalId("font.test")); + assertEquals("foo.bar", value.fromExternalId("[font]foo.bar")); + } + + @Test + public void testIsFontKey() { + assertTrue(FontValue.isFontKey("font.a.b.c")); + assertTrue(FontValue.isFontKey("[font]a.b.c")); + assertFalse(FontValue.isFontKey("a.b.c")); + } + + @Test + public void testInheritsFrom() { + FontValue grandParent = new FontValue("font.grandparent", FONT); + values.addFont(grandParent); + FontValue parent = new FontValue("font.parent", "font.grandparent"); + values.addFont(parent); + FontValue value = new FontValue("font.test", "font.parent"); + values.addFont(value); + + assertTrue(value.inheritsFrom("font.parent", values)); + assertTrue(value.inheritsFrom("font.grandparent", values)); + assertTrue(parent.inheritsFrom("font.grandparent", values)); + + assertFalse(value.inheritsFrom("font.test", values)); + assertFalse(parent.inheritsFrom("font.test", values)); + assertFalse(grandParent.inheritsFrom("font.test", values)); + } + +} diff --git a/Ghidra/Framework/Docking/src/test/java/docking/theme/GuiTest.java b/Ghidra/Framework/Generic/src/test/java/generic/theme/GuiTest.java similarity index 89% rename from Ghidra/Framework/Docking/src/test/java/docking/theme/GuiTest.java rename to Ghidra/Framework/Generic/src/test/java/generic/theme/GuiTest.java index f2418128c0..298d52daf9 100644 --- a/Ghidra/Framework/Docking/src/test/java/docking/theme/GuiTest.java +++ b/Ghidra/Framework/Generic/src/test/java/generic/theme/GuiTest.java @@ -13,20 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package docking.theme; +package generic.theme; -import java.util.List; -import java.util.Map; - -import org.apache.commons.collections4.map.HashedMap; import org.junit.Before; -import docking.test.AbstractDockingTest; -import generic.theme.*; +public class GuiTest { -public class GuiTest extends AbstractDockingTest { - - private Map> aliasMap = new HashedMap<>(); private GThemeValueMap darkValues = new GThemeValueMap(); @Before diff --git a/Ghidra/Framework/Generic/src/test/java/generic/theme/IconValueTest.java b/Ghidra/Framework/Generic/src/test/java/generic/theme/IconValueTest.java new file mode 100644 index 0000000000..b5fb3d4a2a --- /dev/null +++ b/Ghidra/Framework/Generic/src/test/java/generic/theme/IconValueTest.java @@ -0,0 +1,137 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme; + +import static org.junit.Assert.*; + +import javax.swing.Icon; + +import org.junit.Before; +import org.junit.Test; + +import resources.ResourceManager; + +public class IconValueTest { + private static Icon ICON1 = ResourceManager.getDefaultIcon(); + private GThemeValueMap values; + + @Before + public void setup() { + values = new GThemeValueMap(); + } + + @Test + public void testDirectValue() { + IconValue value = new IconValue("icon.test", ICON1); + values.addIcon(value); + + assertEquals("icon.test", value.getId()); + assertEquals(ICON1, value.getRawValue()); + assertNull(value.getReferenceId()); + assertEquals(ICON1, value.get(values)); + } + + @Test + public void testIndirectValue() { + values.addIcon(new IconValue("icon.parent", ICON1)); + IconValue value = new IconValue("icon.test", "icon.parent"); + values.addIcon(value); + + assertEquals("icon.test", value.getId()); + assertNull(value.getRawValue()); + assertEquals("icon.parent", value.getReferenceId()); + assertEquals(ICON1, value.get(values)); + } + + @Test + public void TestIndirectMultiHopValue() { + values.addIcon(new IconValue("icon.grandparent", ICON1)); + values.addIcon(new IconValue("icon.parent", "icon.grandparent")); + IconValue value = new IconValue("icon.test", "icon.parent"); + values.addIcon(value); + + assertNull(value.getRawValue()); + assertEquals("icon.parent", value.getReferenceId()); + assertEquals(ICON1, value.get(values)); + } + + @Test + public void TestUnresolvedIndirectValue() { + IconValue value = new IconValue("icon.test", "icon.parent"); + values.addIcon(value); + + assertNull(value.getRawValue()); + assertEquals("icon.parent", value.getReferenceId()); + assertEquals(IconValue.LAST_RESORT_DEFAULT, value.get(values)); + } + + @Test + public void testReferenceLoop() { + values.addIcon(new IconValue("icon.grandparent", "icon.test")); + values.addIcon(new IconValue("icon.parent", "icon.grandparent")); + IconValue value = new IconValue("icon.test", "icon.parent"); + assertEquals(IconValue.LAST_RESORT_DEFAULT, value.get(values)); + } + + @Test + public void testToExernalId() { + IconValue value = new IconValue("icon.test", ICON1); + assertEquals("icon.test", value.toExternalId("icon.test")); + assertEquals("[icon]foo.bar", value.toExternalId("foo.bar")); + } + + @Test + public void testFromExternalId() { + IconValue value = new IconValue("icon.test", ICON1); + assertEquals("icon.test", value.fromExternalId("icon.test")); + assertEquals("foo.bar", value.fromExternalId("[icon]foo.bar")); + } + + @Test + public void testIsIconKey() { + assertTrue(IconValue.isIconKey("icon.a.b.c")); + assertTrue(IconValue.isIconKey("[icon]a.b.c")); + assertFalse(IconValue.isIconKey("a.b.c")); + } + + @Test + public void testInheritsFrom() { + IconValue grandParent = new IconValue("icon.grandparent", ICON1); + values.addIcon(grandParent); + IconValue parent = new IconValue("icon.parent", "icon.grandparent"); + values.addIcon(parent); + IconValue value = new IconValue("icon.test", "icon.parent"); + values.addIcon(value); + + assertTrue(value.inheritsFrom("icon.parent", values)); + assertTrue(value.inheritsFrom("icon.grandparent", values)); + assertTrue(parent.inheritsFrom("icon.grandparent", values)); + + assertFalse(value.inheritsFrom("icon.test", values)); + assertFalse(parent.inheritsFrom("icon.test", values)); + assertFalse(grandParent.inheritsFrom("icon.test", values)); + } + + @Test + public void testCreatingValueFromGIcon() { + IconValue parent = new IconValue("icon.parent", ICON1); + values.addIcon(parent); + Icon gIcon = new GIcon("icon.parent"); + IconValue value = new IconValue("icon.value", gIcon); + assertEquals("icon.parent", value.getReferenceId()); + assertNull(value.getRawValue()); + } +} diff --git a/Ghidra/Framework/Generic/src/test/java/generic/theme/ThemeEventTest.java b/Ghidra/Framework/Generic/src/test/java/generic/theme/ThemeEventTest.java new file mode 100644 index 0000000000..9c83c32d68 --- /dev/null +++ b/Ghidra/Framework/Generic/src/test/java/generic/theme/ThemeEventTest.java @@ -0,0 +1,127 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme; + +import static org.junit.Assert.*; + +import java.awt.Color; +import java.awt.Font; + +import javax.swing.Icon; + +import org.junit.Before; +import org.junit.Test; + +import resources.ResourceManager; + +public class ThemeEventTest { + private static Font FONT1 = new Font("Dialog", 12, Font.PLAIN); + private static Font FONT2 = new Font("Dialog", 14, Font.PLAIN); + private static Icon ICON1 = ResourceManager.loadImage("images/flag.png"); + private static Icon ICON2 = ResourceManager.loadImage("images/exec.png"); + + private GThemeValueMap values; + + @Before + public void setup() { + values = new GThemeValueMap(); + } + + @Test + public void testIsColorChangedDirect() { + ColorValue value = new ColorValue("color.value", Color.RED); + values.addColor(value); + ColorValue newValue = new ColorValue("color.value", Color.BLUE); + values.addColor(value); + + ColorChangedThemeEvent event = new ColorChangedThemeEvent(values, newValue); + assertTrue(event.isColorChanged("color.value")); + assertFalse(event.isColorChanged("color.othervalue")); + } + + @Test + public void testIsColorChangedIndirect() { + ColorValue parent = new ColorValue("color.parent", Color.RED); + values.addColor(parent); + ColorValue value = new ColorValue("color.value", "color.parent"); + values.addColor(value); + + ColorValue newValue = new ColorValue("color.parent", Color.BLUE); + values.addColor(value); + + ColorChangedThemeEvent event = new ColorChangedThemeEvent(values, newValue); + assertTrue(event.isColorChanged("color.parent")); + assertTrue(event.isColorChanged("color.value")); + assertFalse(event.isColorChanged("color.othervalue")); + } + + @Test + public void testIsFontChangedDirect() { + FontValue value = new FontValue("font.value", FONT1); + values.addFont(value); + FontValue newValue = new FontValue("font.value", FONT2); + values.addFont(value); + + FontChangedThemeEvent event = new FontChangedThemeEvent(values, newValue); + assertTrue(event.isFontChanged("font.value")); + assertFalse(event.isFontChanged("font.othervalue")); + } + + @Test + public void testIsFontChangedIndirect() { + FontValue parent = new FontValue("font.parent", FONT1); + values.addFont(parent); + FontValue value = new FontValue("font.value", "font.parent"); + values.addFont(value); + + FontValue newValue = new FontValue("font.parent", FONT2); + values.addFont(value); + + FontChangedThemeEvent event = new FontChangedThemeEvent(values, newValue); + assertTrue(event.isFontChanged("font.parent")); + assertTrue(event.isFontChanged("font.value")); + assertFalse(event.isFontChanged("font.othervalue")); + } + + @Test + public void testIsIconChangedDirect() { + IconValue value = new IconValue("ICON.value", ICON1); + values.addIcon(value); + IconValue newValue = new IconValue("icon.value", ICON2); + values.addIcon(value); + + IconChangedThemeEvent event = new IconChangedThemeEvent(values, newValue); + assertTrue(event.isIconChanged("icon.value")); + assertFalse(event.isIconChanged("icon.othervalue")); + } + + @Test + public void testIsIconChangedIndirect() { + IconValue parent = new IconValue("icon.parent", ICON1); + values.addIcon(parent); + IconValue value = new IconValue("icon.value", "icon.parent"); + values.addIcon(value); + + IconValue newValue = new IconValue("icon.parent", ICON2); + values.addIcon(value); + + IconChangedThemeEvent event = new IconChangedThemeEvent(values, newValue); + assertTrue(event.isIconChanged("icon.parent")); + assertTrue(event.isIconChanged("icon.value")); + assertFalse(event.isIconChanged("icon.othervalue")); + } + +}