mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-01-21 00:40:10 +00:00
GP-1981 added import/export, theme listener, ThemePlugin tests, renamed
themes, deleteTheme action, zip support and got Nimbus working
This commit is contained in:
parent
b4d2271474
commit
25f7df2aa7
@ -24,8 +24,7 @@ import javax.help.HelpSetException;
|
||||
|
||||
import docking.help.*;
|
||||
import generic.jar.ResourceFile;
|
||||
import generic.theme.Gui;
|
||||
import generic.theme.ThemeListener;
|
||||
import generic.theme.*;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.Msg;
|
||||
import help.HelpService;
|
||||
@ -38,7 +37,7 @@ import resources.ResourceManager;
|
||||
public class GhidraHelpService extends HelpManager {
|
||||
|
||||
private static final String MASTER_HELP_SET_HS = "Base_HelpSet.hs";
|
||||
private ThemeListener listener = t -> reload();
|
||||
private ThemeListener listener = new HelpThemeListener();
|
||||
|
||||
public static void install() {
|
||||
try {
|
||||
@ -120,4 +119,11 @@ public class GhidraHelpService extends HelpManager {
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
class HelpThemeListener implements ThemeListener {
|
||||
@Override
|
||||
public void themeChanged(GTheme newTheme) {
|
||||
reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ package ghidra.formats.gfilesystem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import docking.widgets.SelectFromListDialog;
|
||||
import ghidra.formats.gfilesystem.factory.FileSystemInfoRec;
|
||||
|
||||
/**
|
||||
|
@ -16,13 +16,12 @@
|
||||
package ghidra.plugins.fsbrowser;
|
||||
|
||||
import static ghidra.formats.gfilesystem.fileinfo.FileAttributeType.*;
|
||||
import static java.util.Map.entry;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import static java.util.Map.*;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
@ -31,6 +30,7 @@ import org.apache.commons.io.FilenameUtils;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.SelectFromListDialog;
|
||||
import docking.widgets.dialogs.MultiLineMessageDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
|
@ -18,8 +18,8 @@ package ghidra.plugins.fsbrowser;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import docking.widgets.SelectFromListDialog;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.formats.gfilesystem.SelectFromListDialog;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
@ -28,7 +28,6 @@ import ghidra.util.Msg;
|
||||
*/
|
||||
public class FSBUtils {
|
||||
|
||||
|
||||
/**
|
||||
* Returns the {@link ProgramManager} associated with this fs browser plugin.
|
||||
* <p>
|
||||
|
@ -88,6 +88,8 @@ src/main/resources/images/left.png||GHIDRA||reviewed||END|
|
||||
src/main/resources/images/locationIn.gif||GHIDRA||||END|
|
||||
src/main/resources/images/locationOut.gif||GHIDRA||||END|
|
||||
src/main/resources/images/magnifier.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
|
||||
src/main/resources/images/mail-folder-outbox.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
||||
src/main/resources/images/mail-receive.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
||||
src/main/resources/images/media-playback-start.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
||||
src/main/resources/images/menu16.gif||GHIDRA||reviewed||END|
|
||||
src/main/resources/images/note-red.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
|
||||
|
@ -80,6 +80,8 @@ icon.flag = images/flag.png
|
||||
icon.lock = images/kgpg.png
|
||||
icon.checkmark.green = images/checkmark_green.gif
|
||||
|
||||
icon.theme.import = images/mail-receive.png
|
||||
icon.theme.export = images/mail-folder-outbox.png
|
||||
|
||||
[Dark Defaults]
|
||||
|
||||
|
@ -33,6 +33,7 @@ import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import docking.widgets.label.GDLabel;
|
||||
import docking.widgets.list.GListCellRenderer;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.filechooser.ExtensionFileFilter;
|
||||
import resources.ResourceManager;
|
||||
@ -71,11 +72,13 @@ public class IconPropertyEditor extends PropertyEditorSupport {
|
||||
if (icon instanceof UrlImageIcon urlIcon) {
|
||||
return urlIcon.getOriginalPath();
|
||||
}
|
||||
return "<Default>";
|
||||
return "<Original>";
|
||||
}
|
||||
|
||||
class IconChooserPanel extends JPanel {
|
||||
|
||||
private static final String IMAGE_DIR = "images/";
|
||||
private static final String LAST_ICON_DIR_PREFERENCE_KEY = "IconEditor.lastDir";
|
||||
private GDLabel previewLabel;
|
||||
private DropDownSelectionTextField<Icon> dropDown;
|
||||
private IconDropDownDataModel dataModel;
|
||||
@ -151,8 +154,14 @@ public class IconPropertyEditor extends PropertyEditorSupport {
|
||||
chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
|
||||
chooser.setSelectedFileFilter(
|
||||
ExtensionFileFilter.forExtensions("Icon Files", ".png", "gif"));
|
||||
String lastDir = Preferences.getProperty(LAST_ICON_DIR_PREFERENCE_KEY);
|
||||
if (lastDir != null) {
|
||||
chooser.setCurrentDirectory(new File(lastDir));
|
||||
}
|
||||
File file = chooser.getSelectedFile();
|
||||
if (file != null) {
|
||||
File dir = chooser.getCurrentDirectory();
|
||||
Preferences.setProperty(LAST_ICON_DIR_PREFERENCE_KEY, dir.getAbsolutePath());
|
||||
importIconFile(file);
|
||||
}
|
||||
}
|
||||
@ -164,8 +173,9 @@ public class IconPropertyEditor extends PropertyEditorSupport {
|
||||
return;
|
||||
}
|
||||
File dir = Application.getUserSettingsDirectory();
|
||||
File destinationDir = new File(dir, "themes/images");
|
||||
File destinationFile = new File(destinationDir, file.getName());
|
||||
String relativePath = IMAGE_DIR + file.getName();
|
||||
|
||||
File destinationFile = new File(dir, relativePath);
|
||||
if (destinationFile.exists()) {
|
||||
int result = OptionDialog.showYesNoDialog(dropDown, "Overwrite?",
|
||||
"An icon with that name already exists.\n Do you want to overwrite it?");
|
||||
@ -175,7 +185,8 @@ public class IconPropertyEditor extends PropertyEditorSupport {
|
||||
}
|
||||
try {
|
||||
FileUtils.copyFile(file, destinationFile);
|
||||
ImageIcon icon = ResourceManager.loadImage("themes/images/" + file.getName());
|
||||
String path = ResourceManager.EXTERNAL_ICON_PREFIX + relativePath;
|
||||
ImageIcon icon = ResourceManager.loadImage(path);
|
||||
setValue(icon);
|
||||
}
|
||||
catch (IOException e) {
|
||||
|
@ -24,7 +24,6 @@ import javax.swing.*;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.options.editor.ButtonPanelFactory;
|
||||
import docking.theme.*;
|
||||
import docking.widgets.checkbox.GCheckBox;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
@ -40,10 +39,11 @@ public class ExportThemeDialog extends DialogComponentProvider {
|
||||
private JTextField nameField;
|
||||
private JTextField fileTextField;
|
||||
private GCheckBox includeDefaultsCheckbox;
|
||||
private boolean exportAsZip;
|
||||
|
||||
protected ExportThemeDialog() {
|
||||
public ExportThemeDialog(boolean exportAsZip) {
|
||||
super("Export Theme");
|
||||
|
||||
this.exportAsZip = exportAsZip;
|
||||
addWorkPanel(buildMainPanel());
|
||||
addOKButton();
|
||||
addCancelButton();
|
||||
@ -58,33 +58,45 @@ public class ExportThemeDialog extends DialogComponentProvider {
|
||||
}
|
||||
|
||||
private boolean exportTheme() {
|
||||
File file = new File(fileTextField.getText());
|
||||
String themeName = nameField.getText();
|
||||
if (themeName.isBlank()) {
|
||||
setStatusText("Missing Theme Name", MessageType.ERROR, true);
|
||||
return false;
|
||||
}
|
||||
boolean includeDefaults = includeDefaultsCheckbox.isSelected();
|
||||
|
||||
GTheme activeTheme = Gui.getActiveTheme();
|
||||
FileGTheme fileTheme = new FileGTheme(file, themeName, activeTheme.getLookAndFeelType(),
|
||||
activeTheme.useDarkDefaults());
|
||||
|
||||
if (includeDefaults) {
|
||||
fileTheme.load(Gui.getAllValues());
|
||||
}
|
||||
else {
|
||||
fileTheme.load(Gui.getNonDefaultValues());
|
||||
}
|
||||
|
||||
try {
|
||||
fileTheme.save();
|
||||
FileGTheme exportTheme = createExternalTheme(themeName);
|
||||
loadValues(exportTheme);
|
||||
exportTheme.save();
|
||||
return true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error("Error Exporting Theme", "I/O Error encountered trying to export theme!", e);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void loadValues(FileGTheme exportTheme) {
|
||||
if (includeDefaultsCheckbox.isSelected()) {
|
||||
exportTheme.load(Gui.getAllValues());
|
||||
}
|
||||
else {
|
||||
exportTheme.load(Gui.getNonDefaultValues());
|
||||
}
|
||||
}
|
||||
|
||||
private FileGTheme createExternalTheme(String themeName) {
|
||||
File file = new File(fileTextField.getText());
|
||||
|
||||
GTheme activeTheme = Gui.getActiveTheme();
|
||||
LafType laf = activeTheme.getLookAndFeelType();
|
||||
boolean useDarkDefaults = activeTheme.useDarkDefaults();
|
||||
|
||||
if (exportAsZip) {
|
||||
return new ZipGTheme(file, themeName, laf, useDarkDefaults);
|
||||
}
|
||||
return new FileGTheme(file, themeName, laf, useDarkDefaults);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -118,10 +130,12 @@ public class ExportThemeDialog extends DialogComponentProvider {
|
||||
}
|
||||
|
||||
private Component buildFilePanel() {
|
||||
String name = Gui.getActiveTheme().getName();
|
||||
String fileName = name.replaceAll(" ", "_") + GTheme.FILE_EXTENSION;
|
||||
File homeDir = new File(System.getProperty("user.home")); // prefer the home directory
|
||||
File file = new File(homeDir, fileName);
|
||||
|
||||
String name = Gui.getActiveTheme().getName();
|
||||
String filename = name.replaceAll(" ", "_") + ".";
|
||||
filename += exportAsZip ? GTheme.ZIP_FILE_EXTENSION : GTheme.FILE_EXTENSION;
|
||||
File file = new File(homeDir, filename);
|
||||
|
||||
fileTextField = new JTextField();
|
||||
fileTextField.setText(file.getAbsolutePath());
|
||||
@ -149,4 +163,9 @@ public class ExportThemeDialog extends DialogComponentProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// used for testing
|
||||
public void setOutputFile(File outputFile) {
|
||||
fileTextField.setText(outputFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -47,11 +47,11 @@ public class ProtectedIcon implements Icon {
|
||||
|
||||
@Override
|
||||
public int getIconWidth() {
|
||||
return delegate.getIconWidth();
|
||||
return Math.max(1, delegate.getIconWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIconHeight() {
|
||||
return delegate.getIconHeight();
|
||||
return Math.max(1, delegate.getIconHeight());
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,6 @@ import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.event.*;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -31,18 +29,13 @@ import docking.DialogComponentProvider;
|
||||
import docking.DockingWindowManager;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.theme.*;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.combobox.GhidraComboBox;
|
||||
import docking.widgets.dialogs.InputDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import docking.widgets.table.GFilterTable;
|
||||
import docking.widgets.table.GTable;
|
||||
import generic.theme.*;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.filechooser.ExtensionFileFilter;
|
||||
import ghidra.util.MessageType;
|
||||
import ghidra.util.Swing;
|
||||
|
||||
public class ThemeDialog extends DialogComponentProvider {
|
||||
private static ThemeDialog INSTANCE;
|
||||
@ -54,12 +47,11 @@ public class ThemeDialog extends DialogComponentProvider {
|
||||
private FontValueEditor fontEditor = new FontValueEditor(this::fontValueChanged);
|
||||
private IconValueEditor iconEditor = new IconValueEditor(this::iconValueChanged);
|
||||
|
||||
// stores the original value for ids whose value has changed
|
||||
private GThemeValueMap changedValuesMap = new GThemeValueMap();
|
||||
private JButton saveButton;
|
||||
private JButton restoreButton;
|
||||
private GhidraComboBox<String> combo;
|
||||
private ItemListener comboListener = this::themeComboChanged;
|
||||
private ThemeListener listener = new DialogThemeListener();
|
||||
|
||||
public ThemeDialog() {
|
||||
super("Theme Dialog", false);
|
||||
@ -73,21 +65,10 @@ public class ThemeDialog extends DialogComponentProvider {
|
||||
setRememberSize(false);
|
||||
updateButtons();
|
||||
createActions();
|
||||
Gui.addThemeListener(listener);
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
DockingAction importAction =
|
||||
new ActionBuilder("Import Theme", getTitle()).toolBarIcon(new GIcon("icon.navigate.in"))
|
||||
.onAction(e -> importTheme())
|
||||
.build();
|
||||
addAction(importAction);
|
||||
|
||||
DockingAction exportAction = new ActionBuilder("Export Theme", getTitle())
|
||||
.toolBarIcon(new GIcon("icon.navigate.out"))
|
||||
.onAction(e -> exportTheme())
|
||||
.build();
|
||||
addAction(exportAction);
|
||||
|
||||
DockingAction reloadDefaultsAction = new ActionBuilder("Reload Ghidra Defaults", getTitle())
|
||||
.toolBarIcon(new GIcon("icon.refresh"))
|
||||
.onAction(e -> reloadDefaultsCallback())
|
||||
@ -104,14 +85,14 @@ public class ThemeDialog extends DialogComponentProvider {
|
||||
}
|
||||
|
||||
private boolean handleChanges() {
|
||||
if (hasChanges()) {
|
||||
if (Gui.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 save();
|
||||
return ThemeUtils.saveThemeChanges();
|
||||
}
|
||||
Gui.reloadGhidraDefaults();
|
||||
}
|
||||
@ -119,12 +100,11 @@ public class ThemeDialog extends DialogComponentProvider {
|
||||
}
|
||||
|
||||
protected void saveCallback() {
|
||||
save();
|
||||
reset();
|
||||
ThemeUtils.saveThemeChanges();
|
||||
}
|
||||
|
||||
private void restoreCallback() {
|
||||
if (hasChanges()) {
|
||||
if (Gui.hasThemeChanges()) {
|
||||
int result = OptionDialog.showYesNoDialog(null, "Restore Theme Values",
|
||||
"Are you sure you want to discard all your changes?");
|
||||
if (result == OptionDialog.NO_OPTION) {
|
||||
@ -132,11 +112,10 @@ public class ThemeDialog extends DialogComponentProvider {
|
||||
}
|
||||
}
|
||||
Gui.restoreThemeValues();
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reloadDefaultsCallback() {
|
||||
if (hasChanges()) {
|
||||
if (Gui.hasThemeChanges()) {
|
||||
int result = OptionDialog.showYesNoDialog(null, "Reload Ghidra Default Values",
|
||||
"This will discard all your theme changes. Continue?");
|
||||
if (result == OptionDialog.NO_OPTION) {
|
||||
@ -144,11 +123,9 @@ public class ThemeDialog extends DialogComponentProvider {
|
||||
}
|
||||
}
|
||||
Gui.reloadGhidraDefaults();
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
changedValuesMap.clear();
|
||||
colorTableModel.reloadAll();
|
||||
fontTableModel.reloadAll();
|
||||
iconTableModel.reloadAll();
|
||||
@ -156,141 +133,33 @@ public class ThemeDialog extends DialogComponentProvider {
|
||||
updateCombo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all current theme changes
|
||||
* @return true if the operation was not cancelled.
|
||||
*/
|
||||
private boolean save() {
|
||||
GTheme activeTheme = Gui.getActiveTheme();
|
||||
|
||||
String name = activeTheme.getName();
|
||||
|
||||
while (!canSaveToName(name)) {
|
||||
name = getNameFromUser(name);
|
||||
if (name == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return saveCurrentValues(name);
|
||||
}
|
||||
|
||||
private String getNameFromUser(String name) {
|
||||
InputDialog inputDialog = new InputDialog("Create Theme", "New Theme Name", name);
|
||||
DockingWindowManager.showDialog(inputDialog);
|
||||
return inputDialog.getValue();
|
||||
}
|
||||
|
||||
private boolean canSaveToName(String name) {
|
||||
GTheme existing = Gui.getTheme(name);
|
||||
if (existing == null) {
|
||||
return true;
|
||||
}
|
||||
if (existing instanceof FileGTheme fileTheme) {
|
||||
int result = OptionDialog.showYesNoDialog(null, "Overwrite Existing Theme?",
|
||||
"Do you want to overwrite the existing theme file for \"" + name + "\"?");
|
||||
if (result == OptionDialog.YES_OPTION) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean saveCurrentValues(String themeName) {
|
||||
GTheme activeTheme = Gui.getActiveTheme();
|
||||
File file = getSaveFile(themeName);
|
||||
|
||||
FileGTheme newTheme = new FileGTheme(file, themeName, activeTheme.getLookAndFeelType(),
|
||||
activeTheme.useDarkDefaults());
|
||||
newTheme.load(Gui.getNonDefaultValues());
|
||||
try {
|
||||
newTheme.save();
|
||||
Gui.addTheme(newTheme);
|
||||
Gui.setTheme(newTheme);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, null, "I/O Error",
|
||||
"Error writing theme file: " + newTheme.getFile().getAbsolutePath(), e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private File getSaveFile(String themeName) {
|
||||
File dir = Application.getUserSettingsDirectory();
|
||||
File themeDir = new File(dir, Gui.THEME_DIR);
|
||||
if (!themeDir.exists()) {
|
||||
themeDir.mkdir();
|
||||
}
|
||||
String cleanedName = themeName.replaceAll(" ", "_") + GTheme.FILE_EXTENSION;
|
||||
return new File(themeDir, cleanedName);
|
||||
}
|
||||
|
||||
private void importTheme() {
|
||||
if (!handleChanges()) {
|
||||
return;
|
||||
}
|
||||
GTheme startingTheme = Gui.getActiveTheme();
|
||||
GhidraFileChooser chooser = new GhidraFileChooser(getComponent());
|
||||
chooser.setTitle("Choose Theme File");
|
||||
chooser.setApproveButtonToolTipText("Select File");
|
||||
chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
|
||||
chooser.setSelectedFileFilter(
|
||||
new ExtensionFileFilter("Ghidra Theme Files", GTheme.FILE_EXTENSION));
|
||||
File file = chooser.getSelectedFile();
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FileGTheme imported = new FileGTheme(file);
|
||||
Gui.setTheme(imported);
|
||||
if (!save()) {
|
||||
Gui.setTheme(startingTheme);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, null, "Error Importing Theme File",
|
||||
"Error encountered importing file: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
reset();
|
||||
}
|
||||
|
||||
private void exportTheme() {
|
||||
ExportThemeDialog dialog = new ExportThemeDialog();
|
||||
DockingWindowManager.showDialog(dialog);
|
||||
}
|
||||
|
||||
private void themeComboChanged(ItemEvent e) {
|
||||
if (e.getStateChange() == ItemEvent.SELECTED) {
|
||||
String themeName = (String) e.getItem();
|
||||
|
||||
Swing.runLater(() -> {
|
||||
GTheme theme = Gui.getTheme(themeName);
|
||||
Gui.setTheme(theme);
|
||||
if (theme.getLookAndFeelType() == LafType.GTK) {
|
||||
setStatusText(
|
||||
"Warning - Themes using the GTK LookAndFeel do not support changing java component colors, fonts or icons. You can still change Ghidra values.",
|
||||
MessageType.ERROR, true);
|
||||
}
|
||||
else if (theme.getLookAndFeelType() == LafType.NIMBUS) {
|
||||
setStatusText(
|
||||
"Warning - Themes using the Nimbus LookAndFeel do not support changing java component fonts or icons. You can still change Ghidra values.",
|
||||
MessageType.ERROR, true);
|
||||
}
|
||||
else {
|
||||
setStatusText("");
|
||||
}
|
||||
changedValuesMap.clear();
|
||||
colorTableModel.reloadAll();
|
||||
fontTableModel.reloadAll();
|
||||
iconTableModel.reloadAll();
|
||||
});
|
||||
if (e.getStateChange() != ItemEvent.SELECTED) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasChanges() {
|
||||
return !changedValuesMap.isEmpty();
|
||||
if (!ThemeUtils.askToSaveThemeChanges()) {
|
||||
Swing.runLater(() -> updateCombo());
|
||||
return;
|
||||
}
|
||||
String themeName = (String) e.getItem();
|
||||
|
||||
Swing.runLater(() -> {
|
||||
GTheme theme = Gui.getTheme(themeName);
|
||||
Gui.setTheme(theme);
|
||||
if (theme.getLookAndFeelType() == LafType.GTK) {
|
||||
setStatusText(
|
||||
"Warning - Themes using the GTK LookAndFeel do not support changing java component colors, fonts or icons.",
|
||||
MessageType.ERROR);
|
||||
}
|
||||
else {
|
||||
setStatusText("");
|
||||
}
|
||||
colorTableModel.reloadAll();
|
||||
fontTableModel.reloadAll();
|
||||
iconTableModel.reloadAll();
|
||||
});
|
||||
}
|
||||
|
||||
protected void editColor(ColorValue value) {
|
||||
@ -306,76 +175,31 @@ public class ThemeDialog extends DialogComponentProvider {
|
||||
}
|
||||
|
||||
void colorValueChanged(PropertyChangeEvent event) {
|
||||
ColorValue oldValue = (ColorValue) event.getOldValue();
|
||||
ColorValue newValue = (ColorValue) event.getNewValue();
|
||||
updateChangedValueMap(oldValue, newValue);
|
||||
// run later - don't rock the boat in the middle of a listener callback
|
||||
Swing.runLater(() -> {
|
||||
ColorValue newValue = (ColorValue) event.getNewValue();
|
||||
Gui.setColor(newValue);
|
||||
colorTableModel.reloadCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
void fontValueChanged(PropertyChangeEvent event) {
|
||||
FontValue oldValue = (FontValue) event.getOldValue();
|
||||
FontValue newValue = (FontValue) event.getNewValue();
|
||||
updateChangedValueMap(oldValue, newValue);
|
||||
// run later - don't rock the boat in the middle of a listener callback
|
||||
Swing.runLater(() -> {
|
||||
FontValue newValue = (FontValue) event.getNewValue();
|
||||
Gui.setFont(newValue);
|
||||
fontTableModel.reloadCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
void iconValueChanged(PropertyChangeEvent event) {
|
||||
IconValue oldValue = (IconValue) event.getOldValue();
|
||||
IconValue newValue = (IconValue) event.getNewValue();
|
||||
updateChangedValueMap(oldValue, newValue);
|
||||
// run later - don't rock the boat in the middle of a listener callback
|
||||
Swing.runLater(() -> {
|
||||
IconValue newValue = (IconValue) event.getNewValue();
|
||||
Gui.setIcon(newValue);
|
||||
iconTableModel.reloadCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
private void updateChangedValueMap(ColorValue oldValue, ColorValue newValue) {
|
||||
ColorValue originalValue = changedValuesMap.getColor(oldValue.getId());
|
||||
if (originalValue == null) {
|
||||
changedValuesMap.addColor(oldValue);
|
||||
}
|
||||
else if (originalValue.equals(newValue)) {
|
||||
// if restoring the original color, remove it from the map of changes
|
||||
changedValuesMap.removeColor(oldValue.getId());
|
||||
}
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
private void updateChangedValueMap(FontValue oldValue, FontValue newValue) {
|
||||
FontValue originalValue = changedValuesMap.getFont(oldValue.getId());
|
||||
if (originalValue == null) {
|
||||
changedValuesMap.addFont(oldValue);
|
||||
}
|
||||
else if (originalValue.equals(newValue)) {
|
||||
// if restoring the original color, remove it from the map of changes
|
||||
changedValuesMap.removeFont(oldValue.getId());
|
||||
}
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
private void updateChangedValueMap(IconValue oldValue, IconValue newValue) {
|
||||
IconValue originalValue = changedValuesMap.getIcon(oldValue.getId());
|
||||
if (originalValue == null) {
|
||||
changedValuesMap.addIcon(oldValue);
|
||||
}
|
||||
else if (originalValue.equals(newValue)) {
|
||||
// if restoring the original color, remove it from the map of changes
|
||||
changedValuesMap.removeFont(oldValue.getId());
|
||||
}
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
private void updateButtons() {
|
||||
boolean hasChanges = hasChanges();
|
||||
boolean hasChanges = Gui.hasThemeChanges();
|
||||
saveButton.setEnabled(hasChanges);
|
||||
restoreButton.setEnabled(hasChanges);
|
||||
}
|
||||
@ -577,4 +401,40 @@ public class ThemeDialog extends DialogComponentProvider {
|
||||
DockingWindowManager.showDialog(INSTANCE);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
Gui.removeThemeListener(listener);
|
||||
super.close();
|
||||
}
|
||||
|
||||
class DialogThemeListener implements ThemeListener {
|
||||
@Override
|
||||
public void themeChanged(GTheme newTheme) {
|
||||
reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void themeValuesRestored() {
|
||||
reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fontChanged(String id) {
|
||||
fontTableModel.reloadCurrent();
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void colorChanged(String id) {
|
||||
colorTableModel.reloadCurrent();
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void iconChanged(String id) {
|
||||
iconTableModel.reloadCurrent();
|
||||
updateButtons();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,13 +23,11 @@ import java.util.function.Supplier;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import docking.theme.*;
|
||||
import docking.widgets.table.*;
|
||||
import generic.theme.*;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.framework.plugintool.ServiceProviderStub;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.table.column.AbstractGColumnRenderer;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
import resources.icons.*;
|
||||
@ -46,7 +44,6 @@ public class ThemeIconTableModel extends GDynamicColumnTableModel<IconValue, Obj
|
||||
}
|
||||
|
||||
private void load() {
|
||||
Msg.debug(this, "loading");
|
||||
currentValues = Gui.getAllValues();
|
||||
icons = currentValues.getIcons();
|
||||
themeValues = new GThemeValueMap(currentValues);
|
||||
@ -189,7 +186,8 @@ public class ThemeIconTableModel extends GDynamicColumnTableModel<IconValue, Obj
|
||||
}
|
||||
Icon icon = resolvedIcon.icon();
|
||||
String sizeString = "[" + icon.getIconWidth() + "x" + icon.getIconHeight() + "] ";
|
||||
String iconString = "<Internal>";
|
||||
|
||||
String iconString = FileGTheme.JAVA_ICON;
|
||||
if (icon instanceof UrlImageIcon urlIcon) {
|
||||
iconString = urlIcon.getOriginalPath();
|
||||
}
|
||||
@ -202,7 +200,7 @@ public class ThemeIconTableModel extends GDynamicColumnTableModel<IconValue, Obj
|
||||
if (resolvedIcon.refId() != null) {
|
||||
iconString = resolvedIcon.refId() + " [" + iconString + "]";
|
||||
}
|
||||
return sizeString + iconString;
|
||||
return String.format("%-8s%s", sizeString, iconString);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,222 @@
|
||||
/* ###
|
||||
* 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 docking.theme.gui;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import docking.DockingWindowManager;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.SelectFromListDialog;
|
||||
import docking.widgets.dialogs.InputDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import docking.widgets.filechooser.GhidraFileChooserMode;
|
||||
import generic.theme.*;
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.filechooser.ExtensionFileFilter;
|
||||
|
||||
public class ThemeUtils {
|
||||
|
||||
public static boolean askToSaveThemeChanges() {
|
||||
if (Gui.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();
|
||||
}
|
||||
Gui.reloadGhidraDefaults();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves all current theme changes
|
||||
* @return true if the operation was not cancelled.
|
||||
*/
|
||||
public static boolean saveThemeChanges() {
|
||||
GTheme activeTheme = Gui.getActiveTheme();
|
||||
String name = activeTheme.getName();
|
||||
|
||||
while (!canSaveToName(name)) {
|
||||
name = getNameFromUser(name);
|
||||
if (name == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return saveCurrentValues(name);
|
||||
}
|
||||
|
||||
public static void resetThemeToDefault() {
|
||||
if (askToSaveThemeChanges()) {
|
||||
Gui.setTheme(Gui.getDefaultTheme());
|
||||
}
|
||||
}
|
||||
|
||||
public static void importTheme() {
|
||||
GhidraFileChooser chooser = new GhidraFileChooser(null);
|
||||
chooser.setTitle("Choose Theme File");
|
||||
chooser.setApproveButtonToolTipText("Select File");
|
||||
chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
|
||||
chooser.setFileFilter(ExtensionFileFilter.forExtensions("Ghidra Theme Files",
|
||||
GTheme.FILE_EXTENSION, GTheme.ZIP_FILE_EXTENSION));
|
||||
|
||||
File file = chooser.getSelectedFile();
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
importTheme(file);
|
||||
}
|
||||
|
||||
static void importTheme(File themeFile) {
|
||||
if (!ThemeUtils.askToSaveThemeChanges()) {
|
||||
return;
|
||||
}
|
||||
GTheme startingTheme = Gui.getActiveTheme();
|
||||
try {
|
||||
FileGTheme imported;
|
||||
if (themeFile.getName().endsWith(GTheme.ZIP_FILE_EXTENSION)) {
|
||||
imported = new ZipGTheme(themeFile);
|
||||
}
|
||||
else if (themeFile.getName().endsWith(GTheme.FILE_EXTENSION)) {
|
||||
imported = new FileGTheme(themeFile);
|
||||
}
|
||||
else {
|
||||
Msg.showError(ThemeUtils.class, null, "Error Importing Theme",
|
||||
"Imported File must end in either " + GTheme.FILE_EXTENSION + " or " +
|
||||
GTheme.ZIP_FILE_EXTENSION);
|
||||
return;
|
||||
}
|
||||
Gui.setTheme(imported);
|
||||
if (!ThemeUtils.saveThemeChanges()) {
|
||||
Gui.setTheme(startingTheme);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(ThemeUtils.class, null, "Error Importing Theme File",
|
||||
"Error encountered importing file: " + themeFile.getAbsolutePath(), e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void exportTheme() {
|
||||
if (!ThemeUtils.askToSaveThemeChanges()) {
|
||||
return;
|
||||
}
|
||||
boolean hasExternalIcons = !Gui.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.)";
|
||||
if (hasExternalIcons) {
|
||||
message =
|
||||
"Export as zip file? (You have external icons so a zip file is required if you\n" +
|
||||
"want to include the icons in the export.)";
|
||||
}
|
||||
int result = OptionDialog.showOptionDialog(null, "Export as Zip?", message, "Export Zip",
|
||||
"Export File", OptionDialog.QUESTION_MESSAGE);
|
||||
if (result == OptionDialog.CANCEL_OPTION) {
|
||||
return;
|
||||
}
|
||||
boolean exportAsZip = result == OptionDialog.OPTION_ONE;
|
||||
|
||||
ExportThemeDialog dialog = new ExportThemeDialog(exportAsZip);
|
||||
DockingWindowManager.showDialog(dialog);
|
||||
}
|
||||
|
||||
public static void deleteTheme() {
|
||||
List<GTheme> savedThemes = new ArrayList<>(
|
||||
Gui.getAllThemes().stream().filter(t -> t instanceof FileGTheme).toList());
|
||||
if (savedThemes.isEmpty()) {
|
||||
Msg.showInfo(ThemeUtils.class, null, "Delete Theme", "There are no deletable themes");
|
||||
return;
|
||||
}
|
||||
|
||||
GTheme selectedTheme = SelectFromListDialog.selectFromList(savedThemes, "Delete Theme",
|
||||
"Select theme to delete", t -> t.getName());
|
||||
if (selectedTheme == null) {
|
||||
return;
|
||||
}
|
||||
if (Gui.getActiveTheme().equals(selectedTheme)) {
|
||||
Msg.showWarn(ThemeUtils.class, null, "Delete Failed",
|
||||
"Can't delete the current theme.");
|
||||
return;
|
||||
}
|
||||
FileGTheme fileTheme = (FileGTheme) selectedTheme;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getNameFromUser(String name) {
|
||||
InputDialog inputDialog = new InputDialog("Create Theme", "New Theme Name", name);
|
||||
DockingWindowManager.showDialog(inputDialog);
|
||||
return inputDialog.getValue();
|
||||
}
|
||||
|
||||
private static boolean canSaveToName(String name) {
|
||||
GTheme existing = Gui.getTheme(name);
|
||||
if (existing == null) {
|
||||
return true;
|
||||
}
|
||||
if (existing instanceof FileGTheme fileTheme) {
|
||||
int result = OptionDialog.showYesNoDialog(null, "Overwrite Existing Theme?",
|
||||
"Do you want to overwrite the existing theme file for \"" + name + "\"?");
|
||||
if (result == OptionDialog.YES_OPTION) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean saveCurrentValues(String themeName) {
|
||||
GTheme activeTheme = Gui.getActiveTheme();
|
||||
File file = getSaveFile(themeName);
|
||||
|
||||
FileGTheme newTheme = new FileGTheme(file, themeName, activeTheme.getLookAndFeelType(),
|
||||
activeTheme.useDarkDefaults());
|
||||
newTheme.load(Gui.getNonDefaultValues());
|
||||
try {
|
||||
newTheme.save();
|
||||
Gui.addTheme(newTheme);
|
||||
Gui.setTheme(newTheme);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(ThemeUtils.class, null, "I/O Error",
|
||||
"Error writing theme file: " + newTheme.getFile().getAbsolutePath(), e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private static File getSaveFile(String themeName) {
|
||||
File dir = Application.getUserSettingsDirectory();
|
||||
File themeDir = new File(dir, Gui.THEME_DIR);
|
||||
if (!themeDir.exists()) {
|
||||
themeDir.mkdir();
|
||||
}
|
||||
String cleanedName = themeName.replaceAll(" ", "_") + "." + GTheme.FILE_EXTENSION;
|
||||
return new File(themeDir, cleanedName);
|
||||
}
|
||||
}
|
@ -56,13 +56,12 @@ public abstract class ThemeValueEditor<T> {
|
||||
*/
|
||||
public void editValue(ThemeValue<T> themeValue) {
|
||||
this.currentThemeValue = themeValue;
|
||||
T value = getRawValue(themeValue.getId());
|
||||
if (dialog == null) {
|
||||
dialog = new EditorDialog(value);
|
||||
dialog = new EditorDialog(themeValue);
|
||||
DockingWindowManager.showDialog(dialog);
|
||||
}
|
||||
else {
|
||||
dialog.setValue(value);
|
||||
dialog.setValue(themeValue);
|
||||
dialog.toFront();
|
||||
}
|
||||
|
||||
@ -83,22 +82,30 @@ public abstract class ThemeValueEditor<T> {
|
||||
*/
|
||||
protected abstract ThemeValue<T> createNewThemeValue(String id, T newValue);
|
||||
|
||||
private void valueChanged(T newValue) {
|
||||
private void valueChanged(T value) {
|
||||
ThemeValue<T> oldValue = currentThemeValue;
|
||||
String id = oldValue.getId();
|
||||
ThemeValue<T> newValue = createNewThemeValue(id, value);
|
||||
firePropertyChangeEvent(oldValue, newValue);
|
||||
PropertyChangeEvent event =
|
||||
new PropertyChangeEvent(this, id, oldValue, createNewThemeValue(id, newValue));
|
||||
new PropertyChangeEvent(this, id, oldValue, newValue);
|
||||
clientListener.propertyChange(event);
|
||||
}
|
||||
|
||||
private void firePropertyChangeEvent(ThemeValue<T> oldValue, ThemeValue<T> newValue) {
|
||||
PropertyChangeEvent event =
|
||||
new PropertyChangeEvent(this, oldValue.getId(), oldValue, newValue);
|
||||
clientListener.propertyChange(event);
|
||||
}
|
||||
|
||||
class EditorDialog extends DialogComponentProvider {
|
||||
private PropertyChangeListener internalListener = ev -> editorChanged();
|
||||
private T originalValue;
|
||||
private ThemeValue<T> originalValue;
|
||||
|
||||
protected EditorDialog(T initialValue) {
|
||||
protected EditorDialog(ThemeValue<T> initialValue) {
|
||||
super("Edit " + typeName + ": " + currentThemeValue.getId(), false, false, true, false);
|
||||
this.originalValue = initialValue;
|
||||
addWorkPanel(buildWorkPanel(initialValue));
|
||||
addWorkPanel(buildWorkPanel(getRawValue(initialValue.getId())));
|
||||
addOKButton();
|
||||
addCancelButton();
|
||||
setRememberSize(false);
|
||||
@ -119,10 +126,10 @@ public abstract class ThemeValueEditor<T> {
|
||||
return panel;
|
||||
}
|
||||
|
||||
void setValue(T value) {
|
||||
void setValue(ThemeValue<T> value) {
|
||||
originalValue = value;
|
||||
editor.removePropertyChangeListener(internalListener);
|
||||
editor.setValue(value);
|
||||
editor.setValue(getRawValue(value.getId()));
|
||||
editor.addPropertyChangeListener(internalListener);
|
||||
}
|
||||
|
||||
@ -134,7 +141,7 @@ public abstract class ThemeValueEditor<T> {
|
||||
|
||||
@Override
|
||||
protected void cancelCallback() {
|
||||
valueChanged(originalValue);
|
||||
firePropertyChangeEvent(currentThemeValue, originalValue);
|
||||
close();
|
||||
dialog = null;
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.formats.gfilesystem;
|
||||
package docking.widgets;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
@ -25,7 +25,6 @@ import javax.swing.*;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.DockingWindowManager;
|
||||
import docking.widgets.MultiLineLabel;
|
||||
import docking.widgets.list.ListPanel;
|
||||
import ghidra.util.SystemUtilities;
|
||||
|
||||
@ -92,6 +91,10 @@ public class SelectFromListDialog<T> extends DialogComponentProvider {
|
||||
return selectedObject;
|
||||
}
|
||||
|
||||
public void setSelectedObject(T obj) {
|
||||
listPanel.setSelectedValue(obj);
|
||||
}
|
||||
|
||||
private void doSelect() {
|
||||
selectedObject = null;
|
||||
actionComplete = false;
|
Binary file not shown.
After Width: | Height: | Size: 768 B |
Binary file not shown.
After Width: | Height: | Size: 716 B |
@ -0,0 +1,217 @@
|
||||
/* ###
|
||||
* 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 docking.theme.gui;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import docking.test.AbstractDockingTest;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.SelectFromListDialog;
|
||||
import docking.widgets.dialogs.InputDialog;
|
||||
import generic.theme.*;
|
||||
import generic.theme.builtin.MetalTheme;
|
||||
import generic.theme.builtin.NimbusTheme;
|
||||
|
||||
public class ThemeUtilsTest extends AbstractDockingTest {
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
GTheme nimbusTheme = new NimbusTheme();
|
||||
GTheme metalTheme = new MetalTheme();
|
||||
Gui.addTheme(nimbusTheme);
|
||||
Gui.addTheme(metalTheme);
|
||||
Gui.setTheme(nimbusTheme);
|
||||
|
||||
// get rid of any leftover imported themes from previous tests
|
||||
Set<GTheme> allThemes = Gui.getAllThemes();
|
||||
for (GTheme gTheme : allThemes) {
|
||||
if (gTheme instanceof FileGTheme fileTheme) {
|
||||
Gui.deleteTheme(fileTheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportThemeNonZip() throws IOException {
|
||||
assertEquals("Nimbus Theme", Gui.getActiveTheme().getName());
|
||||
File themeFile = createThemeFile("Bob");
|
||||
ThemeUtils.importTheme(themeFile);
|
||||
assertEquals("Bob", Gui.getActiveTheme().getName());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportThemeFromZip() throws IOException {
|
||||
assertEquals("Nimbus Theme", Gui.getActiveTheme().getName());
|
||||
File themeFile = createZipThemeFile("zippy");
|
||||
ThemeUtils.importTheme(themeFile);
|
||||
assertEquals("zippy", Gui.getActiveTheme().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportThemeWithCurrentChangesCancelled() throws IOException {
|
||||
assertEquals("Nimbus Theme", Gui.getActiveTheme().getName());
|
||||
Gui.setColor("Panel.background", Color.RED);
|
||||
assertTrue(Gui.hasThemeChanges());
|
||||
|
||||
File themeFile = createThemeFile("Bob");
|
||||
runSwingLater(() -> ThemeUtils.importTheme(themeFile));
|
||||
OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
|
||||
assertNotNull(dialog);
|
||||
assertEquals("Save Theme Changes?", dialog.getTitle());
|
||||
pressButtonByText(dialog, "Cancel");
|
||||
waitForSwing();
|
||||
assertEquals("Nimbus Theme", Gui.getActiveTheme().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportThemeWithCurrentChangesSaved() throws IOException {
|
||||
assertEquals("Nimbus Theme", Gui.getActiveTheme().getName());
|
||||
|
||||
// make a change in the current theme, so you get asked to save
|
||||
Gui.setColor("Panel.background", Color.RED);
|
||||
assertTrue(Gui.hasThemeChanges());
|
||||
|
||||
File themeFile = createThemeFile("Bob");
|
||||
runSwingLater(() -> ThemeUtils.importTheme(themeFile));
|
||||
OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
|
||||
assertNotNull(dialog);
|
||||
assertEquals("Save Theme Changes?", dialog.getTitle());
|
||||
pressButtonByText(dialog, "Yes");
|
||||
InputDialog inputDialog = waitForDialogComponent(InputDialog.class);
|
||||
assertNotNull(inputDialog);
|
||||
runSwing(() -> inputDialog.setValue("Joe"));
|
||||
pressButtonByText(inputDialog, "OK");
|
||||
waitForSwing();
|
||||
assertEquals("Bob", Gui.getActiveTheme().getName());
|
||||
assertNotNull(Gui.getTheme("Joe"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImportThemeWithCurrentChangesThrownAway() throws IOException {
|
||||
assertEquals("Nimbus Theme", Gui.getActiveTheme().getName());
|
||||
|
||||
// make a change in the current theme, so you get asked to save
|
||||
Gui.setColor("Panel.background", Color.RED);
|
||||
assertTrue(Gui.hasThemeChanges());
|
||||
|
||||
File bobThemeFile = createThemeFile("Bob");
|
||||
runSwingLater(() -> ThemeUtils.importTheme(bobThemeFile));
|
||||
|
||||
OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
|
||||
assertNotNull(dialog);
|
||||
assertEquals("Save Theme Changes?", dialog.getTitle());
|
||||
pressButtonByText(dialog, "No");
|
||||
waitForSwing();
|
||||
assertEquals("Bob", Gui.getActiveTheme().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportThemeAsZip() throws IOException {
|
||||
runSwingLater(() -> ThemeUtils.exportTheme());
|
||||
OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
|
||||
pressButtonByText(dialog, "Export Zip");
|
||||
ExportThemeDialog exportDialog = waitForDialogComponent(ExportThemeDialog.class);
|
||||
File exportFile = createTempFile("whatever", ".theme.zip");
|
||||
runSwing(() -> exportDialog.setOutputFile(exportFile));
|
||||
pressButtonByText(exportDialog, "OK");
|
||||
waitForSwing();
|
||||
assertTrue(exportFile.exists());
|
||||
ZipGTheme zipTheme = new ZipGTheme(exportFile);
|
||||
assertEquals("Nimbus Theme", zipTheme.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExportThemeAsFile() throws IOException {
|
||||
runSwingLater(() -> ThemeUtils.exportTheme());
|
||||
OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
|
||||
pressButtonByText(dialog, "Export File");
|
||||
ExportThemeDialog exportDialog = waitForDialogComponent(ExportThemeDialog.class);
|
||||
File exportFile = createTempFile("whatever", ".theme");
|
||||
runSwing(() -> exportDialog.setOutputFile(exportFile));
|
||||
pressButtonByText(exportDialog, "OK");
|
||||
waitForSwing();
|
||||
assertTrue(exportFile.exists());
|
||||
FileGTheme fileTheme = new FileGTheme(exportFile);
|
||||
assertEquals("Nimbus Theme", fileTheme.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteTheme() throws IOException {
|
||||
File themeFile = createThemeFile("Bob");
|
||||
ThemeUtils.importTheme(themeFile);
|
||||
themeFile = createThemeFile("Joe");
|
||||
ThemeUtils.importTheme(themeFile);
|
||||
themeFile = createThemeFile("Lisa");
|
||||
ThemeUtils.importTheme(themeFile);
|
||||
|
||||
assertNotNull(Gui.getTheme("Bob"));
|
||||
assertNotNull(Gui.getTheme("Joe"));
|
||||
assertNotNull(Gui.getTheme("Lisa"));
|
||||
|
||||
runSwingLater(() -> ThemeUtils.deleteTheme());
|
||||
@SuppressWarnings("unchecked")
|
||||
SelectFromListDialog<GTheme> dialog = waitForDialogComponent(SelectFromListDialog.class);
|
||||
runSwing(() -> dialog.setSelectedObject(Gui.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"));
|
||||
|
||||
}
|
||||
|
||||
private File createZipThemeFile(String themeName) throws IOException {
|
||||
File file = createTempFile("Test_Theme", ".theme.zip");
|
||||
ZipGTheme zipGTheme = new ZipGTheme(file, themeName, LafType.METAL, false);
|
||||
zipGTheme.addColor(new ColorValue("Panel.Background", Color.RED));
|
||||
zipGTheme.save();
|
||||
return file;
|
||||
}
|
||||
|
||||
private File createThemeFile(String themeName) throws IOException {
|
||||
String themeData = createThemeDataString(themeName);
|
||||
File file = createTempFile("Test_Theme", ".theme");
|
||||
FileUtils.writeStringToFile(file, themeData, Charset.defaultCharset());
|
||||
return file;
|
||||
}
|
||||
|
||||
private String createThemeDataString(String themeName) {
|
||||
String themeData = """
|
||||
name = THEMENAME
|
||||
lookAndFeel = Metal
|
||||
useDarkDefaults = false
|
||||
[color]Panel.background = #ffcccc
|
||||
""";
|
||||
|
||||
return themeData.replace("THEMENAME", themeName);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/* ###
|
||||
* 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.io.*;
|
||||
import java.util.Enumeration;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class ExternalThemeReader extends ThemeReader {
|
||||
|
||||
public ExternalThemeReader(File file) throws IOException {
|
||||
try (ZipFile zipFile = new ZipFile(file)) {
|
||||
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
String name = entry.getName();
|
||||
try (InputStream is = zipFile.getInputStream(entry)) {
|
||||
if (name.endsWith(".theme")) {
|
||||
processThemeData(name, is);
|
||||
}
|
||||
else {
|
||||
processIconFile(name, is);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processIconFile(String path, InputStream is) throws IOException {
|
||||
int indexOf = path.indexOf("images/");
|
||||
if (indexOf < 0) {
|
||||
Msg.error(this, "Unknown file: " + path);
|
||||
}
|
||||
String relativePath = path.substring(indexOf, path.length());
|
||||
File dir = Application.getUserSettingsDirectory();
|
||||
File iconFile = new File(dir, relativePath);
|
||||
FileUtils.copyInputStreamToFile(is, iconFile);
|
||||
}
|
||||
|
||||
private void processThemeData(String name, InputStream is) throws IOException {
|
||||
InputStreamReader reader = new InputStreamReader(is);
|
||||
read(reader);
|
||||
}
|
||||
}
|
@ -27,8 +27,9 @@ import ghidra.util.WebColors;
|
||||
import resources.icons.UrlImageIcon;
|
||||
|
||||
public class FileGTheme extends GTheme {
|
||||
public static final String JAVA_ICON = "<JAVA ICON>";
|
||||
public static final String FILE_PREFIX = "File:";
|
||||
private final File file;
|
||||
protected final File file;
|
||||
|
||||
public FileGTheme(File file) throws IOException {
|
||||
this(file, new ThemeReader(file));
|
||||
@ -40,7 +41,7 @@ public class FileGTheme extends GTheme {
|
||||
}
|
||||
|
||||
FileGTheme(File file, ThemeReader reader) {
|
||||
super(reader.getThemeName(), reader.getLookAndFeelType(), false);
|
||||
super(reader.getThemeName(), reader.getLookAndFeelType(), reader.useDarkDefaults());
|
||||
this.file = file;
|
||||
reader.loadValues(this);
|
||||
}
|
||||
@ -63,46 +64,50 @@ public class FileGTheme extends GTheme {
|
||||
|
||||
public void save() throws IOException {
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
|
||||
List<ColorValue> colors = getColors();
|
||||
Collections.sort(colors);
|
||||
|
||||
List<FontValue> fonts = getFonts();
|
||||
Collections.sort(fonts);
|
||||
|
||||
List<IconValue> icons = getIcons();
|
||||
Collections.sort(icons);
|
||||
|
||||
writer.write(THEME_NAME_KEY + " = " + getName());
|
||||
writer.newLine();
|
||||
|
||||
writer.write(THEME_LOOK_AND_FEEL_KEY + " = " + getLookAndFeelType().getName());
|
||||
writer.newLine();
|
||||
|
||||
writer.write(THEME_USE_DARK_DEFAULTS + " = " + useDarkDefaults());
|
||||
writer.newLine();
|
||||
|
||||
for (ColorValue colorValue : colors) {
|
||||
String outputId = colorValue.toExternalId(colorValue.getId());
|
||||
writer.write(outputId + " = " + getValueOutput(colorValue));
|
||||
writer.newLine();
|
||||
}
|
||||
|
||||
for (FontValue fontValue : fonts) {
|
||||
String outputId = fontValue.toExternalId(fontValue.getId());
|
||||
writer.write(outputId + " = " + getValueOutput(fontValue));
|
||||
writer.newLine();
|
||||
}
|
||||
|
||||
for (IconValue iconValue : icons) {
|
||||
String outputId = iconValue.toExternalId(iconValue.getId());
|
||||
writer.write(outputId + " = " + getValueOutput(iconValue));
|
||||
writer.newLine();
|
||||
}
|
||||
writeThemeValues(writer);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void writeThemeValues(BufferedWriter writer) throws IOException {
|
||||
List<ColorValue> colors = getColors();
|
||||
Collections.sort(colors);
|
||||
|
||||
List<FontValue> fonts = getFonts();
|
||||
Collections.sort(fonts);
|
||||
|
||||
List<IconValue> icons = getIcons();
|
||||
Collections.sort(icons);
|
||||
|
||||
writer.write(THEME_NAME_KEY + " = " + getName());
|
||||
writer.newLine();
|
||||
|
||||
writer.write(THEME_LOOK_AND_FEEL_KEY + " = " + getLookAndFeelType().getName());
|
||||
writer.newLine();
|
||||
|
||||
writer.write(THEME_USE_DARK_DEFAULTS + " = " + useDarkDefaults());
|
||||
writer.newLine();
|
||||
|
||||
for (ColorValue colorValue : colors) {
|
||||
String outputId = colorValue.toExternalId(colorValue.getId());
|
||||
writer.write(outputId + " = " + getValueOutput(colorValue));
|
||||
writer.newLine();
|
||||
}
|
||||
|
||||
for (FontValue fontValue : fonts) {
|
||||
String outputId = fontValue.toExternalId(fontValue.getId());
|
||||
writer.write(outputId + " = " + getValueOutput(fontValue));
|
||||
writer.newLine();
|
||||
}
|
||||
|
||||
for (IconValue iconValue : icons) {
|
||||
String outputId = iconValue.toExternalId(iconValue.getId());
|
||||
writer.write(outputId + " = " + getValueOutput(iconValue));
|
||||
writer.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
private String getValueOutput(ColorValue colorValue) {
|
||||
if (colorValue.getReferenceId() != null) {
|
||||
return colorValue.toExternalId(colorValue.getReferenceId());
|
||||
@ -128,7 +133,7 @@ public class FileGTheme extends GTheme {
|
||||
if (icon instanceof UrlImageIcon urlIcon) {
|
||||
return urlIcon.getOriginalPath();
|
||||
}
|
||||
return "<UNKNOWN>";
|
||||
return JAVA_ICON;
|
||||
}
|
||||
|
||||
private String getValueOutput(FontValue fontValue) {
|
||||
|
@ -28,7 +28,8 @@ import javax.swing.Icon;
|
||||
* in an application.
|
||||
*/
|
||||
public class GTheme extends GThemeValueMap {
|
||||
public static String FILE_EXTENSION = ".theme";
|
||||
public static String FILE_EXTENSION = "theme";
|
||||
public static String ZIP_FILE_EXTENSION = "theme.zip";
|
||||
|
||||
static final String THEME_NAME_KEY = "name";
|
||||
static final String THEME_LOOK_AND_FEEL_KEY = "lookAndFeel";
|
||||
@ -39,7 +40,7 @@ public class GTheme extends GThemeValueMap {
|
||||
private final boolean useDarkDefaults;
|
||||
|
||||
public GTheme(String name) {
|
||||
this(name, LafType.SYSTEM, false);
|
||||
this(name, LafType.getDefaultLookAndFeel(), false);
|
||||
|
||||
}
|
||||
|
||||
@ -49,7 +50,7 @@ public class GTheme extends GThemeValueMap {
|
||||
* @param lookAndFeel the look and feel type used by this theme
|
||||
* @param useDarkDefaults determines whether or
|
||||
*/
|
||||
protected GTheme(String name, LafType lookAndFeel, boolean useDarkDefaults) {
|
||||
public GTheme(String name, LafType lookAndFeel, boolean useDarkDefaults) {
|
||||
this.name = name;
|
||||
this.lookAndFeel = lookAndFeel;
|
||||
this.useDarkDefaults = useDarkDefaults;
|
||||
|
@ -15,12 +15,19 @@
|
||||
*/
|
||||
package generic.theme;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import resources.ResourceManager;
|
||||
import resources.icons.UrlImageIcon;
|
||||
|
||||
public class GThemeValueMap {
|
||||
Map<String, ColorValue> colorMap = new HashMap<>();
|
||||
Map<String, FontValue> fontMap = new HashMap<>();
|
||||
Map<String, IconValue> iconMap = new HashMap<>();
|
||||
protected Map<String, ColorValue> colorMap = new HashMap<>();
|
||||
protected Map<String, FontValue> fontMap = new HashMap<>();
|
||||
protected Map<String, IconValue> iconMap = new HashMap<>();
|
||||
|
||||
public GThemeValueMap() {
|
||||
}
|
||||
@ -132,4 +139,29 @@ public class GThemeValueMap {
|
||||
fontMap.remove(id);
|
||||
}
|
||||
|
||||
public void removeIcon(String id) {
|
||||
iconMap.remove(id);
|
||||
}
|
||||
|
||||
public Set<File> getExternalIconFiles() {
|
||||
Set<File> files = new HashSet<>();
|
||||
for (IconValue iconValue : iconMap.values()) {
|
||||
Icon icon = iconValue.getRawValue();
|
||||
if (icon instanceof UrlImageIcon urlIcon) {
|
||||
String originalPath = urlIcon.getOriginalPath();
|
||||
if (originalPath.startsWith(ResourceManager.EXTERNAL_ICON_PREFIX)) {
|
||||
URL url = urlIcon.getUrl();
|
||||
String filePath = url.getFile();
|
||||
if (filePath != null) {
|
||||
File iconFile = new File(filePath);
|
||||
if (iconFile.exists()) {
|
||||
files.add(iconFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,18 +15,20 @@
|
||||
*/
|
||||
package generic.theme;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.UIManager;
|
||||
import javax.swing.plaf.ComponentUI;
|
||||
|
||||
import com.formdev.flatlaf.*;
|
||||
|
||||
import generic.theme.builtin.JavaColorMapping;
|
||||
import ghidra.framework.Application;
|
||||
import generic.theme.builtin.*;
|
||||
import generic.theme.laf.LookAndFeelManager;
|
||||
import ghidra.framework.*;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
@ -42,8 +44,8 @@ public class Gui {
|
||||
|
||||
private static final String THEME_PREFFERENCE_KEY = "Theme";
|
||||
|
||||
private static GTheme activeTheme = new DefaultTheme();
|
||||
private static Set<GTheme> allThemes;
|
||||
private static GTheme activeTheme = getDefaultTheme();
|
||||
private static Set<GTheme> allThemes = null;
|
||||
|
||||
private static GThemeValueMap ghidraLightDefaults = new GThemeValueMap();
|
||||
private static GThemeValueMap ghidraDarkDefaults = new GThemeValueMap();
|
||||
@ -55,8 +57,15 @@ public class Gui {
|
||||
private static Map<String, GColorUIResource> gColorMap = new HashMap<>();
|
||||
private static boolean isInitialized;
|
||||
private static Map<String, GIconUIResource> gIconMap = new HashMap<>();
|
||||
|
||||
// these notifications are only when the user is manipulating theme values, so rare and at
|
||||
// user speed, so using copy on read
|
||||
private static WeakSet<ThemeListener> themeListeners =
|
||||
WeakDataStructureFactory.createCopyOnWriteWeakSet();
|
||||
WeakDataStructureFactory.createCopyOnReadWeakSet();
|
||||
|
||||
// stores the original value for ids whose value has changed from the current theme
|
||||
private static GThemeValueMap changedValuesMap = new GThemeValueMap();
|
||||
private static LookAndFeelManager lookAndFeelManager;
|
||||
|
||||
private Gui() {
|
||||
// static utils class, can't construct
|
||||
@ -86,23 +95,25 @@ public class Gui {
|
||||
public static void reloadGhidraDefaults() {
|
||||
loadThemeDefaults();
|
||||
buildCurrentValues();
|
||||
lookAndFeelManager.update();
|
||||
notifyThemeValuesRestored();
|
||||
}
|
||||
|
||||
public static void restoreThemeValues() {
|
||||
buildCurrentValues();
|
||||
lookAndFeelManager.update();
|
||||
notifyThemeValuesRestored();
|
||||
}
|
||||
|
||||
public static void setTheme(GTheme theme) {
|
||||
if (theme.hasSupportedLookAndFeel()) {
|
||||
activeTheme = theme;
|
||||
LafType lookAndFeel = theme.getLookAndFeelType();
|
||||
lookAndFeelManager = lookAndFeel.getLookAndFeelManager();
|
||||
try {
|
||||
lookAndFeel.install();
|
||||
lookAndFeelManager.installLookAndFeel();
|
||||
notifyThemeChanged();
|
||||
saveThemeToPreferences(theme);
|
||||
fixupJavaDefaults();
|
||||
buildCurrentValues();
|
||||
updateUIs();
|
||||
notifyThemeListeners();
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(Gui.class, "Error setting LookAndFeel: " + lookAndFeel.getName(), e);
|
||||
@ -110,20 +121,46 @@ public class Gui {
|
||||
}
|
||||
}
|
||||
|
||||
private static void notifyThemeListeners() {
|
||||
private static void notifyThemeChanged() {
|
||||
for (ThemeListener listener : themeListeners) {
|
||||
listener.themeChanged(activeTheme);
|
||||
}
|
||||
}
|
||||
|
||||
private static void notifyThemeValuesRestored() {
|
||||
for (ThemeListener listener : themeListeners) {
|
||||
listener.themeValuesRestored();
|
||||
}
|
||||
}
|
||||
|
||||
private static void notifyColorChanged(String id) {
|
||||
for (ThemeListener listener : themeListeners) {
|
||||
listener.colorChanged(id);
|
||||
}
|
||||
}
|
||||
|
||||
private static void notifyFontChanged(String id) {
|
||||
for (ThemeListener listener : themeListeners) {
|
||||
listener.fontChanged(id);
|
||||
}
|
||||
}
|
||||
|
||||
private static void notifyIconChanged(String id) {
|
||||
for (ThemeListener listener : themeListeners) {
|
||||
listener.iconChanged(id);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addTheme(GTheme newTheme) {
|
||||
loadThemes();
|
||||
allThemes.remove(newTheme);
|
||||
allThemes.add(newTheme);
|
||||
}
|
||||
|
||||
private static void updateUIs() {
|
||||
for (Window window : Window.getWindows()) {
|
||||
SwingUtilities.updateComponentTreeUI(window);
|
||||
public static void deleteTheme(FileGTheme theme) {
|
||||
theme.file.delete();
|
||||
if (allThemes != null) {
|
||||
allThemes.remove(theme);
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,16 +177,12 @@ public class Gui {
|
||||
}
|
||||
|
||||
public static Set<GTheme> getAllThemes() {
|
||||
if (allThemes == null) {
|
||||
allThemes = findThemes();
|
||||
}
|
||||
return Collections.unmodifiableSet(allThemes);
|
||||
loadThemes();
|
||||
return new HashSet<>(allThemes);
|
||||
}
|
||||
|
||||
public static Set<GTheme> getSupportedThemes() {
|
||||
if (allThemes == null) {
|
||||
allThemes = findThemes();
|
||||
}
|
||||
loadThemes();
|
||||
Set<GTheme> supported = new HashSet<>();
|
||||
for (GTheme theme : allThemes) {
|
||||
if (theme.hasSupportedLookAndFeel()) {
|
||||
@ -251,26 +284,21 @@ public class Gui {
|
||||
}
|
||||
map.load(activeTheme);
|
||||
currentValues = map;
|
||||
GColor.refreshAll();
|
||||
GIcon.refreshAll();
|
||||
repaintAll();
|
||||
changedValuesMap.clear();
|
||||
}
|
||||
|
||||
private static Set<GTheme> findThemes() {
|
||||
Set<GTheme> set = new HashSet<>();
|
||||
set.addAll(findDiscoverableThemes());
|
||||
set.addAll(loadThemesFromFiles());
|
||||
|
||||
// The set should contains a duplicate of the active theme. Make sure the active theme
|
||||
// instance is the one in the set
|
||||
set.remove(activeTheme);
|
||||
set.add(activeTheme);
|
||||
return set;
|
||||
private static void loadThemes() {
|
||||
if (allThemes == null) {
|
||||
Set<GTheme> set = new HashSet<>();
|
||||
set.addAll(findDiscoverableThemes());
|
||||
set.addAll(loadThemesFromFiles());
|
||||
allThemes = set;
|
||||
}
|
||||
}
|
||||
|
||||
private static Collection<GTheme> loadThemesFromFiles() {
|
||||
List<File> fileList = new ArrayList<>();
|
||||
FileFilter themeFileFilter = file -> file.getName().endsWith(GTheme.FILE_EXTENSION);
|
||||
FileFilter themeFileFilter = file -> file.getName().endsWith("." + GTheme.FILE_EXTENSION);
|
||||
|
||||
File dir = Application.getUserSettingsDirectory();
|
||||
File themeDir = new File(dir, THEME_DIR);
|
||||
@ -326,34 +354,84 @@ public class Gui {
|
||||
"Can't find or instantiate class: " + className);
|
||||
}
|
||||
}
|
||||
return new DefaultTheme();
|
||||
return getDefaultTheme();
|
||||
}
|
||||
|
||||
public static void setFont(FontValue newValue) {
|
||||
FontValue currentValue = currentValues.getFont(newValue.getId());
|
||||
if (newValue.equals(currentValue)) {
|
||||
return;
|
||||
}
|
||||
updateChangedValuesMap(currentValue, newValue);
|
||||
|
||||
currentValues.addFont(newValue);
|
||||
// all fonts are direct (there is no GFont), so to we need to update the
|
||||
// UiDefaults for java fonts. Ghidra fonts are expected to be "on the fly" (they
|
||||
// call Gui.getFont(id) for every use.
|
||||
String id = newValue.getId();
|
||||
if (javaDefaults.containsFont(id)) {
|
||||
UIManager.getDefaults().put(id, newValue.get(currentValues));
|
||||
updateUIs();
|
||||
}
|
||||
else {
|
||||
repaintAll();
|
||||
}
|
||||
boolean isJavaFont = javaDefaults.containsFont(id);
|
||||
lookAndFeelManager.updateFont(id, newValue.get(currentValues), isJavaFont);
|
||||
notifyFontChanged(id);
|
||||
}
|
||||
|
||||
public static void setColor(String id, Color color) {
|
||||
setColor(new ColorValue(id, color));
|
||||
}
|
||||
|
||||
public static void setColor(ColorValue colorValue) {
|
||||
currentValues.addColor(colorValue);
|
||||
// all colors use indirection via GColor, so to update all we need to do is refresh GColors
|
||||
// and repaint
|
||||
GColor.refreshAll();
|
||||
repaintAll();
|
||||
public static void setColor(ColorValue newValue) {
|
||||
ColorValue currentValue = currentValues.getColor(newValue.getId());
|
||||
if (newValue.equals(currentValue)) {
|
||||
return;
|
||||
}
|
||||
updateChangedValuesMap(currentValue, newValue);
|
||||
|
||||
currentValues.addColor(newValue);
|
||||
String id = newValue.getId();
|
||||
boolean isJavaColor = javaDefaults.containsColor(id);
|
||||
lookAndFeelManager.updateColor(id, newValue.get(currentValues), isJavaColor);
|
||||
notifyColorChanged(newValue.getId());
|
||||
}
|
||||
|
||||
private static 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 static 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 static 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);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setIcon(String id, Icon icon) {
|
||||
@ -361,31 +439,20 @@ public class Gui {
|
||||
}
|
||||
|
||||
public static void setIcon(IconValue newValue) {
|
||||
IconValue currentValue = currentValues.getIcon(newValue.getId());
|
||||
if (newValue.equals(currentValue)) {
|
||||
return;
|
||||
}
|
||||
updateChangedValuesMap(currentValue, newValue);
|
||||
|
||||
currentValues.addIcon(newValue);
|
||||
|
||||
// Icons are a mixed bag. Java Icons are direct and Ghidra Icons are indirect (to support static use)
|
||||
// Mainly because Nimbus is buggy and can't handle non-nimbus Icons, so we can't wrap them
|
||||
// So need to update UiDefaults for java icons. For Ghidra Icons, it is sufficient to refrech
|
||||
// GIcons and repaint
|
||||
String id = newValue.getId();
|
||||
if (javaDefaults.containsIcon(id)) {
|
||||
UIManager.getDefaults().put(id, newValue.get(currentValues));
|
||||
updateUIs();
|
||||
}
|
||||
else {
|
||||
GIcon.refreshAll();
|
||||
repaintAll();
|
||||
}
|
||||
}
|
||||
|
||||
private static void repaintAll() {
|
||||
for (Window window : Window.getWindows()) {
|
||||
window.repaint();
|
||||
}
|
||||
boolean isJavaIcon = javaDefaults.containsIcon(id);
|
||||
lookAndFeelManager.updateIcon(id, newValue.get(currentValues), isJavaIcon);
|
||||
notifyIconChanged(id);
|
||||
}
|
||||
|
||||
public static GColorUIResource getGColorUiResource(String id) {
|
||||
|
||||
GColorUIResource gColor = gColorMap.get(id);
|
||||
if (gColor == null) {
|
||||
gColor = new GColorUIResource(id);
|
||||
@ -405,11 +472,13 @@ public class Gui {
|
||||
}
|
||||
|
||||
public static void setJavaDefaults(GThemeValueMap map) {
|
||||
javaDefaults = map;
|
||||
javaDefaults = fixupJavaDefaultsInheritence(map);
|
||||
buildCurrentValues();
|
||||
GColor.refreshAll();
|
||||
GIcon.refreshAll();
|
||||
}
|
||||
|
||||
public static void fixupJavaDefaults() {
|
||||
public static GThemeValueMap fixupJavaDefaultsInheritence(GThemeValueMap map) {
|
||||
List<ColorValue> colors = javaDefaults.getColors();
|
||||
JavaColorMapping mapping = new JavaColorMapping();
|
||||
for (ColorValue value : colors) {
|
||||
@ -418,6 +487,7 @@ public class Gui {
|
||||
javaDefaults.addColor(mapped);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public static GThemeValueMap getJavaDefaults() {
|
||||
@ -476,4 +546,22 @@ public class Gui {
|
||||
themePropertiesLoader = loader;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasThemeChanges() {
|
||||
return !changedValuesMap.isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,8 +33,7 @@ public enum LafType {
|
||||
FLAT_DARCULA("Flat Darcula"),
|
||||
WINDOWS("Windows"),
|
||||
WINDOWS_CLASSIC("Windows Classic"),
|
||||
MAC("Mac OS X"),
|
||||
SYSTEM("System");
|
||||
MAC("Mac OS X");
|
||||
|
||||
private String name;
|
||||
|
||||
@ -55,24 +54,7 @@ public enum LafType {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static LookAndFeelInstaller getSystemLookAndFeelInstaller() {
|
||||
OperatingSystem OS = Platform.CURRENT_PLATFORM.getOperatingSystem();
|
||||
if (OS == OperatingSystem.LINUX) {
|
||||
return getInstaller(NIMBUS);
|
||||
}
|
||||
else if (OS == OperatingSystem.MAC_OS_X) {
|
||||
return getInstaller(MAC);
|
||||
}
|
||||
else if (OS == OperatingSystem.WINDOWS) {
|
||||
return getInstaller(WINDOWS);
|
||||
}
|
||||
return getInstaller(NIMBUS);
|
||||
}
|
||||
|
||||
public boolean isSupported() {
|
||||
if (this == SYSTEM) {
|
||||
return true;
|
||||
}
|
||||
LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels();
|
||||
for (LookAndFeelInfo info : installedLookAndFeels) {
|
||||
if (name.equals(info.getName())) {
|
||||
@ -82,36 +64,43 @@ public enum LafType {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void install() throws Exception {
|
||||
getInstaller(this).install();
|
||||
public LookAndFeelManager getLookAndFeelManager() {
|
||||
return getManager(this);
|
||||
}
|
||||
|
||||
private static LookAndFeelInstaller getInstaller(LafType lookAndFeel) {
|
||||
private static LookAndFeelManager getManager(LafType lookAndFeel) {
|
||||
switch (lookAndFeel) {
|
||||
case FLAT_DARCULA:
|
||||
return new FlatLookAndFeelInstaller(FLAT_DARCULA);
|
||||
case FLAT_DARK:
|
||||
return new FlatLookAndFeelInstaller(FLAT_DARK);
|
||||
case FLAT_LIGHT:
|
||||
return new FlatLookAndFeelInstaller(FLAT_LIGHT);
|
||||
case GTK:
|
||||
return new GTKLookAndFeelInstaller();
|
||||
case MAC:
|
||||
return new LookAndFeelInstaller(MAC);
|
||||
case METAL:
|
||||
return new LookAndFeelInstaller(METAL);
|
||||
case MOTIF:
|
||||
return new MotifLookAndFeelInstaller(); // Motif has some specific ui fix ups
|
||||
case NIMBUS:
|
||||
return new NimbusLookAndFeelInstaller(); // Nimbus installs a special way
|
||||
case SYSTEM:
|
||||
return getSystemLookAndFeelInstaller();
|
||||
case WINDOWS:
|
||||
return new LookAndFeelInstaller(WINDOWS);
|
||||
case WINDOWS_CLASSIC:
|
||||
return new LookAndFeelInstaller(WINDOWS_CLASSIC);
|
||||
return new GenericLookAndFeelManager(lookAndFeel);
|
||||
case FLAT_DARCULA:
|
||||
case FLAT_DARK:
|
||||
case FLAT_LIGHT:
|
||||
return new GenericFlatLookAndFeelManager(lookAndFeel);
|
||||
case GTK:
|
||||
return new GtkLookAndFeelManager();
|
||||
case MOTIF:
|
||||
return new MotifLookAndFeelManager();
|
||||
case NIMBUS:
|
||||
return new NimbusLookAndFeelManager();
|
||||
default:
|
||||
throw new AssertException("No lookAndFeelInstaller defined for " + lookAndFeel);
|
||||
throw new AssertException("No lookAndFeelManager defined for " + lookAndFeel);
|
||||
}
|
||||
}
|
||||
|
||||
public static LafType getDefaultLookAndFeel() {
|
||||
OperatingSystem OS = Platform.CURRENT_PLATFORM.getOperatingSystem();
|
||||
switch (OS) {
|
||||
case MAC_OS_X:
|
||||
return MAC;
|
||||
case WINDOWS:
|
||||
return WINDOWS;
|
||||
case LINUX:
|
||||
case UNSUPPORTED:
|
||||
default:
|
||||
return NIMBUS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,5 +16,23 @@
|
||||
package generic.theme;
|
||||
|
||||
public interface ThemeListener {
|
||||
public void themeChanged(GTheme newTheme);
|
||||
public default void themeChanged(GTheme newTheme) {
|
||||
// default do nothing
|
||||
}
|
||||
|
||||
public default void colorChanged(String id) {
|
||||
// default do nothing
|
||||
}
|
||||
|
||||
public default void fontChanged(String id) {
|
||||
// default do nothing
|
||||
}
|
||||
|
||||
public default void iconChanged(String id) {
|
||||
// default do nothing
|
||||
}
|
||||
|
||||
public default void themeValuesRestored() {
|
||||
// default do nothing
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,10 @@ public class ThemePropertyFileReader {
|
||||
}
|
||||
}
|
||||
|
||||
protected ThemePropertyFileReader() {
|
||||
|
||||
}
|
||||
|
||||
ThemePropertyFileReader(String source, Reader reader) throws IOException {
|
||||
filePath = source;
|
||||
read(reader);
|
||||
@ -77,7 +81,7 @@ public class ThemePropertyFileReader {
|
||||
return errors;
|
||||
}
|
||||
|
||||
private void read(Reader reader) throws IOException {
|
||||
protected void read(Reader reader) throws IOException {
|
||||
List<Section> sections = readSections(new LineNumberReader(reader));
|
||||
for (Section section : sections) {
|
||||
switch (section.getName()) {
|
||||
@ -116,7 +120,9 @@ public class ThemePropertyFileReader {
|
||||
valueMap.addFont(parseFontProperty(key, value, lineNumber));
|
||||
}
|
||||
else if (IconValue.isIconKey(key)) {
|
||||
valueMap.addIcon(parseIconProperty(key, value));
|
||||
if (!FileGTheme.JAVA_ICON.equals(value)) {
|
||||
valueMap.addIcon(parseIconProperty(key, value));
|
||||
}
|
||||
}
|
||||
else {
|
||||
error(lineNumber, "Can't process property: " + key + " = " + value);
|
||||
|
@ -29,6 +29,10 @@ public class ThemeReader extends ThemePropertyFileReader {
|
||||
super(file);
|
||||
}
|
||||
|
||||
protected ThemeReader() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processNoSection(Section section) throws IOException {
|
||||
themeSection = section;
|
||||
@ -63,4 +67,7 @@ public class ThemeReader extends ThemePropertyFileReader {
|
||||
return lookAndFeel;
|
||||
}
|
||||
|
||||
public boolean useDarkDefaults() {
|
||||
return useDarkDefaults;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
/* ###
|
||||
* 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.io.*;
|
||||
import java.util.Set;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
public class ZipGTheme extends FileGTheme {
|
||||
|
||||
public ZipGTheme(File file, String name, LafType laf, boolean useDarkDefaults) {
|
||||
super(file, name, laf, useDarkDefaults);
|
||||
}
|
||||
|
||||
public ZipGTheme(File file) throws IOException {
|
||||
this(file, new ExternalThemeReader(file));
|
||||
}
|
||||
|
||||
public ZipGTheme(File file, ThemeReader reader) {
|
||||
super(file, reader.getThemeName(), reader.getLookAndFeelType(), reader.useDarkDefaults());
|
||||
reader.loadValues(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() throws IOException {
|
||||
String dir = getName() + ".theme/";
|
||||
try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||
ZipOutputStream zos = new ZipOutputStream(fos);
|
||||
saveThemeFileToZip(dir, zos);
|
||||
Set<File> iconFiles = getExternalIconFiles();
|
||||
for (File iconFile : iconFiles) {
|
||||
copyToZipFile(dir, iconFile, zos);
|
||||
}
|
||||
zos.finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void copyToZipFile(String dir, File iconFile, ZipOutputStream zos) throws IOException {
|
||||
ZipEntry entry = new ZipEntry(dir + "images/" + iconFile.getName());
|
||||
zos.putNextEntry(entry);
|
||||
Files.copy(iconFile, zos);
|
||||
}
|
||||
|
||||
private void saveThemeFileToZip(String dir, ZipOutputStream zos) throws IOException {
|
||||
ZipEntry entry = new ZipEntry(dir + getName() + ".theme");
|
||||
zos.putNextEntry(entry);
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(zos));
|
||||
writeThemeValues(writer);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
}
|
@ -21,7 +21,7 @@ import generic.theme.LafType;
|
||||
public class CDEMotifTheme extends DiscoverableGTheme {
|
||||
|
||||
public CDEMotifTheme() {
|
||||
super("Motif", LafType.MOTIF, false);
|
||||
super("Motif Theme", LafType.MOTIF, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,6 +20,6 @@ import generic.theme.LafType;
|
||||
|
||||
public class FlatDarculaTheme extends DiscoverableGTheme {
|
||||
public FlatDarculaTheme() {
|
||||
super("Flat Darcula", LafType.FLAT_DARCULA, true);
|
||||
super("Flat Darcula Theme", LafType.FLAT_DARCULA, true);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,6 @@ import generic.theme.LafType;
|
||||
|
||||
public class FlatDarkTheme extends DiscoverableGTheme {
|
||||
public FlatDarkTheme() {
|
||||
super("Flat Dark", LafType.FLAT_DARK, true);
|
||||
super("Flat Dark Theme", LafType.FLAT_DARK, true);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import generic.theme.LafType;
|
||||
public class FlatLightTheme extends DiscoverableGTheme {
|
||||
|
||||
public FlatLightTheme() {
|
||||
super("Flat Light", LafType.FLAT_LIGHT, false);
|
||||
super("Flat Light Theme", LafType.FLAT_LIGHT, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import generic.theme.LafType;
|
||||
public class GTKTheme extends DiscoverableGTheme {
|
||||
|
||||
public GTKTheme() {
|
||||
super("GTK+", LafType.GTK, false);
|
||||
super("GTK+ Theme", LafType.GTK, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,6 +21,6 @@ import generic.theme.LafType;
|
||||
public class MacTheme extends DiscoverableGTheme {
|
||||
|
||||
public MacTheme() {
|
||||
super("Mac OS X", LafType.MAC, false);
|
||||
super("Mac OS X Theme", LafType.MAC, false);
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import generic.theme.LafType;
|
||||
public class MetalTheme extends DiscoverableGTheme {
|
||||
|
||||
public MetalTheme() {
|
||||
super("Metal", LafType.METAL, false);
|
||||
super("Metal Theme", LafType.METAL, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import generic.theme.LafType;
|
||||
public class NimbusTheme extends DiscoverableGTheme {
|
||||
|
||||
public NimbusTheme() {
|
||||
super("Nimbus", LafType.NIMBUS, false);
|
||||
super("Nimbus Theme", LafType.NIMBUS, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,6 +21,6 @@ import generic.theme.LafType;
|
||||
public class WindowsClassicTheme extends DiscoverableGTheme {
|
||||
|
||||
public WindowsClassicTheme() {
|
||||
super("Windows Classic", LafType.WINDOWS_CLASSIC, false);
|
||||
super("Windows Classic Theme", LafType.WINDOWS_CLASSIC, false);
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,6 @@ import generic.theme.LafType;
|
||||
public class WindowsTheme extends DiscoverableGTheme {
|
||||
|
||||
public WindowsTheme() {
|
||||
super("Windows", LafType.WINDOWS, false);
|
||||
super("Windows Theme", LafType.WINDOWS, false);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package generic.theme.laf;
|
||||
|
||||
import generic.theme.LafType;
|
||||
|
||||
/**
|
||||
* Common {@link LookAndFeelInstaller} for any of the "Flat" lookAndFeels
|
||||
*/
|
||||
public class GenericFlatLookAndFeelManager extends LookAndFeelManager {
|
||||
|
||||
public GenericFlatLookAndFeelManager(LafType laf) {
|
||||
super(laf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LookAndFeelInstaller getLookAndFeelInstaller() {
|
||||
return new FlatLookAndFeelInstaller(getLookAndFeelType());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package generic.theme.laf;
|
||||
|
||||
import generic.theme.LafType;
|
||||
|
||||
/**
|
||||
* Generic {@link LookAndFeelManager} for lookAndFeels that do not require any special handling
|
||||
* to install or update
|
||||
*/
|
||||
public class GenericLookAndFeelManager extends LookAndFeelManager {
|
||||
|
||||
public GenericLookAndFeelManager(LafType laf) {
|
||||
super(laf);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LookAndFeelInstaller getLookAndFeelInstaller() {
|
||||
return new LookAndFeelInstaller(getLookAndFeelType());
|
||||
}
|
||||
|
||||
}
|
@ -15,13 +15,13 @@
|
||||
*/
|
||||
package generic.theme.laf;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.UnsupportedLookAndFeelException;
|
||||
|
||||
import generic.theme.LafType;
|
||||
|
||||
public class GTKLookAndFeelInstaller extends LookAndFeelInstaller {
|
||||
public class GtkLookAndFeelInstaller extends LookAndFeelInstaller {
|
||||
|
||||
public GTKLookAndFeelInstaller() {
|
||||
public GtkLookAndFeelInstaller() {
|
||||
super(LafType.GTK);
|
||||
}
|
||||
|
||||
@ -30,14 +30,12 @@ public class GTKLookAndFeelInstaller extends LookAndFeelInstaller {
|
||||
IllegalAccessException, UnsupportedLookAndFeelException {
|
||||
|
||||
super.installLookAndFeel();
|
||||
LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
|
||||
WrappingLookAndFeel wrappingLookAndFeel = new WrappingLookAndFeel(lookAndFeel);
|
||||
UIManager.setLookAndFeel(wrappingLookAndFeel);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void installJavaDefaults() {
|
||||
// handled by WrappingLookAndFeel
|
||||
}
|
||||
|
||||
// @Override
|
||||
// protected void installJavaDefaults() {
|
||||
// // GTK does not support changing its values, so set the javaDefaults to an empty map
|
||||
// Gui.setJavaDefaults(new GThemeValueMap());
|
||||
// }
|
||||
//
|
||||
}
|
@ -13,11 +13,19 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package generic.theme;
|
||||
package generic.theme.laf;
|
||||
|
||||
public class DefaultTheme extends DiscoverableGTheme {
|
||||
import generic.theme.LafType;
|
||||
|
||||
public DefaultTheme() {
|
||||
super("Default", LafType.SYSTEM, false);
|
||||
public class GtkLookAndFeelManager extends LookAndFeelManager {
|
||||
|
||||
public GtkLookAndFeelManager() {
|
||||
super(LafType.GTK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LookAndFeelInstaller getLookAndFeelInstaller() {
|
||||
return new GtkLookAndFeelInstaller();
|
||||
}
|
||||
|
||||
}
|
@ -54,7 +54,7 @@ public class LookAndFeelInstaller {
|
||||
* @throws UnsupportedLookAndFeelException if
|
||||
* <code>lnf.isSupportedLookAndFeel()</code> is false
|
||||
*/
|
||||
public void install() throws ClassNotFoundException, InstantiationException,
|
||||
public final void install() throws ClassNotFoundException, InstantiationException,
|
||||
IllegalAccessException, UnsupportedLookAndFeelException {
|
||||
cleanUiDefaults();
|
||||
installLookAndFeel();
|
||||
@ -88,8 +88,7 @@ public class LookAndFeelInstaller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs Colors, Fonts, and Icons into the UIDefaults. Subclasses my override this if they need to install
|
||||
* UI properties in a different way.
|
||||
* Extracts java default colors, fonts, and icons and stores them in {@link Gui}.
|
||||
*/
|
||||
protected void installJavaDefaults() {
|
||||
GThemeValueMap javaDefaults = extractJavaDefaults();
|
||||
@ -99,22 +98,37 @@ public class LookAndFeelInstaller {
|
||||
|
||||
private void installPropertiesBackIntoUiDefaults(GThemeValueMap javaDefaults) {
|
||||
UIDefaults defaults = UIManager.getDefaults();
|
||||
|
||||
GTheme theme = Gui.getActiveTheme();
|
||||
|
||||
// we replace java default colors with GColor equivalents so that we
|
||||
// can change colors without having to reinstall ui on each component
|
||||
// This trick only works for colors. Fonts and icons don't universally
|
||||
// allow being wrapped like colors do.
|
||||
for (ColorValue colorValue : javaDefaults.getColors()) {
|
||||
String id = colorValue.getId();
|
||||
GColorUIResource gColor = Gui.getGColorUiResource(id);
|
||||
defaults.put(id, gColor);
|
||||
}
|
||||
|
||||
// For fonts and icons we only want to install values that have been changed by
|
||||
// the theme
|
||||
for (FontValue fontValue : javaDefaults.getFonts()) {
|
||||
String id = fontValue.getId();
|
||||
//Note: fonts don't support indirect values, so there is no GFont object
|
||||
Font font = Gui.getFont(id);
|
||||
defaults.put(id, font);
|
||||
FontValue themeValue = theme.getFont(id);
|
||||
if (themeValue != null) {
|
||||
Font font = Gui.getFont(id);
|
||||
defaults.put(id, font);
|
||||
}
|
||||
}
|
||||
for (IconValue iconValue : javaDefaults.getIcons()) {
|
||||
String id = iconValue.getId();
|
||||
IconValue themeValue = theme.getIcon(id);
|
||||
if (themeValue != null) {
|
||||
Icon icon = Gui.getRawIcon(id, true);
|
||||
defaults.put(id, icon);
|
||||
}
|
||||
}
|
||||
// for (IconValue iconValue : javaDefaults.getIcons()) {
|
||||
// String id = iconValue.getId();
|
||||
// GIconUIResource gIcon = Gui.getGIconUiResource(id);
|
||||
// defaults.put(id, gIcon);
|
||||
// }
|
||||
}
|
||||
|
||||
protected GThemeValueMap extractJavaDefaults() {
|
||||
|
@ -0,0 +1,97 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package generic.theme.laf;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import generic.theme.*;
|
||||
|
||||
/**
|
||||
* Manages installing and updating a {@link LookAndFeel}
|
||||
*/
|
||||
public abstract class LookAndFeelManager {
|
||||
|
||||
private LafType laf;
|
||||
|
||||
protected LookAndFeelManager(LafType laf) {
|
||||
this.laf = laf;
|
||||
}
|
||||
|
||||
protected abstract LookAndFeelInstaller getLookAndFeelInstaller();
|
||||
|
||||
public LafType getLookAndFeelType() {
|
||||
return laf;
|
||||
}
|
||||
|
||||
public void installLookAndFeel() throws ClassNotFoundException, InstantiationException,
|
||||
IllegalAccessException, UnsupportedLookAndFeelException {
|
||||
|
||||
LookAndFeelInstaller installer = getLookAndFeelInstaller();
|
||||
installer.install();
|
||||
updateComponentUis();
|
||||
}
|
||||
|
||||
public void update() {
|
||||
GColor.refreshAll();
|
||||
GIcon.refreshAll();
|
||||
updateComponentUis();
|
||||
// repaintAll();
|
||||
}
|
||||
|
||||
public void updateColor(String id, Color color, boolean isJavaColor) {
|
||||
GColor.refreshAll();
|
||||
repaintAll();
|
||||
}
|
||||
|
||||
public void updateIcon(String id, Icon icon, boolean isJavaIcon) {
|
||||
// Icons are a mixed bag. Java Icons are direct and Ghidra Icons are indirect (to support static use)
|
||||
// Mainly because Nimbus is buggy and can't handle non-nimbus Icons, so we can't wrap them
|
||||
// So need to update UiDefaults for java icons. For Ghidra Icons, it is sufficient to refrech
|
||||
// GIcons and repaint
|
||||
if (isJavaIcon) {
|
||||
UIManager.getDefaults().put(id, icon);
|
||||
updateComponentUis();
|
||||
}
|
||||
GIcon.refreshAll();
|
||||
repaintAll();
|
||||
}
|
||||
|
||||
public void updateFont(String id, Font font, boolean isJavaFont) {
|
||||
if (isJavaFont) {
|
||||
UIManager.getDefaults().put(id, font);
|
||||
updateComponentUis();
|
||||
}
|
||||
else {
|
||||
repaintAll();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void updateComponentUis() {
|
||||
for (Window window : Window.getWindows()) {
|
||||
SwingUtilities.updateComponentTreeUI(window);
|
||||
}
|
||||
}
|
||||
|
||||
protected void repaintAll() {
|
||||
for (Window window : Window.getWindows()) {
|
||||
window.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package generic.theme.laf;
|
||||
|
||||
import generic.theme.LafType;
|
||||
|
||||
public class MotifLookAndFeelManager extends LookAndFeelManager {
|
||||
|
||||
public MotifLookAndFeelManager() {
|
||||
super(LafType.MOTIF);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LookAndFeelInstaller getLookAndFeelInstaller() {
|
||||
return new MotifLookAndFeelInstaller();
|
||||
}
|
||||
|
||||
}
|
@ -22,7 +22,6 @@ import javax.swing.*;
|
||||
import javax.swing.plaf.nimbus.NimbusLookAndFeel;
|
||||
|
||||
import generic.theme.*;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class NimbusLookAndFeelInstaller extends LookAndFeelInstaller {
|
||||
|
||||
@ -37,7 +36,7 @@ public class NimbusLookAndFeelInstaller extends LookAndFeelInstaller {
|
||||
|
||||
@Override
|
||||
protected void installJavaDefaults() {
|
||||
// do nothing - already handled by extended NimbusLookAndFeel
|
||||
// do nothing - already handled by installing extended NimbusLookAndFeel
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -60,9 +59,44 @@ public class NimbusLookAndFeelInstaller extends LookAndFeelInstaller {
|
||||
|
||||
@Override
|
||||
public UIDefaults getDefaults() {
|
||||
UIDefaults defaults = super.getDefaults();
|
||||
GThemeValueMap javaDefaults = extractJavaDefaults(defaults);
|
||||
|
||||
// need to set javaDefalts now to trigger building currentValues so the when
|
||||
// we create GColors below, they can be resolved.
|
||||
Gui.setJavaDefaults(javaDefaults);
|
||||
|
||||
// replace all colors with GColors
|
||||
for (ColorValue colorValue : javaDefaults.getColors()) {
|
||||
String id = colorValue.getId();
|
||||
defaults.put(id, Gui.getGColorUiResource(id));
|
||||
}
|
||||
|
||||
GTheme theme = Gui.getActiveTheme();
|
||||
|
||||
// only replace fonts that have been changed by the theme
|
||||
for (FontValue fontValue : theme.getFonts()) {
|
||||
String id = fontValue.getId();
|
||||
Font font = Gui.getFont(id);
|
||||
defaults.put(id, font);
|
||||
}
|
||||
|
||||
// only replace icons that have been changed by the theme
|
||||
for (IconValue iconValue : theme.getIcons()) {
|
||||
String id = iconValue.getId();
|
||||
Icon icon = Gui.getRawIcon(id, true);
|
||||
defaults.put(id, icon);
|
||||
}
|
||||
|
||||
defaults.put("Label.textForeground", Gui.getGColorUiResource("Label.foreground"));
|
||||
GColor.refreshAll();
|
||||
GIcon.refreshAll();
|
||||
return defaults;
|
||||
}
|
||||
|
||||
private GThemeValueMap extractJavaDefaults(UIDefaults defaults) {
|
||||
GThemeValueMap javaDefaults = new GThemeValueMap();
|
||||
|
||||
UIDefaults defaults = super.getDefaults();
|
||||
List<String> colorIds =
|
||||
LookAndFeelInstaller.getLookAndFeelIdsForType(defaults, Color.class);
|
||||
for (String id : colorIds) {
|
||||
@ -79,31 +113,12 @@ public class NimbusLookAndFeelInstaller extends LookAndFeelInstaller {
|
||||
}
|
||||
List<String> iconIds =
|
||||
LookAndFeelInstaller.getLookAndFeelIdsForType(defaults, Icon.class);
|
||||
Msg.debug(LookAndFeelInstaller.class, "Icons found: " + iconIds.size());
|
||||
for (String id : iconIds) {
|
||||
Icon icon = defaults.getIcon(id);
|
||||
javaDefaults.addIcon(new IconValue(id, icon));
|
||||
}
|
||||
|
||||
Gui.setJavaDefaults(javaDefaults);
|
||||
for (String id : colorIds) {
|
||||
defaults.put(id, Gui.getGColorUiResource(id));
|
||||
}
|
||||
// for (String id : iconIds) {
|
||||
// GIconUIResource icon = Gui.getGIconUiResource(id);
|
||||
// if (icon.getId().equals("Menu.arrowIcon")) {
|
||||
// defaults.put(id, new IconWrappedImageIcon(Gui.getRawIcon(id, false)));
|
||||
// }
|
||||
// else {
|
||||
// defaults.put(id, Gui.getGIconUiResource(id));
|
||||
// }
|
||||
// }
|
||||
|
||||
// javaDefaults.addColor(new ColorValue("Label.textForground", "Label.foreground"));
|
||||
defaults.put("Label.textForeground", Gui.getGColorUiResource("Label.foreground"));
|
||||
GColor.refreshAll();
|
||||
GIcon.refreshAll();
|
||||
return defaults;
|
||||
return javaDefaults;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,102 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package generic.theme.laf;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import generic.theme.LafType;
|
||||
|
||||
public class NimbusLookAndFeelManager extends LookAndFeelManager {
|
||||
private UIDefaults overrides = new UIDefaults();
|
||||
|
||||
public NimbusLookAndFeelManager() {
|
||||
super(LafType.NIMBUS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LookAndFeelInstaller getLookAndFeelInstaller() {
|
||||
return new NimbusLookAndFeelInstaller();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateColor(String id, Color color, boolean isJavaColor) {
|
||||
super.updateColor(id, color, isJavaColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFont(String id, Font font, boolean isJavaFont) {
|
||||
if (isJavaFont) {
|
||||
overrides.put(id, font);
|
||||
updateNimbusOverrides();
|
||||
}
|
||||
repaintAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateIcon(String id, Icon icon, boolean isJavaIcon) {
|
||||
if (isJavaIcon) {
|
||||
overrides.put(id, icon);
|
||||
updateNimbusOverrides();
|
||||
}
|
||||
repaintAll();
|
||||
}
|
||||
|
||||
private void updateNimbusOverrides() {
|
||||
UIDefaults defaults = getNimbusOverrides();
|
||||
for (Window window : Window.getWindows()) {
|
||||
updateNimbusUI(window, defaults);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNimbusUI(Component c, UIDefaults defaults) {
|
||||
updateNimbusUIComp(c, defaults);
|
||||
c.invalidate();
|
||||
c.validate();
|
||||
c.repaint();
|
||||
}
|
||||
|
||||
private UIDefaults getNimbusOverrides() {
|
||||
UIDefaults defaults = new UIDefaults();
|
||||
defaults.putAll(overrides);
|
||||
return defaults;
|
||||
}
|
||||
|
||||
private void updateNimbusUIComp(Component c, UIDefaults defaults) {
|
||||
if (c instanceof JComponent) {
|
||||
JComponent jc = (JComponent) c;
|
||||
jc.putClientProperty("Nimbus.Overrides", defaults);
|
||||
JPopupMenu jpm = jc.getComponentPopupMenu();
|
||||
if (jpm != null) {
|
||||
updateNimbusUI(jpm, defaults);
|
||||
}
|
||||
}
|
||||
Component[] children = null;
|
||||
if (c instanceof JMenu) {
|
||||
children = ((JMenu) c).getMenuComponents();
|
||||
}
|
||||
else if (c instanceof Container) {
|
||||
children = ((Container) c).getComponents();
|
||||
}
|
||||
if (children != null) {
|
||||
for (Component child : children) {
|
||||
updateNimbusUIComp(child, defaults);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package generic.theme.laf;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import generic.theme.*;
|
||||
|
||||
public class WrappingLookAndFeel extends LookAndFeel {
|
||||
private LookAndFeel delegate;
|
||||
|
||||
WrappingLookAndFeel(LookAndFeel delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UIDefaults getDefaults() {
|
||||
GThemeValueMap javaDefaults = new GThemeValueMap();
|
||||
|
||||
UIDefaults defaults = delegate.getDefaults();
|
||||
List<String> colorIds =
|
||||
LookAndFeelInstaller.getLookAndFeelIdsForType(defaults, Color.class);
|
||||
for (String id : colorIds) {
|
||||
Color color = defaults.getColor(id);
|
||||
ColorValue value = new ColorValue(id, color);
|
||||
javaDefaults.addColor(value);
|
||||
}
|
||||
Gui.setJavaDefaults(javaDefaults);
|
||||
for (String id : colorIds) {
|
||||
defaults.put(id, Gui.getGColorUiResource(id));
|
||||
// defaults.put(id, new GColor(id));
|
||||
}
|
||||
defaults.put("Label.textForeground", Gui.getGColorUiResource("Label.foreground"));
|
||||
GColor.refreshAll();
|
||||
GIcon.refreshAll();
|
||||
return defaults;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return delegate.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getID() {
|
||||
return delegate.getID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return delegate.getDescription();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNativeLookAndFeel() {
|
||||
return delegate.isNativeLookAndFeel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupportedLookAndFeel() {
|
||||
return delegate.isSupportedLookAndFeel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LayoutStyle getLayoutStyle() {
|
||||
return delegate.getLayoutStyle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideErrorFeedback(Component component) {
|
||||
delegate.provideErrorFeedback(component);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getDisabledIcon(JComponent component, Icon icon) {
|
||||
return delegate.getDisabledIcon(component, icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getDisabledSelectedIcon(JComponent component, Icon icon) {
|
||||
return delegate.getDisabledSelectedIcon(component, icon);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getSupportsWindowDecorations() {
|
||||
return delegate.getSupportsWindowDecorations();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
delegate.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninitialize() {
|
||||
delegate.uninitialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Wrapped: " + delegate.toString();
|
||||
}
|
||||
}
|
@ -47,7 +47,7 @@ import utility.module.ModuleUtilities;
|
||||
* as opposed to using the flawed constructor {@link ImageIcon#ImageIcon(Image)}.
|
||||
*/
|
||||
public class ResourceManager {
|
||||
|
||||
public final static String EXTERNAL_ICON_PREFIX = "[EXTERNAL]";
|
||||
private final static String DEFAULT_ICON_FILENAME = Images.BOMB;
|
||||
private static ImageIcon DEFAULT_ICON;
|
||||
private static Map<String, ImageIcon> iconMap = new HashMap<>();
|
||||
@ -525,46 +525,50 @@ public class ResourceManager {
|
||||
return icons;
|
||||
}
|
||||
|
||||
private static ImageIcon doLoadIcon(String filename) {
|
||||
private static ImageIcon doLoadIcon(String path) {
|
||||
|
||||
// if the has the "external prefix", it is an icon in the user's application directory
|
||||
if (path.startsWith(EXTERNAL_ICON_PREFIX)) {
|
||||
String relativePath = path.substring(EXTERNAL_ICON_PREFIX.length());
|
||||
File dir = Application.getUserSettingsDirectory();
|
||||
File iconFile = new File(dir, relativePath);
|
||||
if (iconFile.exists()) {
|
||||
try {
|
||||
return new UrlImageIcon(path, iconFile.toURI().toURL());
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
// handled below
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// if only the name of an icon is given, but not a path, check to see if it is
|
||||
// a resource that lives under our "images/" folder
|
||||
if (!filename.contains("/")) {
|
||||
URL url = getResource("images/" + filename);
|
||||
if (!path.contains("/")) {
|
||||
URL url = getResource("images/" + path);
|
||||
if (url != null) {
|
||||
return new UrlImageIcon(filename, url);
|
||||
return new UrlImageIcon(path, url);
|
||||
}
|
||||
}
|
||||
|
||||
// look for it directly with the given path
|
||||
URL url = getResource(filename);
|
||||
URL url = getResource(path);
|
||||
if (url != null) {
|
||||
return new UrlImageIcon(filename, url);
|
||||
return new UrlImageIcon(path, url);
|
||||
}
|
||||
|
||||
// try using the filename as a file path
|
||||
File imageFile = new File(filename);
|
||||
File imageFile = new File(path);
|
||||
if (imageFile.exists()) {
|
||||
try {
|
||||
return new UrlImageIcon(filename, imageFile.toURI().toURL());
|
||||
return new UrlImageIcon(path, imageFile.toURI().toURL());
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
// handled below
|
||||
}
|
||||
}
|
||||
|
||||
// try to see if is an icon in the users application directory
|
||||
File dir = Application.getUserSettingsDirectory();
|
||||
File iconFile = new File(dir, filename);
|
||||
if (iconFile.exists()) {
|
||||
try {
|
||||
return new UrlImageIcon(filename, iconFile.toURI().toURL());
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
// handled below
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ public class GThemeTest extends AbstractGenericTest {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
theme = new DefaultTheme();
|
||||
theme = Gui.getDefaultTheme();
|
||||
new Font("Courier", Font.BOLD, 12);
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ public class GThemeTest extends AbstractGenericTest {
|
||||
theme = new FileGTheme(file);
|
||||
|
||||
assertEquals("abc", theme.getName());
|
||||
assertEquals(LafType.SYSTEM, theme.getLookAndFeelType());
|
||||
assertEquals(LafType.getDefaultLookAndFeel(), theme.getLookAndFeelType());
|
||||
|
||||
assertEquals(Color.RED, theme.getColor("color.a.1").get(theme));
|
||||
assertEquals(Color.BLUE, theme.getColor("color.a.2").get(theme));
|
||||
|
@ -17,6 +17,7 @@ package ghidra.app.plugin.gui;
|
||||
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.theme.gui.ThemeDialog;
|
||||
import docking.theme.gui.ThemeUtils;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.framework.main.ApplicationLevelOnlyPlugin;
|
||||
import ghidra.framework.main.UtilityPluginPackage;
|
||||
@ -41,15 +42,42 @@ public class ThemeManagerPlugin extends Plugin implements ApplicationLevelOnlyPl
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
String owner = getName();
|
||||
String themeSubMenu = "Theme Actions";
|
||||
|
||||
new ActionBuilder("", getName()).menuPath("Edit", "Theme")
|
||||
.onAction(e -> showThemeProperties())
|
||||
String group = "theme";
|
||||
new ActionBuilder("Edit Theme", owner)
|
||||
.menuPath("Edit", "Theme")
|
||||
.menuGroup(group, "1")
|
||||
.onAction(e -> ThemeDialog.editTheme())
|
||||
.buildAndInstall(tool);
|
||||
|
||||
}
|
||||
new ActionBuilder("Reset To Default", owner)
|
||||
.menuPath("Edit", themeSubMenu, "Reset To Default")
|
||||
.menuGroup(group, "2")
|
||||
.onAction(e -> ThemeUtils.resetThemeToDefault())
|
||||
.buildAndInstall(tool);
|
||||
|
||||
private void showThemeProperties() {
|
||||
ThemeDialog.editTheme();
|
||||
}
|
||||
new ActionBuilder("Import Theme", owner)
|
||||
.menuPath("Edit", themeSubMenu, "Import...")
|
||||
.menuGroup(group, "3")
|
||||
.onAction(e -> ThemeUtils.importTheme())
|
||||
.buildAndInstall(tool);
|
||||
|
||||
new ActionBuilder("Export Theme", owner)
|
||||
.menuPath("Edit", themeSubMenu, "Export...")
|
||||
.menuGroup(group, "4")
|
||||
.onAction(e -> ThemeUtils.exportTheme())
|
||||
.buildAndInstall(tool);
|
||||
|
||||
new ActionBuilder("Delete Theme", owner)
|
||||
.menuPath("Edit", themeSubMenu, "Delete...")
|
||||
.menuGroup(group, "5")
|
||||
// .enabledWhen(e -> Gui.getActiveTheme() instanceof FileGTheme)
|
||||
.onAction(e -> ThemeUtils.deleteTheme())
|
||||
.buildAndInstall(tool);
|
||||
|
||||
tool.setMenuGroup(new String[] { "Edit", themeSubMenu }, group, "2");
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user