GP-1981 Refactored Gui to use ThemeManager

This commit is contained in:
ghidragon
2022-11-07 16:52:28 -05:00
parent a92a27e9f1
commit edfb5a0877
78 changed files with 1902 additions and 1230 deletions
@@ -212,10 +212,10 @@ public interface AutoOptions {
else {
options.registerOption(key.getName(), type, defaultValue, help, description,
editor);
// TODO: Wish Ghidra would do this upon any option registration
options.putObject(key.getName(), defaultValue, type);
}
// TODO: Wish Ghidra would do this upon any option registration
options.putObject(key.getName(), defaultValue, type);
}
}
@@ -68,6 +68,8 @@ color.fg.plugin.comments.history.text = blue
color.fg.plugin.comments.history.user = color.fg
color.fg.plugin.comments.history.date = rgb(124, 37, 18)
color.bg.plugin.programtree = color.bg
color.bg.plugin.datamgr.edge.default = blue
color.bg.plugin.datamgr.edge.composite = magenta
color.bg.plugin.datamgr.edge.reference = blue
@@ -37,6 +37,6 @@
icon.fsbrowser.file.substring.release. = images/famfamfam_silk_icons_v013/bullet_purple.png
icon.fsbrowser.file.overlay.imported = images/checkmark_green.gif
icon.fsbrowser.file.overlay.filesystem = EMPTY_ICON{images/nuvola/16x16/ledgreen.png[size(8,8)][move(8,8)]} // lower right quadrant
icon.fsbrowser.file.overlay.imported = EMPTY_ICON{images/checkmark_green.gif[size(8,8)][move(8,8)]} // lower right quadrant
icon.fsbrowser.file.overlay.filesystem = EMPTY_ICON{images/nuvola/16x16/ledgreen.png[size(8,8)][move(0,8)]} // lower left quadrant
icon.fsbrowser.file.overlay.missing.password = EMPTY_ICON{images/lock.png[size(8,8)][move(8,0)]} // upper right quadrant
@@ -38,7 +38,6 @@ import ghidra.program.database.ProgramDB;
import ghidra.util.*;
import ghidra.util.exception.UsrException;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskMonitorAdapter;
/**
* Main Ghidra application class. Creates
@@ -73,7 +72,6 @@ public class GhidraRun implements GhidraLaunchable {
Runnable mainTask = () -> {
GhidraApplicationConfiguration configuration = new GhidraApplicationConfiguration();
configuration.setTaskMonitor(new StatusReportingTaskMonitor());
Application.initializeApplication(layout, configuration);
log = LogManager.getLogger(GhidraRun.class);
@@ -243,15 +241,3 @@ public class GhidraRun implements GhidraLaunchable {
// this exists just to allow access to the constructor
}
}
class StatusReportingTaskMonitor extends TaskMonitorAdapter {
@Override
public synchronized void setCancelEnabled(boolean enable) {
// Not permitted
}
@Override
public void setMessage(String message) {
SplashScreen.updateSplashScreenStatus(message);
}
}
@@ -33,8 +33,7 @@ import docking.actions.KeyBindingUtils;
import docking.options.editor.FontEditor;
import docking.widgets.OptionDialog;
import docking.widgets.filechooser.GhidraFileChooser;
import generic.theme.GIcon;
import generic.theme.Gui;
import generic.theme.*;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
@@ -274,7 +273,7 @@ public class TextEditorComponentProvider extends ComponentProviderAdapter {
FontEditor editor = new FontEditor();
editor.setValue(Gui.getFont(FONT_ID));
editor.showDialog();
Gui.setFont(FONT_ID, (Font) editor.getValue());
ThemeManager.getInstance().setFont(FONT_ID, (Font) editor.getValue());
}
private void save() {
@@ -34,6 +34,8 @@ import resources.ResourceManager;
* Cell renderer for the drag and drop tree.
*/
class DnDTreeCellRenderer extends DefaultTreeCellRenderer {
private static final Color BACKGROUND_UNSELECTED = new GColor("color.bg.tree");
private static final Color BACKGROUND_SELECTED = new GColor("color.bg.tree.selected");
private static final String DISABLED_DOCS = "DisabledDocument.gif";
private static final String DISABLED_FRAGMENT = "DisabledFragment";
@@ -73,8 +75,8 @@ class DnDTreeCellRenderer extends DefaultTreeCellRenderer {
*/
DnDTreeCellRenderer() {
super();
defaultNonSelectionColor = new GColor("Tree.textBackground");
defaultSelectionColor = new GColor("Tree.selectionBackground");
defaultNonSelectionColor = BACKGROUND_UNSELECTED;
defaultSelectionColor = BACKGROUND_SELECTED;
rowForFeedback = -1;
// disable HTML rendering
@@ -70,6 +70,7 @@ public abstract class DragNDropTree extends JTree implements Draggable, Droppabl
public DragNDropTree(DefaultTreeModel model) {
super(model);
setBackground(new GColor("color.bg.tree"));
treeModel = model;
this.root = (ProgramNode) model.getRoot();
//setEditable(true); // edit interferes with drag gesture listener
@@ -315,8 +315,9 @@ public class ProgramDnDTree extends DragNDropTree {
return false;
}
try {
Object data = e.getTransferable().getTransferData(
SelectionTransferable.localProgramSelectionFlavor);
Object data = e.getTransferable()
.getTransferData(
SelectionTransferable.localProgramSelectionFlavor);
SelectionTransferData transferData = (SelectionTransferData) data;
return program.getDomainFile().getPathname().equals(transferData.getProgramPath());
}
@@ -31,9 +31,8 @@ import docking.actions.KeyBindingUtils;
import docking.options.editor.FontEditor;
import docking.widgets.OptionDialog;
import generic.jar.ResourceFile;
import generic.theme.GIcon;
import generic.theme.*;
import generic.theme.GThemeDefaults.Colors;
import generic.theme.Gui;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.util.*;
import ghidra.util.datastruct.FixedSizeStack;
@@ -508,7 +507,7 @@ public class GhidraScriptEditorComponentProvider extends ComponentProvider {
FontEditor editor = new FontEditor();
editor.setValue(Gui.getFont(FONT_ID));
editor.showDialog();
Gui.setFont(FONT_ID, (Font) editor.getValue());
ThemeManager.getInstance().setFont(FONT_ID, (Font) editor.getValue());
}
private void save() {
@@ -20,7 +20,7 @@ import docking.DockingWindowManager;
import docking.framework.ApplicationInformationDisplayFactory;
import docking.framework.SplashScreen;
import docking.widgets.PopupKeyStorePasswordProvider;
import generic.theme.Gui;
import generic.theme.ApplicationThemeManager;
import ghidra.docking.util.LookAndFeelUtils;
import ghidra.formats.gfilesystem.crypto.CryptoProviders;
import ghidra.formats.gfilesystem.crypto.PopupGUIPasswordProvider;
@@ -30,6 +30,7 @@ import ghidra.framework.preferences.Preferences;
import ghidra.net.ApplicationKeyManagerFactory;
import ghidra.util.ErrorDisplay;
import ghidra.util.SystemUtilities;
import ghidra.util.task.TaskMonitorAdapter;
public class GhidraApplicationConfiguration extends HeadlessGhidraApplicationConfiguration {
@@ -43,12 +44,13 @@ public class GhidraApplicationConfiguration extends HeadlessGhidraApplicationCon
@Override
protected void initializeApplication() {
Gui.initialize();
ApplicationThemeManager.initialize();
LookAndFeelUtils.performPlatformSpecificFixups();
if (showSplashScreen) {
showUserAgreement();
SplashScreen.showSplashScreen();
this.monitor = new StatusReportingTaskMonitor();
}
super.initializeApplication();
@@ -89,4 +91,17 @@ public class GhidraApplicationConfiguration extends HeadlessGhidraApplicationCon
public ErrorDisplay getErrorDisplay() {
return new DockingErrorDisplay();
}
private static class StatusReportingTaskMonitor extends TaskMonitorAdapter {
@Override
public synchronized void setCancelEnabled(boolean enable) {
// Not permitted
}
@Override
public void setMessage(String message) {
SplashScreen.updateSplashScreenStatus(message);
}
}
}
@@ -66,7 +66,7 @@ public class FileIconService {
}
private void createSubstringMap() {
GThemeValueMap values = Gui.getAllValues();
GThemeValueMap values = ThemeManager.getInstance().getCurrentValues();
List<IconValue> icons = values.getIcons();
for (IconValue iconValue : icons) {
String id = iconValue.getId();
@@ -32,7 +32,7 @@ import org.junit.*;
import docking.test.AbstractDockingTest;
import generic.theme.GColor;
import generic.theme.GThemeDefaults.Colors.Palette;
import generic.theme.Gui;
import generic.theme.ThemeManager;
import ghidra.framework.options.*;
import ghidra.framework.options.OptionsTest.FRUIT;
import ghidra.program.database.ProgramBuilder;
@@ -61,7 +61,7 @@ public class OptionsDBTest extends AbstractDockingTest {
ProgramDB program = builder.getProgram();
txID = program.startTransaction("Test");
options = new OptionsDB(program);
Gui.setColor("color.test", Palette.MAGENTA);
ThemeManager.getInstance().setColor("color.test", Palette.MAGENTA);
testColor = new GColor("color.test");
}
@@ -33,7 +33,7 @@ public class TokenHighlightColors {
float h = (float) Math.random(); // 0-360
float s = .25f; // saturation; gray to full color; full color is too harsh for highlights
float b = 1f; // brightness; black to full color
if (Gui.getActiveTheme().useDarkDefaults()) {
if (Gui.isDarkTheme()) {
s = .5f; // a bit more color against a dark background
b = .5f; // less brightness, as the background is not as bright
}
@@ -92,7 +92,7 @@ src/main/resources/images/page_go.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/page_green.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/play.png||GHIDRA||||END|
src/main/resources/images/preferences-system-windows.png||Tango Icons - Public Domain||||END|
src/main/resources/images/redo.png||GHIDRA||||END|
src/main/resources/images/redo.png||Crystal Clear Icons - LGPL 2.1||||END|
src/main/resources/images/software-update-available.png||Tango Icons - Public Domain|||tango icon set|END|
src/main/resources/images/table.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/tag.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
@@ -100,7 +100,7 @@ src/main/resources/images/text_lowercase.png||FAMFAMFAM Icons - CC 2.5|||famfamf
src/main/resources/images/textfield_rename.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/tip.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/trash-empty.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/undo.png||GHIDRA||||END|
src/main/resources/images/undo.png||Crystal Clear Icons - LGPL 2.1||||END|
src/main/resources/images/user-home.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/view-filter.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/warning.help.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
@@ -82,12 +82,16 @@ color.border.provider.disconnected = orange
color.bg.filechooser = color.bg
color.fg.filechooser = color.fg
color.bg.filechooser.shortcut = lightGray
color.bg.fieldpanel = color.bg
color.fg.fieldpanel = color.fg
color.bg.fieldpanel.selection = color.bg.selection
color.bg.fieldpanel.highlight = color.bg.highlight
color.bg.fieldpanel.selection.and.highlight = green
color.bg.tree = [color]Tree.textBackground
color.bg.tree.selected = [color]Tree.selectionBackground
// docking buttons
color.fg.button = black
@@ -239,3 +243,10 @@ color.bg.tableheader.gradient.end.primary = darkBlue
// docking buttons
color.fg.button = darkGray
color.bg.filechooser.shortcut = system.color.bg.widget
color.bg.tree = color.bg
color.bg.tree.selected = [color]Tree.selectionBackground
icon.undo = eatbits1.png
icon.redo = eatbits2.png
@@ -23,6 +23,7 @@ import javax.swing.*;
import docking.action.*;
import generic.theme.Gui;
import generic.theme.ThemeManager;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
import help.HelpDescriptor;
@@ -801,7 +802,7 @@ public abstract class ComponentProvider implements HelpDescriptor, ActionContext
else {
size = Math.max(size - 1, 3);
}
Gui.setFont(registeredFontId, font.deriveFont((float) size));
ThemeManager.getInstance().setFont(registeredFontId, font.deriveFont((float) size));
}
/**
@@ -38,7 +38,7 @@ import docking.widgets.list.GListCellRenderer;
import docking.widgets.table.GTableCellRenderer;
import docking.widgets.tree.support.GTreeRenderer;
import generic.theme.GThemeDefaults.Colors.Palette;
import generic.theme.Gui;
import ghidra.docking.util.LookAndFeelUtils;
import ghidra.util.HTMLUtilities;
import resources.ResourceManager;
@@ -126,7 +126,7 @@ public class DockingUtils {
public static JSeparator createToolbarSeparator() {
Dimension sepDim = new Dimension(2, ICON_SIZE + 2);
JSeparator separator = new JSeparator(SwingConstants.VERTICAL);
if (Gui.isUsingAquaUI(separator.getUI())) {
if (LookAndFeelUtils.isUsingAquaUI(separator.getUI())) {
separator.setUI(new BasicSeparatorUI());
}
separator.setPreferredSize(sepDim); // ugly work around to force height of separator
@@ -312,7 +312,7 @@ public class StatusBar extends JPanel {
int value = 0;
int delta = 16;
if (Gui.getActiveTheme().useDarkDefaults()) {
if (Gui.isDarkTheme()) {
value = 128;
delta = -16;
}
@@ -32,9 +32,9 @@ import ghidra.util.Msg;
public class ApplicationInformationDisplayFactory {
private static final GIcon ICON_HOME = new GIcon("icon.docking.application.home");
private static final GIcon ICON_16 = new GIcon("icon.docking.application.16");
private static final GIcon ICON_128 = new GIcon("icon.base.application.128");
private static final String ICON_HOME = "icon.docking.application.home";
private static final String ICON_16 = "icon.docking.application.16";
private static final String ICON_128 = "icon.docking.application.128";
static {
PluggableServiceRegistry.registerPluggableService(
@@ -144,13 +144,13 @@ public class ApplicationInformationDisplayFactory {
}
protected Icon getSplashScreenIcon128() {
return ICON_128;
return new GIcon(ICON_128);
}
protected List<Image> doGetWindowIcons() {
List<Image> list = new ArrayList<>();
list.add(ICON_128.getImageIcon().getImage());
list.add(ICON_16.getImageIcon().getImage());
list.add(new GIcon(ICON_128).getImageIcon().getImage());
list.add(new GIcon(ICON_16).getImageIcon().getImage());
return list;
}
@@ -163,7 +163,7 @@ public class ApplicationInformationDisplayFactory {
}
protected Icon doGetHomeIcon() {
return ICON_HOME;
return new GIcon(ICON_HOME);
}
protected Runnable doGetHomeCallback() {
@@ -17,7 +17,7 @@ package docking.framework;
import docking.DockingErrorDisplay;
import docking.widgets.PopupKeyStorePasswordProvider;
import generic.theme.Gui;
import generic.theme.ApplicationThemeManager;
import ghidra.docking.util.LookAndFeelUtils;
import ghidra.framework.ApplicationConfiguration;
import ghidra.net.ApplicationKeyManagerFactory;
@@ -49,7 +49,7 @@ public class DockingApplicationConfiguration extends ApplicationConfiguration {
protected void initializeApplication() {
super.initializeApplication();
Gui.initialize();
ApplicationThemeManager.initialize();
LookAndFeelUtils.performPlatformSpecificFixups();
if (showSplashScreen) {
@@ -24,7 +24,7 @@ import javax.swing.KeyStroke;
import org.apache.commons.lang3.StringUtils;
import docking.action.DockingActionIf;
import generic.theme.Gui;
import ghidra.docking.util.LookAndFeelUtils;
import ghidra.util.StringUtilities;
class DockingToolBarUtils {
@@ -96,7 +96,7 @@ class DockingToolBarUtils {
builder.append(InputEvent.getModifiersExText(modifiers));
// The Aqua LaF does not use the '+' symbol between modifiers
if (!Gui.isUsingAquaUI(button.getUI())) {
if (!LookAndFeelUtils.isUsingAquaUI(button.getUI())) {
builder.append('+');
}
}
@@ -97,6 +97,16 @@ public class MultipleActionDockingToolbarButton extends EmptyBorderButton {
popupContext = createPopupContext();
}
@Override
public void updateUI() {
removeMouseListener(popupListener);
super.updateUI();
installMouseListeners();
}
private void installMouseListeners() {
MouseListener[] mouseListeners = getMouseListeners();
for (MouseListener mouseListener : mouseListeners) {
@@ -43,9 +43,11 @@ public class ExportThemeDialog extends DialogComponentProvider {
private JTextField fileTextField;
private GCheckBox includeDefaultsCheckbox;
private boolean exportAsZip;
private ThemeManager themeManager;
public ExportThemeDialog(boolean exportAsZip) {
public ExportThemeDialog(ThemeManager themeManager, boolean exportAsZip) {
super("Export Theme");
this.themeManager = themeManager;
this.exportAsZip = exportAsZip;
addWorkPanel(buildMainPanel());
addOKButton();
@@ -62,7 +64,7 @@ public class ExportThemeDialog extends DialogComponentProvider {
private boolean exportTheme() {
String themeName = nameField.getText();
GTheme activeTheme = Gui.getActiveTheme();
GTheme activeTheme = themeManager.getActiveTheme();
LafType laf = activeTheme.getLookAndFeelType();
boolean useDarkDefaults = activeTheme.useDarkDefaults();
File file = new File(fileTextField.getText());
@@ -87,10 +89,10 @@ public class ExportThemeDialog extends DialogComponentProvider {
private void loadValues(GTheme exportTheme) {
if (includeDefaultsCheckbox.isSelected()) {
exportTheme.load(Gui.getAllValues());
exportTheme.load(themeManager.getCurrentValues());
}
else {
exportTheme.load(Gui.getNonDefaultValues());
exportTheme.load(themeManager.getNonDefaultValues());
}
}
@@ -114,7 +116,7 @@ public class ExportThemeDialog extends DialogComponentProvider {
private Component buildNameField() {
nameField = new JTextField(25);
nameField.setText(Gui.getActiveTheme().getName());
nameField.setText(themeManager.getActiveTheme().getName());
return nameField;
}
@@ -127,7 +129,7 @@ public class ExportThemeDialog extends DialogComponentProvider {
private Component buildFilePanel() {
File homeDir = new File(System.getProperty("user.home")); // prefer the home directory
String name = Gui.getActiveTheme().getName();
String name = themeManager.getActiveTheme().getName();
String filename = name.replaceAll(" ", "_") + ".";
filename += exportAsZip ? GTheme.ZIP_FILE_EXTENSION : GTheme.FILE_EXTENSION;
File file = new File(homeDir, filename);
@@ -37,7 +37,7 @@ public class IconValueEditor extends ThemeValueEditor<Icon> {
@Override
protected Icon getRawValue(String id) {
return Gui.getIcon(id, true);
return Gui.getIcon(id);
}
@Override
@@ -29,7 +29,7 @@ import docking.action.ActionContextProvider;
import docking.widgets.table.GFilterTable;
import docking.widgets.table.GTable;
import generic.theme.ColorValue;
import generic.theme.Gui;
import generic.theme.ThemeManager;
import ghidra.util.Swing;
/**
@@ -41,10 +41,12 @@ public class ThemeColorTable extends JPanel implements ActionContextProvider {
private ColorValueEditor colorEditor = new ColorValueEditor(this::colorValueChanged);
private GTable table;
private GFilterTable<ColorValue> filterTable;
private ThemeManager themeManager;
public ThemeColorTable() {
public ThemeColorTable(ThemeManager themeManager) {
super(new BorderLayout());
colorTableModel = new ThemeColorTableModel();
this.themeManager = themeManager;
colorTableModel = new ThemeColorTableModel(themeManager);
filterTable = new GFilterTable<>(colorTableModel);
table = filterTable.getTable();
@@ -87,7 +89,7 @@ public class ThemeColorTable extends JPanel implements ActionContextProvider {
// run later - don't rock the boat in the middle of a listener callback
Swing.runLater(() -> {
ColorValue newValue = (ColorValue) event.getNewValue();
Gui.setColor(newValue);
themeManager.setColor(newValue);
});
}
@@ -43,9 +43,11 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel<ColorValue, O
private GThemeValueMap defaultValues;
private GThemeValueMap lightDefaultValues;
private GThemeValueMap darkDefaultValues;
private ThemeManager themeManager;
public ThemeColorTableModel() {
public ThemeColorTableModel(ThemeManager themeManager) {
super(new ServiceProviderStub());
this.themeManager = themeManager;
load();
}
@@ -53,7 +55,7 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel<ColorValue, O
* Reloads the just the current values shown in the table. Called whenever a color changes.
*/
public void reloadCurrent() {
currentValues = Gui.getAllValues();
currentValues = themeManager.getCurrentValues();
colors = currentValues.getColors();
fireTableDataChanged();
}
@@ -67,20 +69,17 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel<ColorValue, O
fireTableDataChanged();
}
/**
* Returns the original value for the current theme
*/
public ColorValue getThemeValue(String id) {
ColorValue getThemeValue(String id) {
return themeValues.getColor(id);
}
private void load() {
currentValues = Gui.getAllValues();
currentValues = themeManager.getCurrentValues();
colors = currentValues.getColors();
themeValues = Gui.getThemeValues();
defaultValues = Gui.getDefaults();
lightDefaultValues = Gui.getApplicationLightDefaults();
darkDefaultValues = Gui.getApplicationDarkDefaults();
themeValues = themeManager.getThemeValues();
defaultValues = themeManager.getDefaults();
lightDefaultValues = themeManager.getApplicationLightDefaults();
darkDefaultValues = themeManager.getApplicationDarkDefaults();
}
@@ -155,7 +154,10 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel<ColorValue, O
if (colorValue == null) {
return null;
}
Color color = colorValue.get(valueMap);
Color color = colorValue.hasResolvableValue(valueMap) ? colorValue.get(valueMap) : null;
if (color == null) {
return null;
}
return new ResolvedColor(id, colorValue.getReferenceId(), color);
}
@@ -49,8 +49,11 @@ public class ThemeDialog extends DialogComponentProvider {
private ThemeFontTable fontTable;
private ThemeIconTable iconTable;
public ThemeDialog() {
private ThemeManager themeManager;
public ThemeDialog(ThemeManager themeManager) {
super("Theme Dialog", false);
this.themeManager = themeManager;
addWorkPanel(createMainPanel());
addDismissButton();
@@ -79,7 +82,7 @@ public class ThemeDialog extends DialogComponentProvider {
.enabledWhen(c -> c.isChanged())
.popupWhen(c -> true)
.helpLocation(new HelpLocation("Theming", "Restore_Value"))
.onAction(c -> c.getThemeValue().installValue())
.onAction(c -> c.getThemeValue().installValue(themeManager))
.build();
addAction(resetValueAction);
}
@@ -93,44 +96,44 @@ public class ThemeDialog extends DialogComponentProvider {
}
private boolean handleChanges() {
if (Gui.hasThemeChanges()) {
if (themeManager.hasThemeChanges()) {
int result = OptionDialog.showYesNoCancelDialog(null, "Close Theme Dialog",
"You have changed the theme.\n Do you want save your changes?");
if (result == OptionDialog.CANCEL_OPTION) {
return false;
}
if (result == OptionDialog.YES_OPTION) {
return ThemeUtils.saveThemeChanges();
return ThemeUtils.saveThemeChanges(themeManager);
}
Gui.restoreThemeValues();
themeManager.restoreThemeValues();
}
return true;
}
protected void saveCallback() {
ThemeUtils.saveThemeChanges();
ThemeUtils.saveThemeChanges(themeManager);
}
private void restoreCallback() {
if (Gui.hasThemeChanges()) {
if (themeManager.hasThemeChanges()) {
int result = OptionDialog.showYesNoDialog(null, "Restore Theme Values",
"Are you sure you want to discard all your changes?");
if (result == OptionDialog.NO_OPTION) {
return;
}
}
Gui.restoreThemeValues();
themeManager.restoreThemeValues();
}
private void reloadDefaultsCallback() {
if (Gui.hasThemeChanges()) {
if (themeManager.hasThemeChanges()) {
int result = OptionDialog.showYesNoDialog(null, "Reload Ghidra Default Values",
"This will discard all your theme changes. Continue?");
if (result == OptionDialog.NO_OPTION) {
return;
}
}
Gui.reloadApplicationDefaults();
themeManager.reloadApplicationDefaults();
}
private void reset() {
@@ -147,15 +150,15 @@ public class ThemeDialog extends DialogComponentProvider {
return;
}
if (!ThemeUtils.askToSaveThemeChanges()) {
if (!ThemeUtils.askToSaveThemeChanges(themeManager)) {
Swing.runLater(() -> updateCombo());
return;
}
String themeName = (String) e.getItem();
Swing.runLater(() -> {
GTheme theme = Gui.getTheme(themeName);
Gui.setTheme(theme);
GTheme theme = themeManager.getTheme(themeName);
themeManager.setTheme(theme);
if (theme.getLookAndFeelType() == LafType.GTK) {
setStatusText(
"Warning - Themes using the GTK LookAndFeel do not support changing java component colors, fonts or icons.",
@@ -171,7 +174,7 @@ public class ThemeDialog extends DialogComponentProvider {
}
private void updateButtons() {
boolean hasChanges = Gui.hasThemeChanges();
boolean hasChanges = themeManager.hasThemeChanges();
saveButton.setEnabled(hasChanges);
restoreButton.setEnabled(hasChanges);
}
@@ -194,25 +197,25 @@ public class ThemeDialog extends DialogComponentProvider {
}
private void updateCombo() {
Set<GTheme> supportedThemes = Gui.getSupportedThemes();
Set<GTheme> supportedThemes = themeManager.getSupportedThemes();
List<String> themeNames =
supportedThemes.stream().map(t -> t.getName()).collect(Collectors.toList());
Collections.sort(themeNames);
combo.removeItemListener(comboListener);
combo.setModel(new DefaultComboBoxModel<String>(new Vector<String>(themeNames)));
combo.setSelectedItem(Gui.getActiveTheme().getName());
combo.setSelectedItem(themeManager.getActiveTheme().getName());
combo.addItemListener(comboListener);
}
private Component buildThemeCombo() {
JPanel panel = new JPanel();
Set<GTheme> supportedThemes = Gui.getSupportedThemes();
Set<GTheme> supportedThemes = themeManager.getSupportedThemes();
List<String> themeNames =
supportedThemes.stream().map(t -> t.getName()).collect(Collectors.toList());
Collections.sort(themeNames);
combo = new GhidraComboBox<>(themeNames);
combo.setSelectedItem(Gui.getActiveTheme().getName());
combo.setSelectedItem(themeManager.getActiveTheme().getName());
combo.addItemListener(comboListener);
panel.add(new JLabel("Theme: "), BorderLayout.WEST);
@@ -223,9 +226,9 @@ public class ThemeDialog extends DialogComponentProvider {
private Component buildTabedTables() {
tabbedPane = new JTabbedPane();
colorTable = new ThemeColorTable();
fontTable = new ThemeFontTable();
iconTable = new ThemeIconTable();
colorTable = new ThemeColorTable(themeManager);
fontTable = new ThemeFontTable(themeManager);
iconTable = new ThemeIconTable(themeManager);
tabbedPane.add("Colors", colorTable);
tabbedPane.add("Fonts", fontTable);
tabbedPane.add("Icons", iconTable);
@@ -250,12 +253,16 @@ public class ThemeDialog extends DialogComponentProvider {
return saveButton;
}
public static void editTheme() {
/**
* Edits the current theme
* @param themeManager the application ThemeManager
*/
public static void editTheme(ThemeManager themeManager) {
if (INSTANCE != null) {
INSTANCE.toFront();
return;
}
INSTANCE = new ThemeDialog();
INSTANCE = new ThemeDialog(themeManager);
DockingWindowManager.showDialog(INSTANCE);
}
@@ -29,7 +29,7 @@ import docking.action.ActionContextProvider;
import docking.widgets.table.GFilterTable;
import docking.widgets.table.GTable;
import generic.theme.FontValue;
import generic.theme.Gui;
import generic.theme.ThemeManager;
import ghidra.util.Swing;
/**
@@ -41,11 +41,13 @@ public class ThemeFontTable extends JPanel implements ActionContextProvider {
private FontValueEditor fontEditor = new FontValueEditor(this::fontValueChanged);
private GTable table;
private GFilterTable<FontValue> filterTable;
private ThemeManager themeManager;
public ThemeFontTable() {
public ThemeFontTable(ThemeManager themeManager) {
super(new BorderLayout());
this.themeManager = themeManager;
fontTableModel = new ThemeFontTableModel();
fontTableModel = new ThemeFontTableModel(themeManager);
filterTable = new GFilterTable<>(fontTableModel);
table = filterTable.getTable();
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
@@ -86,7 +88,7 @@ public class ThemeFontTable extends JPanel implements ActionContextProvider {
// run later - don't rock the boat in the middle of a listener callback
Swing.runLater(() -> {
FontValue newValue = (FontValue) event.getNewValue();
Gui.setFont(newValue);
themeManager.setFont(newValue);
});
}
@@ -39,9 +39,11 @@ public class ThemeFontTableModel extends GDynamicColumnTableModel<FontValue, Obj
private GThemeValueMap currentValues;
private GThemeValueMap themeValues;
private GThemeValueMap defaultValues;
private ThemeManager themeManager;
public ThemeFontTableModel() {
public ThemeFontTableModel(ThemeManager themeManager) {
super(new ServiceProviderStub());
this.themeManager = themeManager;
load();
}
@@ -49,7 +51,7 @@ public class ThemeFontTableModel extends GDynamicColumnTableModel<FontValue, Obj
* Reloads the just the current values shown in the table. Called whenever a font changes.
*/
public void reloadCurrent() {
currentValues = Gui.getAllValues();
currentValues = themeManager.getCurrentValues();
fonts = currentValues.getFonts();
fireTableDataChanged();
}
@@ -64,10 +66,10 @@ public class ThemeFontTableModel extends GDynamicColumnTableModel<FontValue, Obj
}
private void load() {
currentValues = Gui.getAllValues();
currentValues = themeManager.getCurrentValues();
fonts = currentValues.getFonts();
themeValues = Gui.getThemeValues();
defaultValues = Gui.getDefaults();
themeValues = themeManager.getThemeValues();
defaultValues = themeManager.getDefaults();
}
@Override
@@ -26,8 +26,8 @@ import docking.ActionContext;
import docking.action.ActionContextProvider;
import docking.widgets.table.GFilterTable;
import docking.widgets.table.GTable;
import generic.theme.Gui;
import generic.theme.IconValue;
import generic.theme.ThemeManager;
import ghidra.util.Swing;
/**
@@ -39,10 +39,12 @@ public class ThemeIconTable extends JPanel implements ActionContextProvider {
private IconValueEditor iconEditor = new IconValueEditor(this::iconValueChanged);
private GTable table;
private GFilterTable<IconValue> filterTable;
private ThemeManager themeManager;
public ThemeIconTable() {
public ThemeIconTable(ThemeManager themeManager) {
super(new BorderLayout());
iconTableModel = new ThemeIconTableModel();
this.themeManager = themeManager;
iconTableModel = new ThemeIconTableModel(themeManager);
filterTable = new GFilterTable<>(iconTableModel);
table = filterTable.getTable();
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
@@ -82,7 +84,7 @@ public class ThemeIconTable extends JPanel implements ActionContextProvider {
// run later - don't rock the boat in the middle of a listener callback
Swing.runLater(() -> {
IconValue newValue = (IconValue) event.getNewValue();
Gui.setIcon(newValue);
themeManager.setIcon(newValue);
});
}
@@ -39,9 +39,11 @@ public class ThemeIconTableModel extends GDynamicColumnTableModel<IconValue, Obj
private GThemeValueMap currentValues;
private GThemeValueMap themeValues;
private GThemeValueMap defaultValues;
private ThemeManager themeManager;
public ThemeIconTableModel() {
public ThemeIconTableModel(ThemeManager themeManager) {
super(new ServiceProviderStub());
this.themeManager = themeManager;
load();
}
@@ -49,7 +51,7 @@ public class ThemeIconTableModel extends GDynamicColumnTableModel<IconValue, Obj
* Reloads the just the current values shown in the table. Called whenever an icon changes.
*/
public void reloadCurrent() {
currentValues = Gui.getAllValues();
currentValues = themeManager.getCurrentValues();
icons = currentValues.getIcons();
fireTableDataChanged();
}
@@ -64,10 +66,10 @@ public class ThemeIconTableModel extends GDynamicColumnTableModel<IconValue, Obj
}
private void load() {
currentValues = Gui.getAllValues();
currentValues = themeManager.getCurrentValues();
icons = currentValues.getIcons();
themeValues = Gui.getThemeValues();
defaultValues = Gui.getDefaults();
themeValues = themeManager.getThemeValues();
defaultValues = themeManager.getDefaults();
}
@Override
@@ -43,17 +43,17 @@ public class ThemeUtils {
* overwrites an existing file.
* @return true if the operation was not cancelled
*/
public static boolean askToSaveThemeChanges() {
if (Gui.hasThemeChanges()) {
public static boolean askToSaveThemeChanges(ThemeManager themeManager) {
if (themeManager.hasThemeChanges()) {
int result = OptionDialog.showYesNoCancelDialog(null, "Save Theme Changes?",
"You have made changes to the theme.\n Do you want save your changes?");
if (result == OptionDialog.CANCEL_OPTION) {
return false;
}
if (result == OptionDialog.YES_OPTION) {
return ThemeUtils.saveThemeChanges();
return ThemeUtils.saveThemeChanges(themeManager);
}
Gui.reloadApplicationDefaults();
themeManager.reloadApplicationDefaults();
}
return true;
}
@@ -63,32 +63,33 @@ public class ThemeUtils {
* name and asking to overwrite an existing file.
* @return true if the operation was not cancelled
*/
public static boolean saveThemeChanges() {
GTheme activeTheme = Gui.getActiveTheme();
public static boolean saveThemeChanges(ThemeManager themeManager) {
GTheme activeTheme = themeManager.getActiveTheme();
String name = activeTheme.getName();
while (!canSaveToName(name)) {
while (!canSaveToName(themeManager, name)) {
name = getNameFromUser(name);
if (name == null) {
return false;
}
}
return saveCurrentValues(name);
return saveCurrentValues(themeManager, name);
}
/**
* Resets the theme to the default, handling the case where the current theme has changes.
*/
public static void resetThemeToDefault() {
if (askToSaveThemeChanges()) {
Gui.setTheme(Gui.getDefaultTheme());
public static void resetThemeToDefault(ThemeManager themeManager) {
if (askToSaveThemeChanges(themeManager)) {
themeManager.setTheme(themeManager.getDefaultTheme());
}
}
/**
* Imports a theme. Handles the case where there are existing changes to the current theme.
* @param themeManager the application ThemeManager
*/
public static void importTheme() {
public static void importTheme(ThemeManager themeManager) {
GhidraFileChooser chooser = new GhidraFileChooser(null);
chooser.setTitle("Choose Theme File");
chooser.setApproveButtonToolTipText("Select File");
@@ -100,14 +101,14 @@ public class ThemeUtils {
if (file == null) {
return;
}
importTheme(file);
importTheme(themeManager, file);
}
static void importTheme(File themeFile) {
if (!ThemeUtils.askToSaveThemeChanges()) {
static void importTheme(ThemeManager themeManager, File themeFile) {
if (!ThemeUtils.askToSaveThemeChanges(themeManager)) {
return;
}
GTheme startingTheme = Gui.getActiveTheme();
GTheme startingTheme = themeManager.getActiveTheme();
try {
GTheme imported = GTheme.loadTheme(themeFile);
// by setting the theme, we can let the normal save handle all the edge cases
@@ -115,9 +116,9 @@ public class ThemeUtils {
// Also, the imported theme may contain default values which we don't want to save. So
// by going through the usual save mechanism, only values that differ from defaults
// be saved.
Gui.setTheme(imported);
if (!ThemeUtils.saveThemeChanges()) {
Gui.setTheme(startingTheme);
themeManager.setTheme(imported);
if (!ThemeUtils.saveThemeChanges(themeManager)) {
themeManager.setTheme(startingTheme);
}
}
catch (IOException e) {
@@ -130,12 +131,13 @@ public class ThemeUtils {
/**
* 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
*/
public static void exportTheme() {
if (!ThemeUtils.askToSaveThemeChanges()) {
public static void exportTheme(ThemeManager themeManager) {
if (!ThemeUtils.askToSaveThemeChanges(themeManager)) {
return;
}
boolean hasExternalIcons = !Gui.getActiveTheme().getExternalIconFiles().isEmpty();
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.)";
@@ -151,16 +153,16 @@ public class ThemeUtils {
}
boolean exportAsZip = result == OptionDialog.OPTION_ONE;
ExportThemeDialog dialog = new ExportThemeDialog(exportAsZip);
ExportThemeDialog dialog = new ExportThemeDialog(themeManager, exportAsZip);
DockingWindowManager.showDialog(dialog);
}
/**
* Prompts for and deletes a selected theme.
*/
public static void deleteTheme() {
public static void deleteTheme(ThemeManager themeManager) {
List<GTheme> savedThemes = new ArrayList<>(
Gui.getAllThemes().stream().filter(t -> t.getFile() != null).toList());
themeManager.getAllThemes().stream().filter(t -> t.getFile() != null).toList());
if (savedThemes.isEmpty()) {
Msg.showInfo(ThemeUtils.class, null, "Delete Theme", "There are no deletable themes");
return;
@@ -171,7 +173,7 @@ public class ThemeUtils {
if (selectedTheme == null) {
return;
}
if (Gui.getActiveTheme().equals(selectedTheme)) {
if (themeManager.getActiveTheme().equals(selectedTheme)) {
Msg.showWarn(ThemeUtils.class, null, "Delete Failed",
"Can't delete the current theme.");
return;
@@ -180,7 +182,7 @@ public class ThemeUtils {
int result = OptionDialog.showYesNoDialog(null, "Delete Theme: " + fileTheme.getName(),
"Are you sure you want to delete theme " + fileTheme.getName());
if (result == OptionDialog.YES_OPTION) {
Gui.deleteTheme(fileTheme);
themeManager.deleteTheme(fileTheme);
}
}
@@ -190,8 +192,8 @@ public class ThemeUtils {
return inputDialog.getValue();
}
private static boolean canSaveToName(String name) {
GTheme existing = Gui.getTheme(name);
private static boolean canSaveToName(ThemeManager themeManager, String name) {
GTheme existing = themeManager.getTheme(name);
// if no theme exists with that name, then we are save to save it
if (existing == null) {
return true;
@@ -210,17 +212,17 @@ public class ThemeUtils {
return result == OptionDialog.YES_OPTION;
}
private static boolean saveCurrentValues(String themeName) {
GTheme activeTheme = Gui.getActiveTheme();
private static boolean saveCurrentValues(ThemeManager themeManager, String themeName) {
GTheme activeTheme = themeManager.getActiveTheme();
File file = getSaveFile(themeName);
GTheme newTheme = new GTheme(file, themeName, activeTheme.getLookAndFeelType(),
activeTheme.useDarkDefaults());
newTheme.load(Gui.getNonDefaultValues());
newTheme.load(themeManager.getNonDefaultValues());
try {
newTheme.save();
Gui.addTheme(newTheme);
Gui.setTheme(newTheme);
themeManager.addTheme(newTheme);
themeManager.setTheme(newTheme);
}
catch (IOException e) {
Msg.showError(ThemeUtils.class, null, "I/O Error",
@@ -24,7 +24,7 @@ import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import generic.theme.Gui;
import ghidra.docking.util.LookAndFeelUtils;
import resources.ResourceManager;
/**
@@ -123,7 +123,7 @@ public class EmptyBorderButton extends JButton {
// Mac OSX LNF doesn't give us rollover callbacks, so we have to add a mouse listener to
// do the work
if (Gui.isUsingAquaUI(getUI())) {
if (LookAndFeelUtils.isUsingAquaUI(getUI())) {
addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
@@ -38,7 +38,6 @@ import docking.widgets.label.GLabel;
import docking.widgets.list.GListCellRenderer;
import generic.theme.GColor;
import generic.theme.GIcon;
import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.framework.preferences.Preferences;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
@@ -74,6 +73,8 @@ public class GhidraFileChooser extends DialogComponentProvider implements FileFi
static final String UP_BUTTON_NAME = "UP_BUTTON";
private static final Color FOREROUND_COLOR = new GColor("color.fg.filechooser");
private static final Color BACKGROUND_COLOR = new GColor("color.bg.filechooser");
private static final Color SHORTCUT_BACKGROUND_COLOR =
new GColor("color.bg.filechooser.shortcut");
static final String PREFERENCES_PREFIX = "G_FILE_CHOOSER";
private static final String WIDTH_PREFERENCE_PREFIX = PREFERENCES_PREFIX + ".WIDTH.";
private static final String HEIGHT_PREFERENCE_PREFIX = PREFERENCES_PREFIX + ".HEIGHT.";
@@ -343,7 +344,7 @@ public class GhidraFileChooser extends DialogComponentProvider implements FileFi
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createLoweredBevelBorder());
panel.setBackground(Palette.DARK_GRAY);
panel.setBackground(SHORTCUT_BACKGROUND_COLOR);
panel.add(shortCutPanel, BorderLayout.NORTH);
return panel;
}
@@ -22,12 +22,14 @@ import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import docking.widgets.AbstractGCellRenderer;
import generic.theme.GColor;
import generic.theme.Gui;
import ghidra.docking.settings.*;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
@@ -56,7 +58,13 @@ public class GTableCellRenderer extends AbstractGCellRenderer implements TableCe
* Constructs a new GTableCellRenderer.
*/
public GTableCellRenderer() {
// When the Look And Feel changes, renderers are not auto updated because they
// are not part of the component tree. So listen for a change to the Look And Feel.
Gui.addThemeListener(e -> {
if (e.isLookAndFeelChanged()) {
updateUI();
}
});
}
/**
@@ -100,10 +108,7 @@ public class GTableCellRenderer extends AbstractGCellRenderer implements TableCe
"Using a GTableCellRenderer in a non-GTable table. (Model class: " +
table.getModel().getClass().getName() + ")");
}
// check if LookAndFeel has changed
if (UIManager.getUI(this) != getUI()) {
updateUI();
}
GTable gTable = (GTable) table;
GTableCellRenderingData data = gTable.getRenderingData(column);
Object rowObject = null;
@@ -45,6 +45,7 @@ import docking.widgets.tree.internal.*;
import docking.widgets.tree.support.*;
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
import docking.widgets.tree.tasks.*;
import generic.theme.*;
import generic.timer.ExpiringSwingTimer;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
@@ -55,8 +56,8 @@ import ghidra.util.worker.PriorityWorker;
* Class for creating a JTree that supports filtering, threading, and a progress bar.
*/
public class GTree extends JPanel implements BusyListener {
public class GTree extends JPanel implements BusyListener, ThemeListener {
private static final Color BACKGROUND = new GColor("color.bg.tree");
private AutoScrollTree tree;
private GTreeModel model;
@@ -134,6 +135,7 @@ public class GTree extends JPanel implements BusyListener {
uniquePreferenceKey));
filterUpdateManager = new SwingUpdateManager(1000, 30000, () -> updateModelFilter());
Gui.addThemeListener(this);
}
/**
@@ -146,6 +148,13 @@ public class GTree extends JPanel implements BusyListener {
threadLocalMonitor.set(monitor);
}
@Override
public void themeChanged(ThemeEvent event) {
if (event.isLookAndFeelChanged()) {
model.fireNodeStructureChanged(getModelRoot());
}
}
/**
* Returns the monitor in associated with the GTree for the calling thread. This method is
* designed to be used by slow loading nodes that are loading <b>off the Swing thread</b>. Some
@@ -1391,6 +1400,7 @@ public class GTree extends JPanel implements BusyListener {
public AutoScrollTree(TreeModel model) {
super(model);
setBackground(BACKGROUND);
scroller = new AutoscrollAdapter(this, 5);
setRowHeight(-1);// variable size rows
@@ -31,6 +31,8 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent
private static final Color VALID_DROP_TARGET_COLOR = new GColor("color.bg.tree.drag");
private static final int DEFAULT_MIN_ICON_WIDTH = 22;
private static final Color BACKGROUND_UNSELECTED = new GColor("color.bg.tree");
private static final Color BACKGROUND_SELECTED = new GColor("color.bg.tree.selected");
private Object dropTarget;
private boolean paintDropTarget;
@@ -41,6 +43,8 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent
public GTreeRenderer() {
setHTMLRenderingEnabled(false);
setBackgroundNonSelectionColor(BACKGROUND_UNSELECTED);
setBackgroundSelectionColor(BACKGROUND_SELECTED);
}
@Override
@@ -21,10 +21,12 @@ import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import docking.framework.ApplicationInformationDisplayFactory;
import generic.theme.LafType;
import generic.theme.ThemeManager;
import ghidra.framework.preferences.Preferences;
import ghidra.util.SystemUtilities;
@@ -81,4 +83,30 @@ public class LookAndFeelUtils {
}
}
}
/**
* Returns the {@link LafType} for the currently active {@link LookAndFeel}
* @return the {@link LafType} for the currently active {@link LookAndFeel}
*/
public static LafType getLookAndFeelType() {
return ThemeManager.getInstance().getLookAndFeelType();
}
/**
* 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 static boolean isUsingAquaUI(ComponentUI UI) {
return ThemeManager.getInstance().isUsingAquaUI(UI);
}
/**
* Returns true if 'Nimbus' is the current Look and Feel
* @return true if 'Nimbus' is the current Look and Feel
*/
public static boolean isUsingNimbusUI() {
return ThemeManager.getInstance().isUsingNimbusUI();
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 B

After

Width:  |  Height:  |  Size: 692 B

@@ -41,67 +41,69 @@ import generic.theme.builtin.NimbusTheme;
public class ThemeUtilsTest extends AbstractDockingTest {
private Color testColor = Palette.RED;
private ThemeManager themeManager;
@Before
public void setup() {
themeManager = ThemeManager.getInstance();
GTheme nimbusTheme = new NimbusTheme();
GTheme metalTheme = new MetalTheme();
Gui.addTheme(nimbusTheme);
Gui.addTheme(metalTheme);
Gui.setTheme(nimbusTheme);
themeManager.addTheme(nimbusTheme);
themeManager.addTheme(metalTheme);
themeManager.setTheme(nimbusTheme);
// get rid of any leftover imported themes from previous tests
Set<GTheme> allThemes = Gui.getAllThemes();
Set<GTheme> allThemes = themeManager.getAllThemes();
for (GTheme theme : allThemes) {
if (!(theme instanceof DiscoverableGTheme)) {
Gui.deleteTheme(theme);
themeManager.deleteTheme(theme);
}
}
}
@Test
public void testImportThemeNonZip() throws IOException {
assertEquals("Nimbus Theme", Gui.getActiveTheme().getName());
assertEquals("Nimbus Theme", themeManager.getActiveTheme().getName());
File themeFile = createThemeFile("Bob");
ThemeUtils.importTheme(themeFile);
assertEquals("Bob", Gui.getActiveTheme().getName());
ThemeUtils.importTheme(themeManager, themeFile);
assertEquals("Bob", themeManager.getActiveTheme().getName());
}
@Test
public void testImportThemeFromZip() throws IOException {
assertEquals("Nimbus Theme", Gui.getActiveTheme().getName());
assertEquals("Nimbus Theme", themeManager.getActiveTheme().getName());
File themeFile = createZipThemeFile("zippy");
ThemeUtils.importTheme(themeFile);
assertEquals("zippy", Gui.getActiveTheme().getName());
ThemeUtils.importTheme(themeManager, themeFile);
assertEquals("zippy", themeManager.getActiveTheme().getName());
}
@Test
public void testImportThemeWithCurrentChangesCancelled() throws IOException {
assertEquals("Nimbus Theme", Gui.getActiveTheme().getName());
Gui.setColor("Panel.background", testColor);
assertTrue(Gui.hasThemeChanges());
assertEquals("Nimbus Theme", themeManager.getActiveTheme().getName());
themeManager.setColor("Panel.background", testColor);
assertTrue(themeManager.hasThemeChanges());
File themeFile = createThemeFile("Bob");
runSwingLater(() -> ThemeUtils.importTheme(themeFile));
runSwingLater(() -> ThemeUtils.importTheme(themeManager, themeFile));
OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
assertNotNull(dialog);
assertEquals("Save Theme Changes?", dialog.getTitle());
pressButtonByText(dialog, "Cancel");
waitForSwing();
assertEquals("Nimbus Theme", Gui.getActiveTheme().getName());
assertEquals("Nimbus Theme", themeManager.getActiveTheme().getName());
}
@Test
public void testImportThemeWithCurrentChangesSaved() throws IOException {
assertEquals("Nimbus Theme", Gui.getActiveTheme().getName());
assertEquals("Nimbus Theme", themeManager.getActiveTheme().getName());
// make a change in the current theme, so you get asked to save
Gui.setColor("Panel.background", testColor);
assertTrue(Gui.hasThemeChanges());
themeManager.setColor("Panel.background", testColor);
assertTrue(themeManager.hasThemeChanges());
File themeFile = createThemeFile("Bob");
runSwingLater(() -> ThemeUtils.importTheme(themeFile));
runSwingLater(() -> ThemeUtils.importTheme(themeManager, themeFile));
OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
assertNotNull(dialog);
assertEquals("Save Theme Changes?", dialog.getTitle());
@@ -111,32 +113,32 @@ public class ThemeUtilsTest extends AbstractDockingTest {
runSwing(() -> inputDialog.setValue("Joe"));
pressButtonByText(inputDialog, "OK");
waitForSwing();
assertEquals("Bob", Gui.getActiveTheme().getName());
assertNotNull(Gui.getTheme("Joe"));
assertEquals("Bob", themeManager.getActiveTheme().getName());
assertNotNull(themeManager.getTheme("Joe"));
}
@Test
public void testImportThemeWithCurrentChangesThrownAway() throws IOException {
assertEquals("Nimbus Theme", Gui.getActiveTheme().getName());
assertEquals("Nimbus Theme", themeManager.getActiveTheme().getName());
// make a change in the current theme, so you get asked to save
Gui.setColor("Panel.background", testColor);
assertTrue(Gui.hasThemeChanges());
themeManager.setColor("Panel.background", testColor);
assertTrue(themeManager.hasThemeChanges());
File bobThemeFile = createThemeFile("Bob");
runSwingLater(() -> ThemeUtils.importTheme(bobThemeFile));
runSwingLater(() -> ThemeUtils.importTheme(themeManager, bobThemeFile));
OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
assertNotNull(dialog);
assertEquals("Save Theme Changes?", dialog.getTitle());
pressButtonByText(dialog, "No");
waitForSwing();
assertEquals("Bob", Gui.getActiveTheme().getName());
assertEquals("Bob", themeManager.getActiveTheme().getName());
}
@Test
public void testExportThemeAsZip() throws IOException {
runSwingLater(() -> ThemeUtils.exportTheme());
runSwingLater(() -> ThemeUtils.exportTheme(themeManager));
OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
pressButtonByText(dialog, "Export Zip");
ExportThemeDialog exportDialog = waitForDialogComponent(ExportThemeDialog.class);
@@ -151,7 +153,7 @@ public class ThemeUtilsTest extends AbstractDockingTest {
@Test
public void testExportThemeAsFile() throws IOException {
runSwingLater(() -> ThemeUtils.exportTheme());
runSwingLater(() -> ThemeUtils.exportTheme(themeManager));
OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
pressButtonByText(dialog, "Export File");
ExportThemeDialog exportDialog = waitForDialogComponent(ExportThemeDialog.class);
@@ -167,29 +169,29 @@ public class ThemeUtilsTest extends AbstractDockingTest {
@Test
public void testDeleteTheme() throws IOException {
File themeFile = createThemeFile("Bob");
ThemeUtils.importTheme(themeFile);
ThemeUtils.importTheme(themeManager, themeFile);
themeFile = createThemeFile("Joe");
ThemeUtils.importTheme(themeFile);
ThemeUtils.importTheme(themeManager, themeFile);
themeFile = createThemeFile("Lisa");
ThemeUtils.importTheme(themeFile);
ThemeUtils.importTheme(themeManager, themeFile);
assertNotNull(Gui.getTheme("Bob"));
assertNotNull(Gui.getTheme("Joe"));
assertNotNull(Gui.getTheme("Lisa"));
assertNotNull(themeManager.getTheme("Bob"));
assertNotNull(themeManager.getTheme("Joe"));
assertNotNull(themeManager.getTheme("Lisa"));
runSwingLater(() -> ThemeUtils.deleteTheme());
runSwingLater(() -> ThemeUtils.deleteTheme(themeManager));
@SuppressWarnings("unchecked")
SelectFromListDialog<GTheme> dialog = waitForDialogComponent(SelectFromListDialog.class);
runSwing(() -> dialog.setSelectedObject(Gui.getTheme("Bob")));
runSwing(() -> dialog.setSelectedObject(themeManager.getTheme("Bob")));
pressButtonByText(dialog, "OK");
OptionDialog optionDialog = waitForDialogComponent(OptionDialog.class);
pressButtonByText(optionDialog, "Yes");
waitForSwing();
assertNotNull(Gui.getTheme("Bob"));
assertNull(Gui.getTheme("Joe"));
assertNotNull(Gui.getTheme("Lisa"));
assertNotNull(themeManager.getTheme("Bob"));
assertNull(themeManager.getTheme("Joe"));
assertNotNull(themeManager.getTheme("Lisa"));
}
@@ -231,7 +233,7 @@ public class ThemeUtilsTest extends AbstractDockingTest {
File file = createTempFile("Test_Theme", ".theme.zip");
GTheme outputTheme = new GTheme(file, themeName, LafType.METAL, false);
outputTheme.addColor(new ColorValue("Panel.Background", testColor));
outputTheme.saveToZip(file, false);
new ThemeWriter(outputTheme).writeThemeToZipFile(file);
return file;
}
@@ -28,7 +28,7 @@ icon.expand.all = expand_all.png
icon.configure.filter = exec.png
icon.clear = erase16.png
icon.delete = icon.error
icon.delete = edit-delete.png
icon.error = emblem-important.png
icon.home = go-home.png
@@ -0,0 +1,484 @@
/* ###
* 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 java.awt.Component;
import java.io.File;
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.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
* {@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<>();
private Map<String, GIconUIResource> gIconMap = new HashMap<>();
// stores the original value for ids whose value has changed from the current theme
private GThemeValueMap changedValuesMap = new GThemeValueMap();
protected LookAndFeelManager lookAndFeelManager;
/**
* Initialized the Theme and its values for the application.
*/
public static void initialize() {
if (INSTANCE instanceof ApplicationThemeManager) {
Msg.error(ThemeManager.class, "Attempted to initialize theming more than once!");
return;
}
ApplicationThemeManager themeManager = new ApplicationThemeManager();
themeManager.doInitialize();
}
protected ApplicationThemeManager() {
// AppliationThemeManagers always replace any other instances
INSTANCE = this;
installInGui();
}
protected void doInitialize() {
installFlatLookAndFeels();
loadThemeDefaults();
setTheme(themePreferences.load());
}
@Override
public void reloadApplicationDefaults() {
loadThemeDefaults();
buildCurrentValues();
lookAndFeelManager.resetAll(javaDefaults);
notifyThemeChanged(new AllValuesChangedThemeEvent(false));
}
@Override
public void restoreThemeValues() {
buildCurrentValues();
lookAndFeelManager.resetAll(javaDefaults);
notifyThemeChanged(new AllValuesChangedThemeEvent(false));
}
@Override
public void restoreColor(String id) {
if (changedValuesMap.containsColor(id)) {
setColor(changedValuesMap.getColor(id));
}
}
@Override
public void restoreFont(String id) {
if (changedValuesMap.containsFont(id)) {
setFont(changedValuesMap.getFont(id));
}
}
@Override
public void restoreIcon(String id) {
if (changedValuesMap.containsIcon(id)) {
setIcon(changedValuesMap.getIcon(id));
}
}
@Override
public boolean isChangedColor(String id) {
return changedValuesMap.containsColor(id);
}
@Override
public boolean isChangedFont(String id) {
return changedValuesMap.containsFont(id);
}
@Override
public boolean isChangedIcon(String id) {
return changedValuesMap.containsIcon(id);
}
@Override
public void setTheme(GTheme theme) {
if (theme.hasSupportedLookAndFeel()) {
activeTheme = theme;
LafType lafType = theme.getLookAndFeelType();
lookAndFeelManager = lafType.getLookAndFeelManager(this);
try {
lookAndFeelManager.installLookAndFeel();
themePreferences.save(theme);
notifyThemeChanged(new AllValuesChangedThemeEvent(true));
}
catch (Exception e) {
Msg.error(this, "Error setting LookAndFeel: " + lafType.getName(), e);
}
}
currentValues.checkForUnresolvedReferences();
}
@Override
public void addTheme(GTheme newTheme) {
loadThemes();
allThemes.remove(newTheme);
allThemes.add(newTheme);
}
@Override
public void deleteTheme(GTheme theme) {
File file = theme.getFile();
if (file != null) {
file.delete();
}
if (allThemes != null) {
allThemes.remove(theme);
}
}
@Override
public Set<GTheme> getAllThemes() {
loadThemes();
return new HashSet<>(allThemes);
}
@Override
public Set<GTheme> getSupportedThemes() {
loadThemes();
Set<GTheme> supported = new HashSet<>();
for (GTheme theme : allThemes) {
if (theme.hasSupportedLookAndFeel()) {
supported.add(theme);
}
}
return supported;
}
@Override
public GTheme getActiveTheme() {
return activeTheme;
}
@Override
public LafType getLookAndFeelType() {
return activeTheme.getLookAndFeelType();
}
@Override
public GTheme getTheme(String themeName) {
Optional<GTheme> first =
getAllThemes().stream().filter(t -> t.getName().equals(themeName)).findFirst();
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());
if (newValue.equals(currentValue)) {
return;
}
updateChangedValuesMap(currentValue, newValue);
currentValues.addFont(newValue);
notifyThemeChanged(new FontChangedThemeEvent(currentValues, newValue));
// update all java LookAndFeel fonts affected by this changed
String id = newValue.getId();
Set<String> changedFontIds = findChangedJavaFontIds(id);
lookAndFeelManager.fontsChanged(changedFontIds);
}
@Override
public void setColor(ColorValue newValue) {
ColorValue currentValue = currentValues.getColor(newValue.getId());
if (newValue.equals(currentValue)) {
return;
}
updateChangedValuesMap(currentValue, newValue);
currentValues.addColor(newValue);
notifyThemeChanged(new ColorChangedThemeEvent(currentValues, newValue));
// now update the ui
if (lookAndFeelManager != null) {
lookAndFeelManager.colorsChanged();
}
}
@Override
public void setIcon(IconValue newValue) {
IconValue currentValue = currentValues.getIcon(newValue.getId());
if (newValue.equals(currentValue)) {
return;
}
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();
Set<String> changedIconIds = findChangedJavaIconIds(id);
Icon newIcon = newValue.get(currentValues);
lookAndFeelManager.iconsChanged(changedIconIds, newIcon);
}
@Override
public GColorUIResource getGColorUiResource(String id) {
GColorUIResource gColor = gColorMap.get(id);
if (gColor == null) {
gColor = new GColorUIResource(id);
gColorMap.put(id, gColor);
}
return gColor;
}
@Override
public GIconUIResource getGIconUiResource(String id) {
GIconUIResource gIcon = gIconMap.get(id);
if (gIcon == null) {
gIcon = new GIconUIResource(id);
gIconMap.put(id, gIcon);
}
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) {
systemValues = map;
}
/**
* 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}
*/
public void setJavaDefaults(GThemeValueMap map) {
javaDefaults = map;
buildCurrentValues();
GColor.refreshAll(currentValues);
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();
}
@Override
public void registerFont(Component component, String fontId) {
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;
changedValuesMap.clear();
}
private void loadThemes() {
if (allThemes == null) {
Set<GTheme> set = new HashSet<>();
set.addAll(findDiscoverableThemes());
set.addAll(themeFileLoader.loadThemeFiles());
allThemes = set;
}
}
private Collection<DiscoverableGTheme> findDiscoverableThemes() {
return ClassSearcher.getInstances(DiscoverableGTheme.class);
}
private void updateChangedValuesMap(ColorValue currentValue, ColorValue newValue) {
String id = newValue.getId();
ColorValue originalValue = changedValuesMap.getColor(id);
// if new value is original value, it is no longer changed, remove it from changed map
if (newValue.equals(originalValue)) {
changedValuesMap.removeColor(id);
}
else if (originalValue == null) {
// first time changed, so current value is original value
changedValuesMap.addColor(currentValue);
}
}
private void updateChangedValuesMap(FontValue currentValue, FontValue newValue) {
String id = newValue.getId();
FontValue originalValue = changedValuesMap.getFont(id);
// if new value is original value, it is no longer changed, remove it from changed map
if (newValue.equals(originalValue)) {
changedValuesMap.removeFont(id);
}
else if (originalValue == null) {
// first time changed, so current value is original value
changedValuesMap.addFont(currentValue);
}
}
private void updateChangedValuesMap(IconValue currentValue, IconValue newValue) {
String id = newValue.getId();
IconValue originalValue = changedValuesMap.getIcon(id);
// if new value is original value, it is no longer changed, remove it from changed map
if (newValue.equals(originalValue)) {
changedValuesMap.removeIcon(id);
}
else if (originalValue == null) {
// first time changed, so current value is original value
changedValuesMap.addIcon(currentValue);
}
}
private Set<String> findChangedJavaFontIds(String id) {
Set<String> affectedIds = new HashSet<>();
List<FontValue> 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 Set<String> findChangedJavaIconIds(String id) {
Set<String> affectedIds = new HashSet<>();
List<IconValue> icons = javaDefaults.getIcons();
for (IconValue iconValue : icons) {
String iconId = iconValue.getId();
if (iconId.equals(id) || iconValue.inheritsFrom(id, currentValues)) {
affectedIds.add(iconId);
}
}
return affectedIds;
}
public void refreshGThemeValues() {
GColor.refreshAll(currentValues);
GIcon.refreshAll(currentValues);
}
}
@@ -150,8 +150,8 @@ public class ColorValue extends ThemeValue<Color> {
}
@Override
public void installValue() {
Gui.setColor(this);
public void installValue(ThemeManager themeManager) {
themeManager.setColor(this);
}
}
@@ -232,8 +232,8 @@ public class FontValue extends ThemeValue<Font> {
}
@Override
public void installValue() {
Gui.setFont(this);
public void installValue(ThemeManager themeManager) {
themeManager.setFont(this);
}
}
@@ -50,20 +50,9 @@ public class GColor extends Color {
* @param id the id used to lookup the current value for this color
*/
public GColor(String id) {
this(id, true);
}
/**
* Construct a GColor with an id that will be used to look up the current color associated with
* that id, which can be changed at runtime.
* @param id the id used to lookup the current value for this color
* @param validate if true, an error will be generated if the id can't be resolved to a color
* at this time
*/
public GColor(String id, boolean validate) {
super(0x808080);
this.id = id;
delegate = Gui.getColor(id, validate);
delegate = Gui.getColor(id);
inUseColors.add(this);
}
@@ -230,9 +219,11 @@ public class GColor extends Color {
/**
* Reloads the delegate.
* @param currentValues the map of current theme values
*/
public void refresh() {
Color color = Gui.getColor(id, false);
public void refresh(GThemeValueMap currentValues) {
ColorValue value = currentValues.getColor(id);
Color color = value == null ? null : value.get(currentValues);
if (color != null) {
if (alpha != null) {
delegate = new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
@@ -246,10 +237,11 @@ public class GColor extends Color {
/**
* Static method for notifying all the existing GColors that colors have changed and they
* should reload their cached indirect color.
* @param currentValues the map of current theme values
*/
public static void refreshAll() {
public static void refreshAll(GThemeValueMap currentValues) {
for (GColor gcolor : inUseColors.getValues()) {
gcolor.refresh();
gcolor.refresh(currentValues);
}
}
@@ -46,10 +46,11 @@ public class GIcon implements Icon {
/**
* Static method for notifying all the existing GIcon that icons have changed and they
* should reload their cached indirect icon.
* @param currentValues the map of all current theme values
*/
public static void refreshAll() {
public static void refreshAll(GThemeValueMap currentValues) {
for (GIcon gIcon : inUseIcons.getValues()) {
gIcon.refresh();
gIcon.refresh(currentValues);
}
}
@@ -59,19 +60,8 @@ public class GIcon implements Icon {
* @param id the id used to lookup the current value for this color
*/
public GIcon(String id) {
this(id, true);
}
/**
* Construct a GIcon with an id that will be used to look up the current icon associated with
* that id, which can be changed at runtime.
* @param id the id used to lookup the current value for this icon
* @param validate if true, an error will be generated if the id can't be resolved to a icon
* at this time
*/
public GIcon(String id, boolean validate) {
this.id = id;
delegate = Gui.getIcon(id, validate);
delegate = Gui.getIcon(id);
inUseIcons.add(this);
}
@@ -134,14 +124,25 @@ public class GIcon implements Icon {
/**
* Reloads the delegate.
* @param currentValues the map of current theme values
*/
public void refresh() {
Icon icon = Gui.getIcon(id, false);
public void refresh(GThemeValueMap currentValues) {
IconValue value = currentValues.getIcon(id);
Icon icon = value == null ? null : value.get(currentValues);
if (icon != null) {
delegate = icon;
}
}
/**
* Returns the current delegate for this GIcon. Note that this delegate can change when the
* theme changes or is edited.
* @return the current delegate icon for this GIcon.
*/
public Icon getDelegate() {
return delegate;
}
@Override
public int hashCode() {
return id.hashCode();
@@ -217,43 +217,6 @@ public class GTheme extends GThemeValueMap {
writer.writeThemeToFile(file);
}
/**
* Saves this theme to a new theme file.
* @param outputFile the file to save to
* @param includeDefaults if true, write all values to the theme file including default values.
* Otherwise, just values that are not the default values are written to the file.
* @return a new FileGTheme that represents the new file/theme
* @throws IOException if an I/O error occurs writing the theme file
*/
public GTheme saveToFile(File outputFile, boolean includeDefaults) throws IOException {
GTheme fileTheme = new GTheme(outputFile, name, lookAndFeel, useDarkDefaults);
if (includeDefaults) {
fileTheme.load(Gui.getDefaults());
}
fileTheme.load(this);
fileTheme.save();
return fileTheme;
}
/**
* Saves this theme to a new theme file.
* @param outputFile the file to save to
* @param includeDefaults if true, write all values to the theme file including default values.
* Otherwise, just values that are not the default values are written to the file.
* @throws IOException if an I/O error occurs writing the theme file
*/
public void saveToZip(File outputFile, boolean includeDefaults) throws IOException {
GTheme theme = new GTheme(name, lookAndFeel, useDarkDefaults);
if (includeDefaults) {
theme.load(Gui.getDefaults());
}
theme.load(this);
ThemeWriter writer = new ThemeWriter(theme);
writer.writeThemeToZipFile(outputFile);
}
/**
* Reads a theme from a file. The file can be either a theme file or a zip file containing
* a theme file and optionally a set of icon files.
File diff suppressed because it is too large Load Diff
@@ -247,8 +247,8 @@ public class IconValue extends ThemeValue<Icon> {
}
@Override
public void installValue() {
Gui.setIcon(this);
public void installValue(ThemeManager themeManager) {
themeManager.setIcon(this);
}
}

Some files were not shown because too many files have changed in this diff Show More