From 65542e9937d9de33eee1f0165b74164e1493e769 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Fri, 18 Nov 2022 12:17:52 -0500 Subject: [PATCH] GHelpBuilder fix to allow theme usage in headless --- .../java/docking/theme/gui/ThemeUtils.java | 21 +- .../theme/ApplicationThemeManager.java | 152 ++++------- .../src/main/java/generic/theme/Gui.java | 12 +- .../generic/theme/HeadlessThemeManager.java | 50 ++++ .../java/generic/theme/StubThemeManager.java | 4 +- ...Loader.java => ThemeDefaultsProvider.java} | 45 +--- .../main/java/generic/theme/ThemeManager.java | 236 +++++++++++++----- .../generic/theme/laf/LookAndFeelManager.java | 8 +- ....java => ApplicationThemeManagerTest.java} | 49 ++-- .../Help/src/main/java/help/GHelpBuilder.java | 13 +- 10 files changed, 332 insertions(+), 258 deletions(-) create mode 100644 Ghidra/Framework/Generic/src/main/java/generic/theme/HeadlessThemeManager.java rename Ghidra/Framework/Generic/src/main/java/generic/theme/{ThemeFileLoader.java => ThemeDefaultsProvider.java} (67%) rename Ghidra/Framework/Generic/src/test/java/generic/theme/{ThemeManagerTest.java => ApplicationThemeManagerTest.java} (94%) diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeUtils.java index e90f530530..25265bb6ef 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeUtils.java @@ -33,14 +33,15 @@ import ghidra.util.filechooser.ExtensionFileFilter; /** * Some common methods related to saving themes. These are invoked from various places to handle - * what to do if a change is made that would result in loosing theme changes. + * what to do if a change is made that would result in loosing theme changes. */ public class ThemeUtils { /** * Asks the user if they want to save the current theme changes. If they answer yes, it - * will handle several use cases such as whether it gets saved to a new file or + * will handle several use cases such as whether it gets saved to a new file or * overwrites an existing file. + * @param themeManager the theme manager * @return true if the operation was not cancelled */ public static boolean askToSaveThemeChanges(ThemeManager themeManager) { @@ -61,6 +62,7 @@ public class ThemeUtils { /** * Saves all current theme changes. Handles several use cases such as requesting a new theme * name and asking to overwrite an existing file. + * @param themeManager the theme manager * @return true if the operation was not cancelled */ public static boolean saveThemeChanges(ThemeManager themeManager) { @@ -78,10 +80,11 @@ public class ThemeUtils { /** * Resets the theme to the default, handling the case where the current theme has changes. + * @param themeManager the theme manager */ public static void resetThemeToDefault(ThemeManager themeManager) { if (askToSaveThemeChanges(themeManager)) { - themeManager.setTheme(themeManager.getDefaultTheme()); + themeManager.setTheme(ThemeManager.getDefaultTheme()); } } @@ -129,7 +132,7 @@ public class ThemeUtils { } /** - * Exports a theme, prompting the user to pick an file. Also handles dealing with any + * Exports a theme, prompting the user to pick an file. Also handles dealing with any * existing changes to the current theme. * @param themeManager the ThemeManager that actually does the export */ @@ -138,9 +141,8 @@ public class ThemeUtils { return; } boolean hasExternalIcons = !themeManager.getActiveTheme().getExternalIconFiles().isEmpty(); - String message = - "Export as zip file? (You are not using any external icons so the zip\n" + - "file would only contain a single theme file.)"; + String message = "Export as zip file? (You are not using any external icons so the zip\n" + + "file would only contain a single theme file.)"; if (hasExternalIcons) { message = "Export as zip file? (You have external icons so a zip file is required if you\n" + @@ -159,6 +161,7 @@ public class ThemeUtils { /** * Prompts for and deletes a selected theme. + * @param themeManager the theme manager */ public static void deleteTheme(ThemeManager themeManager) { List savedThemes = new ArrayList<>( @@ -198,7 +201,7 @@ public class ThemeUtils { if (existing == null) { return true; } - // if the existing theme is a built-in theme, then we definitely can't save to that name + // if the existing theme is a built-in theme, then we definitely can't save to that name if (existing instanceof DiscoverableGTheme) { return false; } @@ -239,7 +242,7 @@ public class ThemeUtils { private static File getSaveFile(String themeName) { File dir = Application.getUserSettingsDirectory(); - File themeDir = new File(dir, ThemeFileLoader.THEME_DIR); + File themeDir = new File(dir, ThemeManager.THEME_DIR); if (!themeDir.exists()) { themeDir.mkdir(); } diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/ApplicationThemeManager.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/ApplicationThemeManager.java index 6e07ea273c..0c682d1462 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/ApplicationThemeManager.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/ApplicationThemeManager.java @@ -16,34 +16,28 @@ package generic.theme; import java.awt.Component; -import java.io.File; +import java.io.*; import java.util.*; import javax.swing.*; -import javax.swing.plaf.ComponentUI; import com.formdev.flatlaf.FlatDarkLaf; import com.formdev.flatlaf.FlatLightLaf; import generic.theme.laf.LookAndFeelManager; +import ghidra.framework.Application; import ghidra.util.Msg; import ghidra.util.classfinder.ClassSearcher; /** * This is the fully functional {@link ThemeManager} that manages themes in a application. To - * activate the theme functionality, Applications (or tests) must call + * activate the theme functionality, Applications (or tests) must call * {@link ApplicationThemeManager#initialize()} */ public class ApplicationThemeManager extends ThemeManager { - private GTheme activeTheme = getDefaultTheme(); + private Set allThemes = null; - private GThemeValueMap applicationDefaults = new GThemeValueMap(); - private GThemeValueMap applicationDarkDefaults = new GThemeValueMap(); - private GThemeValueMap javaDefaults = new GThemeValueMap(); - private GThemeValueMap systemValues = new GThemeValueMap(); - - protected ThemeFileLoader themeFileLoader = new ThemeFileLoader(); protected ThemePreferences themePreferences = new ThemePreferences(); private Map gColorMap = new HashMap<>(); @@ -58,7 +52,8 @@ public class ApplicationThemeManager extends ThemeManager { */ public static void initialize() { if (INSTANCE instanceof ApplicationThemeManager) { - Msg.error(ThemeManager.class, "Attempted to initialize theming more than once!"); + Msg.error(ApplicationThemeManager.class, + "Attempted to initialize theming more than once!"); return; } @@ -74,13 +69,13 @@ public class ApplicationThemeManager extends ThemeManager { protected void doInitialize() { installFlatLookAndFeels(); - loadThemeDefaults(); + loadDefaultThemeValues(); setTheme(themePreferences.load()); } @Override public void reloadApplicationDefaults() { - loadThemeDefaults(); + loadDefaultThemeValues(); buildCurrentValues(); lookAndFeelManager.resetAll(javaDefaults); notifyThemeChanged(new AllValuesChangedThemeEvent(false)); @@ -183,16 +178,6 @@ public class ApplicationThemeManager extends ThemeManager { return supported; } - @Override - public GTheme getActiveTheme() { - return activeTheme; - } - - @Override - public LafType getLookAndFeelType() { - return activeTheme.getLookAndFeelType(); - } - @Override public GTheme getTheme(String themeName) { Optional first = @@ -200,19 +185,6 @@ public class ApplicationThemeManager extends ThemeManager { return first.orElse(null); } - @Override - public GThemeValueMap getThemeValues() { - GThemeValueMap map = new GThemeValueMap(); - map.load(javaDefaults); - map.load(systemValues); - map.load(applicationDefaults); - if (activeTheme.useDarkDefaults()) { - map.load(applicationDarkDefaults); - } - map.load(activeTheme); - return map; - } - @Override public void setFont(FontValue newValue) { FontValue currentValue = currentValues.getFont(newValue.getId()); @@ -286,49 +258,14 @@ public class ApplicationThemeManager extends ThemeManager { return gIcon; } - @Override - public GThemeValueMap getJavaDefaults() { - GThemeValueMap map = new GThemeValueMap(); - map.load(javaDefaults); - return map; - } - - @Override - public GThemeValueMap getApplicationDarkDefaults() { - GThemeValueMap map = new GThemeValueMap(applicationDefaults); - map.load(applicationDarkDefaults); - return map; - } - - @Override - public GThemeValueMap getApplicationLightDefaults() { - GThemeValueMap map = new GThemeValueMap(applicationDefaults); - return map; - } - - /** - * Returns a {@link GThemeValueMap} containing all default values for the current theme. It - * is a combination of application defined defaults and java {@link LookAndFeel} defaults. - * @return the current set of defaults. - */ - public GThemeValueMap getDefaults() { - GThemeValueMap currentDefaults = new GThemeValueMap(javaDefaults); - currentDefaults.load(systemValues); - currentDefaults.load(applicationDefaults); - if (activeTheme.useDarkDefaults()) { - currentDefaults.load(applicationDarkDefaults); - } - return currentDefaults; - } - /** * Sets specially defined system UI values. These values are created by the application as a * convenience for mapping generic concepts to values that differ by Look and Feel. This allows * clients to use 'system' properties without knowing the actual Look and Feel terms. - * + * *

For example, 'system.color.border' defaults to 'controlShadow', but maps to 'nimbusBorder' * in the Nimbus Look and Feel. - * + * * @param map the map */ public void setSystemDefaults(GThemeValueMap map) { @@ -336,7 +273,7 @@ public class ApplicationThemeManager extends ThemeManager { } /** - * Sets the map of Java default UI values. These are the UI values defined by the current Java + * Sets the map of Java default UI values. These are the UI values defined by the current Java * Look and Feel. * @param map the default theme values defined by the {@link LookAndFeel} */ @@ -347,16 +284,6 @@ public class ApplicationThemeManager extends ThemeManager { GIcon.refreshAll(currentValues); } - @Override - public boolean isUsingAquaUI(ComponentUI UI) { - return activeTheme.getLookAndFeelType() == LafType.MAC; - } - - @Override - public boolean isUsingNimbusUI() { - return activeTheme.getLookAndFeelType() == LafType.NIMBUS; - } - @Override public boolean hasThemeChanges() { return !changedValuesMap.isEmpty(); @@ -367,32 +294,14 @@ public class ApplicationThemeManager extends ThemeManager { lookAndFeelManager.registerFont(component, fontId); } - public boolean isDarkTheme() { - return activeTheme.useDarkDefaults(); - } - private void installFlatLookAndFeels() { UIManager.installLookAndFeel(LafType.FLAT_LIGHT.getName(), FlatLightLaf.class.getName()); UIManager.installLookAndFeel(LafType.FLAT_DARK.getName(), FlatDarkLaf.class.getName()); } - private void loadThemeDefaults() { - themeFileLoader.loadThemeDefaultFiles(); - applicationDefaults = themeFileLoader.getDefaults(); - applicationDarkDefaults = themeFileLoader.getDarkDefaults(); - } - - private void buildCurrentValues() { - GThemeValueMap map = new GThemeValueMap(); - - map.load(javaDefaults); - map.load(systemValues); - map.load(applicationDefaults); - if (activeTheme.useDarkDefaults()) { - map.load(applicationDarkDefaults); - } - map.load(activeTheme); - currentValues = map; + @Override + protected void buildCurrentValues() { + super.buildCurrentValues(); changedValuesMap.clear(); } @@ -400,11 +309,42 @@ public class ApplicationThemeManager extends ThemeManager { if (allThemes == null) { Set set = new HashSet<>(); set.addAll(findDiscoverableThemes()); - set.addAll(themeFileLoader.loadThemeFiles()); + set.addAll(loadThemeFiles()); allThemes = set; } } + protected Collection loadThemeFiles() { + List fileList = new ArrayList<>(); + FileFilter themeFileFilter = file -> file.getName().endsWith("." + GTheme.FILE_EXTENSION); + + File dir = Application.getUserSettingsDirectory(); + File themeDir = new File(dir, THEME_DIR); + File[] files = themeDir.listFiles(themeFileFilter); + if (files != null) { + fileList.addAll(Arrays.asList(files)); + } + + List list = new ArrayList<>(); + for (File file : fileList) { + GTheme theme = loadTheme(file); + if (theme != null) { + list.add(theme); + } + } + return list; + } + + private static GTheme loadTheme(File file) { + try { + return new ThemeReader(file).readTheme(); + } + catch (IOException e) { + Msg.error(Gui.class, "Could not load theme from file: " + file.getAbsolutePath(), e); + } + return null; + } + private Collection findDiscoverableThemes() { return ClassSearcher.getInstances(DiscoverableGTheme.class); } 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 a9c3d86428..df190a0747 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/Gui.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/Gui.java @@ -24,19 +24,19 @@ import javax.swing.LookAndFeel; * 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 + * 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 + * in {name}.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 + * 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. - * + * be defined and stored in the users application home directory as a {name}.theme file. + * */ public class Gui { - // Start with an StubThemeManager so that simple tests can operate without having + // Start with an StubThemeManager so that simple tests can operate without having // to initialize the theme system. Applications and integration tests will // called ThemeManager.initialize() which will replace this with a fully initialized version. private static ThemeManager themeManager = new StubThemeManager(); diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/HeadlessThemeManager.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/HeadlessThemeManager.java new file mode 100644 index 0000000000..4c2bcb73b0 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/HeadlessThemeManager.java @@ -0,0 +1,50 @@ +/* ### + * 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 ghidra.util.Msg; + +/** + * This is a strange implementation of {@link ThemeManager} that is meant to be used in a headless + * environment, but also needs theme properties to have been loaded. This is needed by any + * application that needs to do theme property validation. + */ +public class HeadlessThemeManager extends ThemeManager { + + public static void initialize() { + if (INSTANCE instanceof HeadlessThemeManager) { + Msg.error(HeadlessThemeManager.class, + "Attempted to initialize theming more than once!"); + return; + } + + HeadlessThemeManager themeManager = new HeadlessThemeManager(); + themeManager.doInitialize(); + } + + public HeadlessThemeManager() { + INSTANCE = this; + installInGui(); + } + + private void doInitialize() { + loadDefaultThemeValues(); + + buildCurrentValues(); + GColor.refreshAll(currentValues); + GIcon.refreshAll(currentValues); + } +} diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/StubThemeManager.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/StubThemeManager.java index 82c56dfa90..36799bc022 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/StubThemeManager.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/StubThemeManager.java @@ -24,7 +24,7 @@ import java.util.Set; import javax.swing.plaf.ComponentUI; /** - * Version of ThemeManager that is used before an application or test installs a full + * Version of ThemeManager that is used before an application or test installs a full * ApplicationThemeManager. Provides enough basic functionality used by the Gui class to * allow simple unit tests to run. */ @@ -35,7 +35,7 @@ public class StubThemeManager extends ThemeManager { } // palette colors are used statically throughout the application, so having them have values - // in the stub will allow unit tests to run withouth initializing theming + // in the stub will allow unit tests to run without initializing theming protected void installPaletteColors() { addPalette("nocolor", BLACK); addPalette("black", BLACK); diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/ThemeFileLoader.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/ThemeDefaultsProvider.java similarity index 67% rename from Ghidra/Framework/Generic/src/main/java/generic/theme/ThemeFileLoader.java rename to Ghidra/Framework/Generic/src/main/java/generic/theme/ThemeDefaultsProvider.java index 88eb1d3603..13572d8b72 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/ThemeFileLoader.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/ThemeDefaultsProvider.java @@ -15,8 +15,8 @@ */ package generic.theme; -import java.io.*; -import java.util.*; +import java.io.IOException; +import java.util.List; import generic.jar.ResourceFile; import ghidra.framework.Application; @@ -26,17 +26,20 @@ import ghidra.util.Msg; * Loads all the system theme.property files that contain all the default color, font, and * icon values. */ -public class ThemeFileLoader { - public static final String THEME_DIR = "themes"; +public class ThemeDefaultsProvider { private GThemeValueMap defaults = new GThemeValueMap(); private GThemeValueMap darkDefaults = new GThemeValueMap(); + ThemeDefaultsProvider() { + loadThemeDefaultFiles(); + } + /** * Searches for all the theme.property files and loads them into either the standard * defaults (light) map or the dark defaults map. */ - public void loadThemeDefaultFiles() { + private void loadThemeDefaultFiles() { defaults.clear(); darkDefaults.clear(); @@ -56,28 +59,6 @@ public class ThemeFileLoader { } } - public Collection loadThemeFiles() { - List fileList = new ArrayList<>(); - FileFilter themeFileFilter = file -> file.getName().endsWith("." + GTheme.FILE_EXTENSION); - - File dir = Application.getUserSettingsDirectory(); - File themeDir = new File(dir, THEME_DIR); - File[] files = themeDir.listFiles(themeFileFilter); - if (files != null) { - fileList.addAll(Arrays.asList(files)); - } - - List list = new ArrayList<>(); - for (File file : fileList) { - GTheme theme = loadTheme(file); - if (theme != null) { - list.add(theme); - } - } - return list; - - } - /** * Returns the standard defaults {@link GThemeValueMap} * @return the standard defaults {@link GThemeValueMap} @@ -93,14 +74,4 @@ public class ThemeFileLoader { public GThemeValueMap getDarkDefaults() { return darkDefaults; } - - private static GTheme loadTheme(File file) { - try { - return new ThemeReader(file).readTheme(); - } - catch (IOException e) { - Msg.error(Gui.class, "Could not load theme from file: " + file.getAbsolutePath(), e); - } - return null; - } } diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/ThemeManager.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/ThemeManager.java index ad5720773f..be3e98dcfc 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/ThemeManager.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/ThemeManager.java @@ -33,35 +33,45 @@ import utilities.util.reflection.ReflectionUtilities; /** * This class manages application themes and their values. The ThemeManager is an abstract - * base class that has two concrete subclasses (and others for testing purposes) - + * base class that has two concrete subclasses (and others for testing purposes) - * StubThemeManager and ApplicationThememManager. The StubThemeManager exists as a placeholder * until the ApplicationThemeManager is installed via {@link ApplicationThemeManager#initialize()}. *

* 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 + * 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 + * 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. + * be defined and stored in the users application home directory as a .theme file. *

- * Clients that just need to access the colors, fonts, and icons from the theme can use the - * convenience methods in the {@link Gui} class. Clients that need to directly manipulate the + * Clients that just need to access the colors, fonts, and icons from the theme can use the + * convenience methods in the {@link Gui} class. Clients that need to directly manipulate the * themes and values will need to directly use the ThemeManager which and be retrieved using the * static {@link #getInstance()} method. */ public abstract class ThemeManager { + + public static final String THEME_DIR = "themes"; + static final Font DEFAULT_FONT = new Font("Dialog", Font.PLAIN, 12); static final Color DEFAULT_COLOR = Color.CYAN; protected static ThemeManager INSTANCE; + protected GTheme activeTheme = getDefaultTheme(); + + protected GThemeValueMap javaDefaults = new GThemeValueMap(); + protected GThemeValueMap systemValues = new GThemeValueMap(); protected GThemeValueMap currentValues = new GThemeValueMap(); + protected GThemeValueMap applicationDefaults = new GThemeValueMap(); + protected GThemeValueMap applicationDarkDefaults = new GThemeValueMap(); + // these notifications are only when the user is manipulating theme values, so rare and at // user speed, so using copy on read private WeakSet themeListeners = @@ -82,37 +92,66 @@ public abstract class ThemeManager { Gui.setThemeManager(this); } + protected void loadDefaultThemeValues() { + ThemeDefaultsProvider provider = new ThemeDefaultsProvider(); + applicationDefaults = provider.getDefaults(); + applicationDarkDefaults = provider.getDarkDefaults(); + } + + protected void buildCurrentValues() { + GThemeValueMap map = new GThemeValueMap(); + + map.load(javaDefaults); + map.load(systemValues); + map.load(applicationDefaults); + if (activeTheme.useDarkDefaults()) { + map.load(applicationDarkDefaults); + } + map.load(activeTheme); + currentValues = map; + } + /** * Reloads the defaults from all the discoverable theme.property files. */ - public abstract void reloadApplicationDefaults(); + public void reloadApplicationDefaults() { + throw new UnsupportedOperationException(); + } /** * Restores all the current application back to the values as specified by the active theme. * In other words, reverts any changes to the active theme that haven't been saved. */ - public abstract void restoreThemeValues(); + public void restoreThemeValues() { + throw new UnsupportedOperationException(); + } /** * Restores the current color value for the given color id to the value established by the * current theme. * @param id the color id to restore back to the original theme value */ - public abstract void restoreColor(String id); + public void restoreColor(String id) { + throw new UnsupportedOperationException(); + } /** * Restores the current font value for the given font id to the value established by the * current theme. * @param id the font id to restore back to the original theme value */ - public abstract void restoreFont(String id); + public void restoreFont(String id) { + throw new UnsupportedOperationException(); + } /** * Restores the current icon value for the given icon id to the value established by the * current theme. * @param id the icon id to restore back to the original theme value */ - public abstract void restoreIcon(String id); + public void restoreIcon(String id) { + throw new UnsupportedOperationException(); + } /** * Returns true if the color associated with the given id has been changed from the current @@ -121,7 +160,9 @@ public abstract class ThemeManager { * @return true if the color associated with the given id has been changed from the current * theme value for that id. */ - public abstract boolean isChangedColor(String id); + public boolean isChangedColor(String id) { + return false; + } /** * Returns true if the font associated with the given id has been changed from the current @@ -130,7 +171,9 @@ public abstract class ThemeManager { * @return true if the font associated with the given id has been changed from the current * theme value for that id. */ - public abstract boolean isChangedFont(String id); + public boolean isChangedFont(String id) { + return false; + } /** * Returns true if the Icon associated with the given id has been changed from the current @@ -139,57 +182,75 @@ public abstract class ThemeManager { * @return true if the Icon associated with the given id has been changed from the current * theme value for that id. */ - public abstract boolean isChangedIcon(String id); + public boolean isChangedIcon(String id) { + return false; + } /** * Sets the application's active theme to the given theme. * @param theme the theme to make active */ - public abstract void setTheme(GTheme theme); + public void setTheme(GTheme theme) { + throw new UnsupportedOperationException(); + } /** * Adds the given theme to set of all themes. * @param newTheme the theme to add */ - public abstract void addTheme(GTheme newTheme); + public void addTheme(GTheme newTheme) { + throw new UnsupportedOperationException(); + } /** * Removes the theme from the set of all themes. Also, if the theme has an associated * file, the file will be deleted. * @param theme the theme to delete */ - public abstract void deleteTheme(GTheme theme); + public void deleteTheme(GTheme theme) { + throw new UnsupportedOperationException(); + } /** * Returns a set of all known themes. * @return a set of all known themes. */ - public abstract Set getAllThemes(); + public Set getAllThemes() { + throw new UnsupportedOperationException(); + } /** * Returns a set of all known themes that are supported on the current platform. * @return a set of all known themes that are supported on the current platform. */ - public abstract Set getSupportedThemes(); + public Set getSupportedThemes() { + throw new UnsupportedOperationException(); + } /** * Returns the active theme. * @return the active theme. */ - public abstract GTheme getActiveTheme(); + public GTheme getActiveTheme() { + return activeTheme; + } /** * Returns the {@link LafType} for the currently active {@link LookAndFeel} * @return the {@link LafType} for the currently active {@link LookAndFeel} */ - public abstract LafType getLookAndFeelType(); + public LafType getLookAndFeelType() { + return activeTheme.getLookAndFeelType(); + } /** * Returns the known theme that has the given name. * @param themeName the name of the theme to retrieve * @return the known theme that has the given name */ - public abstract GTheme getTheme(String themeName); + public GTheme getTheme(String themeName) { + throw new UnsupportedOperationException(); + } /** * Returns a {@link GThemeValueMap} of all current theme values including unsaved changes to the @@ -206,7 +267,17 @@ public abstract class ThemeManager { * @return the theme values as defined by the current theme, ignoring any unsaved changes that * are currently applied to the application */ - public abstract GThemeValueMap getThemeValues(); + public GThemeValueMap getThemeValues() { + GThemeValueMap map = new GThemeValueMap(); + map.load(javaDefaults); + map.load(systemValues); + map.load(applicationDefaults); + if (activeTheme.useDarkDefaults()) { + map.load(applicationDarkDefaults); + } + map.load(activeTheme); + return map; + } /** * Returns a {@link GThemeValueMap} contains all values that differ from the default @@ -277,7 +348,9 @@ public abstract class ThemeManager { * Updates the current value for the font id in the newValue * @param newValue the new {@link FontValue} to install in the current values. */ - public abstract void setFont(FontValue newValue); + public void setFont(FontValue newValue) { + throw new UnsupportedOperationException(); + } /** * Updates the current color for the given id. @@ -303,7 +376,9 @@ public abstract class ThemeManager { * Updates the current value for the color id in the newValue * @param newValue the new {@link ColorValue} to install in the current values. */ - public abstract void setColor(ColorValue newValue); + public void setColor(ColorValue newValue) { + throw new UnsupportedOperationException(); + } /** * Updates the current {@link Icon} for the given id. @@ -318,25 +393,31 @@ public abstract class ThemeManager { * Updates the current value for the {@link Icon} id in the newValue * @param newValue the new {@link IconValue} to install in the current values. */ - public abstract void setIcon(IconValue newValue); + public void setIcon(IconValue newValue) { + throw new UnsupportedOperationException(); + } /** * gets a UIResource version of the GColor for the given id. Using this method ensures that - * the same instance is used for a given id. This combats some poor code in some of the + * the same instance is used for a given id. This combats some poor code in some of the * {@link LookAndFeel}s where the use == in some places to test for equals. * @param id the id to get a GColorUIResource for * @return a GColorUIResource for the given id */ - public abstract GColorUIResource getGColorUiResource(String id); + public GColorUIResource getGColorUiResource(String id) { + throw new UnsupportedOperationException(); + } /** * gets a UIResource version of the GIcon for the given id. Using this method ensures that - * the same instance is used for a given id. This combats some poor code in some of the + * the same instance is used for a given id. This combats some poor code in some of the * {@link LookAndFeel}s where the use == in some places to test for equals. * @param id the id to get a {@link GIconUIResource} for * @return a GIconUIResource for the given id */ - public abstract GIconUIResource getGIconUiResource(String id); + public GIconUIResource getGIconUiResource(String id) { + throw new UnsupportedOperationException(); + } /** * Returns the {@link GThemeValueMap} containing all the default theme values defined by the @@ -344,44 +425,67 @@ public abstract class ThemeManager { * @return the {@link GThemeValueMap} containing all the default theme values defined by the * current {@link LookAndFeel} */ - public abstract GThemeValueMap getJavaDefaults(); + public GThemeValueMap getJavaDefaults() { + GThemeValueMap map = new GThemeValueMap(); + map.load(javaDefaults); + return map; + } /** * Returns the {@link GThemeValueMap} containing all the dark default values defined * in theme.properties files. Note that dark defaults includes light defaults that haven't * been overridden by a dark default with the same id. - * @return the {@link GThemeValueMap} containing all the dark values defined in + * @return the {@link GThemeValueMap} containing all the dark values defined in * theme.properties files */ - public abstract GThemeValueMap getApplicationDarkDefaults(); + public GThemeValueMap getApplicationDarkDefaults() { + GThemeValueMap map = new GThemeValueMap(applicationDefaults); + map.load(applicationDarkDefaults); + return map; + } /** * Returns the {@link GThemeValueMap} containing all the standard default values defined - * in theme.properties files. - * @return the {@link GThemeValueMap} containing all the standard values defined in + * in theme.properties files. + * @return the {@link GThemeValueMap} containing all the standard values defined in * theme.properties files */ - public abstract GThemeValueMap getApplicationLightDefaults(); + public GThemeValueMap getApplicationLightDefaults() { + GThemeValueMap map = new GThemeValueMap(applicationDefaults); + return map; + } /** * Returns a {@link GThemeValueMap} containing all default values for the current theme. It * is a combination of application defined defaults and java {@link LookAndFeel} defaults. * @return the current set of defaults. */ - public abstract GThemeValueMap getDefaults(); + public GThemeValueMap getDefaults() { + GThemeValueMap currentDefaults = new GThemeValueMap(javaDefaults); + currentDefaults.load(systemValues); + currentDefaults.load(applicationDefaults); + if (activeTheme.useDarkDefaults()) { + currentDefaults.load(applicationDarkDefaults); + } + return currentDefaults; + } /** * Returns true if the given UI object is using the Aqua Look and Feel. * @param UI the UI to examine. * @return true if the UI is using Aqua */ - public abstract boolean isUsingAquaUI(ComponentUI UI); + public boolean isUsingAquaUI(ComponentUI UI) { + return activeTheme.getLookAndFeelType() == LafType.MAC; + } /** * Returns true if 'Nimbus' is the current Look and Feel * @return true if 'Nimbus' is the current Look and Feel */ - public abstract boolean isUsingNimbusUI(); + public boolean isUsingNimbusUI() { + return activeTheme.getLookAndFeelType() == LafType.NIMBUS; + } /** * Adds a {@link ThemeListener} to be notified of theme changes. @@ -400,29 +504,13 @@ public abstract class ThemeManager { themeListeners.remove(listener); } - /** - * Returns the default theme for the current platform. - * @return the default theme for the current platform. - */ - public static GTheme getDefaultTheme() { - OperatingSystem OS = Platform.CURRENT_PLATFORM.getOperatingSystem(); - switch (OS) { - case MAC_OS_X: - return new MacTheme(); - case WINDOWS: - return new WindowsTheme(); - case LINUX: - case UNSUPPORTED: - default: - return new NimbusTheme(); - } - } - /** * Returns true if there are any unsaved changes to the current theme. * @return true if there are any unsaved changes to the current theme. */ - public abstract boolean hasThemeChanges(); + public boolean hasThemeChanges() { + return false; + } /** * Returns true if an color for the given Id has been defined @@ -457,13 +545,35 @@ public abstract class ThemeManager { * @param component the component to set/update the font * @param fontId the id of the font to register with the given component */ - public abstract void registerFont(Component component, String fontId); + public void registerFont(Component component, String fontId) { + // do nothing + } /** * Returns true if the current theme use dark default values. * @return true if the current theme use dark default values. */ - public abstract boolean isDarkTheme(); + public boolean isDarkTheme() { + return activeTheme.useDarkDefaults(); + } + + /** + * Returns the default theme for the current platform. + * @return the default theme for the current platform. + */ + public static GTheme getDefaultTheme() { + OperatingSystem OS = Platform.CURRENT_PLATFORM.getOperatingSystem(); + switch (OS) { + case MAC_OS_X: + return new MacTheme(); + case WINDOWS: + return new WindowsTheme(); + case LINUX: + case UNSUPPORTED: + default: + return new NimbusTheme(); + } + } protected void notifyThemeChanged(ThemeEvent event) { for (ThemeListener listener : themeListeners) { @@ -474,10 +584,10 @@ public abstract class ThemeManager { protected void error(String message) { Throwable t = ReflectionUtilities.createThrowableWithStackOlderThan(); StackTraceElement[] trace = t.getStackTrace(); - StackTraceElement[] filtered = - ReflectionUtilities.filterStackTrace(trace, "java.", "theme.Gui", "theme.ThemeManager", - "theme.GColor"); + StackTraceElement[] filtered = ReflectionUtilities.filterStackTrace(trace, "java.", + "theme.Gui", "theme.ThemeManager", "theme.GColor"); t.setStackTrace(filtered); Msg.error(this, message, t); } + } 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 3ba62a1971..f9e1e67289 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 @@ -36,11 +36,11 @@ import ghidra.util.SystemUtilities; */ public abstract class LookAndFeelManager { - /** - * These are color ids (see {@link GColor} used to represent general concepts that + /** + * These are color ids (see {@link GColor} used to represent general concepts that * application developers can use to get the color for that concept as defined by * a specific {@link LookAndFeel}. This class will define some standard default - * mappings in the constructor, but it is expected that each specific LookAndFeelManager + * mappings in the constructor, but it is expected that each specific LookAndFeelManager * will override these mappings with values appropriate for that LookAndFeel. */ protected static final String SYSTEM_APP_BACKGROUND_COLOR_ID = "system.color.bg.application"; @@ -91,6 +91,7 @@ public abstract class LookAndFeelManager { installJavaDefaults(); fixupLookAndFeelIssues(); installGlobalProperties(); + installCustomLookAndFeelActions(); updateComponentUis(); } @@ -459,7 +460,6 @@ public abstract class LookAndFeelManager { private void installGlobalProperties() { installGlobalLookAndFeelAttributes(); installGlobalFontSizeOverride(); - installCustomLookAndFeelActions(); installPopupMenuSettingsOverride(); } diff --git a/Ghidra/Framework/Generic/src/test/java/generic/theme/ThemeManagerTest.java b/Ghidra/Framework/Generic/src/test/java/generic/theme/ApplicationThemeManagerTest.java similarity index 94% rename from Ghidra/Framework/Generic/src/test/java/generic/theme/ThemeManagerTest.java rename to Ghidra/Framework/Generic/src/test/java/generic/theme/ApplicationThemeManagerTest.java index ec6ba59828..1d5bf0b725 100644 --- a/Ghidra/Framework/Generic/src/test/java/generic/theme/ThemeManagerTest.java +++ b/Ghidra/Framework/Generic/src/test/java/generic/theme/ApplicationThemeManagerTest.java @@ -34,7 +34,7 @@ import generic.theme.builtin.*; import resources.ResourceManager; import resources.icons.UrlImageIcon; -public class ThemeManagerTest { +public class ApplicationThemeManagerTest { private Font FONT = new Font("Dialog", Font.PLAIN, 13); private Font SMALL_FONT = new Font("Dialog", Font.PLAIN, 4); @@ -51,6 +51,8 @@ public class ThemeManagerTest { private GTheme MAC_THEME = new MacTheme(); private ThemeManager themeManager; + private boolean errorsExpected; + @Before public void setUp() { @@ -69,7 +71,6 @@ public class ThemeManagerTest { darkDefaultValues.addColor(new ColorValue("color.test.bg", BLACK)); darkDefaultValues.addColor(new ColorValue("color.test.fg", BLUE)); themeManager = new DummyApplicationThemeManager(); - } @Test @@ -264,16 +265,19 @@ public class ThemeManagerTest { @Test public void testGetColorWithUnresolvedId() { + errorsExpected = true; assertEquals(CYAN, themeManager.getColor("color.badid")); } @Test public void testGetIconWithUnresolvedId() { + errorsExpected = true; assertEquals(ResourceManager.getDefaultIcon(), themeManager.getIcon("icon.badid")); } @Test public void testGetFontWithUnresolvedId() { + errorsExpected = true; assertEquals(ThemeManager.DEFAULT_FONT, themeManager.getFont("font.badid")); } @@ -337,7 +341,7 @@ public class ThemeManagerTest { } // ApplicationThemeManager that doesn't read in theme.properties files or preferences - class DummyApplicationThemeManager extends ApplicationThemeManager { + private class DummyApplicationThemeManager extends ApplicationThemeManager { DummyApplicationThemeManager() { themePreferences = new ThemePreferences() { @Override @@ -350,28 +354,25 @@ public class ThemeManagerTest { // do nothing } }; - themeFileLoader = new ThemeFileLoader() { - @Override - public void loadThemeDefaultFiles() { - // do nothing - } - - @Override - public Collection loadThemeFiles() { - return new HashSet<>(themes); - } - - @Override - public GThemeValueMap getDefaults() { - return defaultValues; - } - - @Override - public GThemeValueMap getDarkDefaults() { - return darkDefaultValues; - } - }; doInitialize(); } + + @Override + protected void loadDefaultThemeValues() { + this.applicationDefaults = defaultValues; + this.applicationDarkDefaults = darkDefaultValues; + } + + @Override + protected Collection loadThemeFiles() { + return new HashSet<>(themes); + } + + @Override + protected void error(String message) { + if (!errorsExpected) { + super.error(message); + } + } } } diff --git a/Ghidra/Framework/Help/src/main/java/help/GHelpBuilder.java b/Ghidra/Framework/Help/src/main/java/help/GHelpBuilder.java index bac98cf874..b3cfea430b 100644 --- a/Ghidra/Framework/Help/src/main/java/help/GHelpBuilder.java +++ b/Ghidra/Framework/Help/src/main/java/help/GHelpBuilder.java @@ -21,7 +21,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; -import generic.theme.ApplicationThemeManager; +import generic.theme.HeadlessThemeManager; import ghidra.framework.Application; import ghidra.framework.ApplicationConfiguration; import help.validator.*; @@ -69,12 +69,11 @@ public class GHelpBuilder { ApplicationConfiguration config = new ApplicationConfiguration() { @Override protected void initializeApplication() { - ApplicationThemeManager.initialize(); - } - - @Override - public boolean isHeadless() { - return false; + // + // We must be headless, as we are utility class. But, we also need theme properties + // to be loaded and correct for the help system to function properly. + // + HeadlessThemeManager.initialize(); } }; Application.initializeApplication(new HelpApplicationLayout("Help Builder", "0.1"), config);