Merge remote-tracking branch

'origin/GP-1-dragonmacher-help-headless-fix'

Conflicts:
	Ghidra/Framework/Help/src/main/java/help/GHelpBuilder.java
This commit is contained in:
Ryan Kurtz 2022-11-19 03:43:08 -05:00
commit 75ddd08bbd
10 changed files with 332 additions and 258 deletions

View File

@ -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<GTheme> 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();
}

View File

@ -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<GTheme> 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<String, GColorUIResource> 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<GTheme> 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.
*
*
* <p>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<GTheme> set = new HashSet<>();
set.addAll(findDiscoverableThemes());
set.addAll(themeFileLoader.loadThemeFiles());
set.addAll(loadThemeFiles());
allThemes = set;
}
}
protected Collection<GTheme> loadThemeFiles() {
List<File> 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<GTheme> 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<DiscoverableGTheme> findDiscoverableThemes() {
return ClassSearcher.getInstances(DiscoverableGTheme.class);
}

View File

@ -24,19 +24,19 @@ import javax.swing.LookAndFeel;
* Provides a static set of methods for globally managing application themes and their values.
* <P>
* 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 <name>.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 <name>.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();

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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<GTheme> loadThemeFiles() {
List<File> 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<GTheme> 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;
}
}

View File

@ -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()}.
* <P>
* 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 <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 <name>.theme file.
* be defined and stored in the users application home directory as a <name>.theme file.
* <P>
* 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<ThemeListener> 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<GTheme> getAllThemes();
public Set<GTheme> 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<GTheme> getSupportedThemes();
public Set<GTheme> 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);
}
}

View File

@ -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();
}

View File

@ -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<GTheme> 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<GTheme> loadThemeFiles() {
return new HashSet<>(themes);
}
@Override
protected void error(String message) {
if (!errorsExpected) {
super.error(message);
}
}
}
}

View File

@ -22,7 +22,7 @@ import java.nio.file.Paths;
import java.util.*;
import generic.application.GenericApplicationLayout;
import generic.theme.ApplicationThemeManager;
import generic.theme.HeadlessThemeManager;
import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration;
import help.validator.*;
@ -70,12 +70,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 GenericApplicationLayout("Help Builder", "0.1"),