GP-2934 cleaning up user interface for Theming

This commit is contained in:
ghidragon 2023-01-19 17:24:05 -05:00
parent 942d60eeac
commit 0bf0911e11
44 changed files with 720 additions and 253 deletions

View File

@ -33,7 +33,7 @@ public class PickProviderDialog extends DialogComponentProvider {
private static String lastSelectedProviderDescription;
private List<GhidraScriptProvider> providers;
private ListPanel listPanel;
private ListPanel<GhidraScriptProvider> listPanel;
private JComponent parent;
private boolean wasCancelled;
@ -61,11 +61,12 @@ public class PickProviderDialog extends DialogComponentProvider {
* @param testItems values to populate model with
* @param defaultItem the default selection
*/
public PickProviderDialog(List<String> testItems, String defaultItem) {
public PickProviderDialog(List<GhidraScriptProvider> testItems,
GhidraScriptProvider defaultItem) {
super("New Script: Type");
DefaultListModel<String> listModel = new DefaultListModel<>();
for (String item : testItems) {
DefaultListModel<GhidraScriptProvider> listModel = new DefaultListModel<>();
for (GhidraScriptProvider item : testItems) {
listModel.addElement(item);
}
@ -143,8 +144,8 @@ public class PickProviderDialog extends DialogComponentProvider {
close();
}
private JPanel buildWorkPanel(DefaultListModel<?> listModel) {
listPanel = new ListPanel();
private JPanel buildWorkPanel(DefaultListModel<GhidraScriptProvider> listModel) {
listPanel = new ListPanel<>();
listPanel.setListModel(listModel);
listPanel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
listPanel.setSelectedIndex(0);

View File

@ -42,7 +42,7 @@ public class SaveDialog extends DialogComponentProvider implements ListSelection
private GhidraScriptProvider provider;
private List<ResourceFile> paths;
private ListPanel listPanel;
private ListPanel<ResourceFile> listPanel;
private JTextField nameField;
private boolean cancelled;
@ -113,7 +113,7 @@ public class SaveDialog extends DialogComponentProvider implements ListSelection
listModel.addElement(dir);
}
listPanel = new ListPanel();
listPanel = new ListPanel<>();
listPanel.setName("PATH_LIST");
listPanel.setListModel(listModel);
listPanel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

View File

@ -19,12 +19,14 @@ src/main/help/help/topics/Theming/ThemingUserDocs.html||GHIDRA||||END|
src/main/help/help/topics/Theming/images/ColorEditor.png||GHIDRA||||END|
src/main/help/help/topics/Theming/images/FontEditor.png||GHIDRA||||END|
src/main/help/help/topics/Theming/images/IconEditor.png||GHIDRA||||END|
src/main/help/help/topics/Theming/images/ThemeDialog.png||GHIDRA||||END|
src/main/help/help/topics/Theming/images/ThemeChooserDialog.png||GHIDRA||||END|
src/main/help/help/topics/Theming/images/ThemeEditorDialog.png||GHIDRA||||END|
src/main/java/docking/dnd/package.html||GHIDRA||reviewed||END|
src/main/java/docking/options/editor/package.html||GHIDRA||reviewed||END|
src/main/java/docking/widgets/fieldpanel/package.html||GHIDRA||reviewed||END|
src/main/java/docking/widgets/filechooser/package.html||GHIDRA||reviewed||END|
src/main/java/docking/wizard/package.html||GHIDRA||reviewed||END|
src/main/resources/images/Minus.png||GHIDRA||||END|
src/main/resources/images/Plus.png||GHIDRA||reviewed||END|
src/main/resources/images/StackFrameElement.png||GHIDRA||reviewed||END|
src/main/resources/images/StackFrame_Red.png||GHIDRA||reviewed||END|
@ -55,6 +57,7 @@ src/main/resources/images/expand.gif||GHIDRA||||END|
src/main/resources/images/filter_off.png||GHIDRA||||END|
src/main/resources/images/filter_on.png||GHIDRA||||END|
src/main/resources/images/folder_add.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/font.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/hourglass.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/hourglass24_01.png||GHIDRA||reviewed||END|
src/main/resources/images/hourglass24_02.png||GHIDRA||reviewed||END|

View File

@ -72,6 +72,11 @@ icon.window = application_xp.png
icon.zoom.in = zoom_in.png
icon.zoom.out = zoom_out.png
icon.theme.import = mail-receive.png
icon.theme.export = mail-folder-outbox.png
icon.theme.font.increment = font.png{Plus.png[size(8,8)][move(8,8)]}
icon.theme.font.decrement = font.png{Minus.png[size(8,8)][move(8,8)]}
icon.docking.application.home = www_16.png
icon.docking.application.16 = www_16.png
icon.docking.application.128 = www_128.png

View File

@ -10,13 +10,14 @@
</HEAD>
<BODY>
<H1 align="center">Editing Themes</H1>
<H1 align="center">Themes</H1>
<H2>Description</H2>
<BlOCKQUOTE>
<P>The Theming feature allows users to customize the colors, fonts, and icons used throughout
the application. The active theme determines the Java Look and Feel, whether the theme should use
light or dark defaults, and any custom colors, fonts, and icons that override the default
the application. The active theme determines the Java Look and Feel, whether the theme should
use light or dark defaults, and any custom colors, fonts, and icons that override the default
values. Users can can easily switch between any of the built-in themes or any saved themes found
in their home application directory</P>
@ -26,22 +27,53 @@
easily be modified using any text editor. Also, users can share themes by exporting them to a
file that can be given to other users who can them import them into their application.</P>
<H2>Theme Dialog<A name="Edit_Theme"></A></H2>
</BlOCKQUOTE>
<H2>Theme Chooser Dialog<A name="Switch_Theme"></A></H2>
<BlOCKQUOTE>
<P>The Theme Chooser allows users to switch themes.
<P>The Theme Dialog is the primary means for creating, editing, and saving themes.</P>
<P align="center"><IMG alt="" src="images/ThemeDialog.png"><BR>
<P align="center"><IMG alt="" src="images/ThemeChooserDialog.png"><BR>
&nbsp;</P>
<P>The Theme Dialog consists of a theme drop-down and a tabbed set of tables that display the
values for every color property, font property, and icon property defined by either the Java
Look and Feel or the application. All application defined properties start with "color.",
"font.", or "icon.", depending on whether the the property is a color, font, or icon
respectively. All other properties are defined by the Java Look and Feel. This naming
convention is not enforced, thus it is possible that 3rd-party Extensions may introduce
property names that do not match this description. See
the <A href="ThemingDeveloperDocs.html#Resource_Ids">Developer Documentation</A> for more details on the property ID
format and naming conventions.
<P>The Theme Chooser dialog displays a list of all the know themes, both built-in and custom.
As you pick different themes, the application will switch to that theme. Press the "OK" button
when the desired theme is selected. Pressing the "Cancel" button will restore the theme to
the what it was when the dialog was invoked.</P>
</BlOCKQUOTE>
<H2>Theme Editor Dialog<A name="Edit_Theme"></A></H2>
<BlOCKQUOTE>
<P>The Theme Editor Dialog is the primary means for creating and editing themes.</P>
<P align="center"><IMG alt="" src="images/ThemeEditorDialog.png"><BR>
&nbsp;</P>
<P>The Theme Editor Dialog consists of a Look And Feel drop-down and a tabbed set of tables that
display the values for every color property, font property, and icon property defined by either
the Java look and feel or the application. The properties fall into one of the following
groups</P>
<UL>
<LI>Application Properties - these are application defined properties. They all start with
either "color.", "font.", or "icon.", depending on whether the property is a color, font,or
icon respectively.</LI>
<LI>System Properties - these are well defined Look and Feel concepts such as various
standard background and foreground colors, border color, and fonts. These all start with
"system." and their values are mapped to specific values for each Java Look and Feel.</LI>
<LI>Look and Feel Properties - these are the properties defined by the current Java
Look and Feel. Ghidra prepends these properties with prefixes that start with either
"laf.color.", "laf.font"., or "laf.icon." For example, if the Look and Feel defines a property
called "Button.background", it would appear in Ghidra as "laf.color.Button.background".</LI>
<LI>Look and Feel Palette Properties - All the color and fonts used by the Look and Feel
properties are grouped into either system property values or auto-generated palette
properties, sso that groups of properties can be changed together.</LI>
</UL>
<P>See the <A href="ThemingDeveloperDocs.html#Resource_Ids">Developer Documentation</A> for more
details on the property ID format and naming conventions.
</P>
<P>Each table entry shows the property ID string, the current value, the theme value,
@ -63,8 +95,14 @@
current theme is a built-in theme, you will first have to supply a new theme name. If the
current theme is a not a built-in theme, you will have the option to overwrite the existing
theme or supplying a new name to save it as a new theme.</P>
<H3>Theme Editor actions</H3>
<UL>
<LI><A name="Increment_Fonts"></A><IMG border="0" src="icon.theme.font.increment" >&nbsp;Increment All Fonts - Increases all fonts in the system by one.</LI>
<LI><A name="Decrement_Fonts"></A><IMG border="0" src="icon.theme.font.decrement" >&nbsp;Decrement All Fonts - Decreases all fonts in the system by one.</LI>
<LI><A name="Reload_Theme"></A><IMG border="0" src="icon.refresh" >&nbsp;Reload Theme - Restores all color, font, and icon values back to the original theme values.</LI>
</UL>
<BLOCKQUOTE>
<H3>Color Editor</H3>
<P>When you double-click on a color value, the Edit Color dialog appears.</P>
@ -107,87 +145,95 @@
<BLOCKQUOTE>
<H3>Switching Themes</H3>
<P>To change the current theme, first bring up the <A href="#Edit_Theme">Theme Dialog</A>.
The Theme Dialog can be invoked from the main application menu using the
<B>Edit</B><IMG alt="" src="help/shared/arrow.gif" border="0"><B>Theme</B> menu. From the Theme
Dialog you can select a theme from the combo box at the top.</P>
<P>To change the current theme, invoke the
<B>Edit<IMG alt="" src="help/shared/arrow.gif" border="0">Theme<IMG alt=""
src="help/shared/arrow.gif" border="0">Change..." </b>menu option
from the main project window. This will bring up the <A href="#Switch_Theme">
Theme Chooser Dialog </A>which allows you to pick a new theme.</P>
<H3>Modifying Theme Values</H3>
<P>All the colors, fonts, and icons that have been registered with the theme API can be
modified using the <A href="#Edit_Theme">Theme Dialog</A>. The Theme Dialog can be invoked
from the main application menu using the
<B>Edit</B><IMG alt="" src="help/shared/arrow.gif" border="0"><B>Theme</B> menu. Choose the
modified using the <A href="#Edit_Theme">Theme Editor Dialog</A>. The Theme Editor Dialog
can be invoked from the main application menu using the
<B>Edit<IMG alt="" src="help/shared/arrow.gif" border="0">Theme<IMG alt=""
src="help/shared/arrow.gif" border="0">Configure..." </b> menu. Choose the
tab for the appropriate type and double-click on the ID column or Current Value column of the
item you want to change. An editor for that type will appear.</P>
<H3>Creating New Themes<A NAME="New_Theme"></A></H3>
<P>To create a new theme, invoke <B>Edit<IMG alt="" src="help/shared/arrow.gif"
border="0">Theme<IMG alt=""
src="help/shared/arrow.gif" border="0">New..." </b>menu option. This will bring up a new
dialog where you can enter the new theme name and select another theme as a starting point.
When the <B>OK</B> button is pressed, the Theme Editor Dialog will appear, allowing you
to begin making changes to theme values.</P>
<H3>Reseting Theme Values<A NAME="Restore_Value"></A></H3>
<P>To reset an individual value back to its original theme value, from the
main application menu invoke the <A href=
"#Edit_Theme">Theme Dialog</A> using the <B>Edit</B> <IMG alt="" src="help/shared/arrow.gif"
border="0"><B>Theme</B> menu. Choose the
tab for the appropriate type and right-click on the row of the value you want to reset, then
choose the <B>Restore Value</B> menu item.</P>
<P>To reset an individual value back to its original theme value, invoke the <A href=
"#Edit_Theme">Theme Editor Dialog</A>. Choose the tab for the appropriate type and
right-click the one the row in the able you want to reset, then choose the
<B>Restore Value</B> menu item from the popup menu.</P>
<H3>Reseting All Theme Values<A name="Reset_Theme_Values"></A></H3>
<P>To reset all values back to the original values established by the current theme, from the
main application menu invoke
the <B>Edit</B><IMG alt="" src="help/shared/arrow.gif" border="0"><B>Theme Actions</B> <IMG
alt="" src="help/shared/arrow.gif" border="0"><B>Reset Theme Values</B> menu.</P>
<P>To reset all values back to the original values established by the current theme,
invoke the <A href= "#Edit_Theme">Theme Editor Dialog</A>. To activate this
action, press the refresh button <IMG alt="" src="images/reload3.png" border="0"> in the top
right corner of the Theme Editor dialog.
<H3>Saving Themes</H3>
<P>After making changes to one or more theme values, the <A href="#Edit_Theme">Theme
Dialog's</A> <B>Save</B> button will be enabled. Pressing the <B>Save</B> button will give
Editor Dialog's</A> <B>Save</B> button will be enabled. Pressing the <B>Save</B> button will give
the user the option of creating a new theme or overwriting the current them (if the current
theme is not a built-in theme). Also, users will have the option of saving a theme if they
dismiss the Theme Dialog while there are changes to one or more theme values.</P>
<H3>Deleting Themes<A name="Delete_Theme"></A></H3>
<P>To delete a custom saved theme, from the main application menu invoke the
<P>Only custom non-built-in themes can be deleted. To delete a custom theme, invoke the
<B>Edit</B><IMG alt="" src=
"help/shared/arrow.gif" border="0"><B>Theme Actions</B> <IMG alt="" src=
"help/shared/arrow.gif" border="0"><B>Delete Theme...</B> menu. This will bring up a dialog
"help/shared/arrow.gif" border="0"><B>Theme</B> <IMG alt="" src=
"help/shared/arrow.gif" border="0"><B>Delete...</B> menu from the main application
menu. This will bring up a dialog
with a list of themes that can be deleted. Select the theme to delete and press the <B>Ok</B>
button.</P>
<H3>Exporting Themes&gt;<A name="Export_Theme"></A></H3>
<H3>Exporting Themes<A name="Export_Theme"></A></H3>
<P>To export a theme so that it can be shared with others, from the
main application menu invoke the <B>Edit</B> <IMG alt=""
src="help/shared/arrow.gif" border="0"><B>Theme Actions</B> <IMG alt="" src=
"help/shared/arrow.gif" border="0"><B>Export Theme...</B> menu. You will first be asked if
<P>To export a theme so that it can be shared with others, invoke the <B>Edit</B> <IMG alt=""
src="help/shared/arrow.gif" border="0"><B>Theme</B> <IMG alt="" src=
"help/shared/arrow.gif" border="0"><B>Export..</B> menu from the main application
window. You will first be asked if
you want to export as a regular theme file or as a Zip file. The Zip file option is useful if
the current theme has icon values that are not included with standard application. In that case,
the Zip file will include those non-standard icon files.</P>
<H3>Importing Themes<A name="Import_Theme"></A></H3>
<P>To import a theme, from the main application menu
<P>To import a theme,
invoke the <B>Edit</B> <IMG alt="" src="help/shared/arrow.gif" border=
"0"><B>Theme Actions</B> <IMG alt="" src="help/shared/arrow.gif" border="0"><B>Import
Theme...</B> menu. A file chooser dialog will appear allowing the user to choose a theme file
"0"><B>Theme</B> <IMG alt="" src="help/shared/arrow.gif" border="0"><B>Import...</B>
menu from the main application menu. A file chooser dialog will appear
allowing the user to choose a theme file
to import. The selected file can be either a standard theme file or a Zip file containing the
theme file and any non-standard icon files defined by that theme.</P>
<H3>Reloading Default Values<A NAME="Reload_Ghidra_Defaults"></A></H3>
<P>This action causes Ghidra to reload all theme default values. It is really only useful
for developers who are actively making changes to theme.properties files. To activate this
action, press the refresh button <IMG alt="" src="images/reload3.png" border="0"> in the top
right corner of the <A href="#Edit_Theme">Theme Dialog</A>.</P>
</BLOCKQUOTE>
<H2>Theme Property Names</H2>
<BlOCKQUOTE>
<P>Theme Property Names (also referred to as IDs or keys) that are defined by the application
use a common format to help make sorting and viewing properties more intuitive as to their use. See
the <A href="ThemingDeveloperDocs.html#Resource_Ids">Developer Documentation</A> for more details on the property ID
format and naming conventions.</P>
</BlOCKQUOTE>
<H2>Theme Files</H2>
<BLOCKQUOTE>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -311,9 +311,8 @@ public class SplashScreen extends JWindow {
Gui.registerFont(statusLabel, FONT_ID);
statusLabel.setFont(Gui.getFont(FONT_ID));
CompoundBorder border =
BorderFactory.createCompoundBorder(BorderFactory.createLoweredBevelBorder(),
BorderFactory.createEmptyBorder(0, 5, 2, 5));
CompoundBorder border = BorderFactory.createCompoundBorder(
BorderFactory.createLoweredBevelBorder(), BorderFactory.createEmptyBorder(0, 5, 2, 5));
statusLabel.setBorder(border);
statusLabel.setOpaque(true);
statusLabel.setBackground(Colors.BACKGROUND);

View File

@ -199,6 +199,7 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel<ColorValue, O
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
JLabel label = (JLabel) super.getTableCellRendererComponent(data);
setFont(Gui.getFont("font.monospaced"));
ResolvedColor resolved = (ResolvedColor) data.getValue();
String text = getValueText(resolved);

View File

@ -19,7 +19,6 @@ import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.*;
import java.util.*;
import java.util.stream.Collectors;
import javax.swing.*;
@ -35,12 +34,11 @@ import ghidra.util.*;
/**
* Primary dialog for editing Themes.
*/
public class ThemeDialog extends DialogComponentProvider {
private static ThemeDialog INSTANCE;
public class ThemeEditorDialog extends DialogComponentProvider {
private static ThemeEditorDialog INSTANCE;
private JButton saveButton;
private JButton restoreButton;
private GhidraComboBox<String> combo;
private GhidraComboBox<LafType> combo;
private ItemListener comboListener = this::themeComboChanged;
private ThemeListener listener = new DialogThemeListener();
private JTabbedPane tabbedPane;
@ -53,14 +51,13 @@ public class ThemeDialog extends DialogComponentProvider {
private GThemeValuesCache valuesCache;
public ThemeDialog(ThemeManager themeManager) {
super("Theme Dialog", false);
public ThemeEditorDialog(ThemeManager themeManager) {
super("Configure Theme: " + themeManager.getActiveTheme().getName(), false);
this.themeManager = themeManager;
addWorkPanel(createMainPanel());
addDismissButton();
addButton(createSaveButton());
addButton(createRestoreButton());
setPreferredSize(1100, 500);
setRememberSize(false);
@ -71,10 +68,30 @@ public class ThemeDialog extends DialogComponentProvider {
}
private void createActions() {
DockingAction reloadDefaultsAction = new ActionBuilder("Reload Theme Defaults", getTitle())
DockingAction incrementFontsAction = new ActionBuilder("Increment All Fonts", getTitle())
.toolBarIcon(new GIcon("icon.theme.font.increment"))
.description("Increases all font sizes by 1")
.helpLocation(new HelpLocation("Theming", "Increment_Fonts"))
.onAction(e -> adjustFonts(1))
.build();
addAction(incrementFontsAction);
DockingAction decrementFontsAction = new ActionBuilder("Decrement All Fonts", getTitle())
.toolBarIcon(new GIcon("icon.theme.font.decrement"))
.toolBarGroup("A")
.description("Decreases all font sizes by 1")
.helpLocation(new HelpLocation("Theming", "Decrement_Fonts"))
.onAction(e -> adjustFonts(-1))
.build();
addAction(decrementFontsAction);
DockingAction reloadDefaultsAction = new ActionBuilder("Restore Theme Values", getTitle())
.toolBarIcon(new GIcon("icon.refresh"))
.helpLocation(new HelpLocation("Theming", "Reload_Ghidra_Defaults"))
.onAction(e -> reloadDefaultsCallback())
.toolBarGroup("B")
.description("Reloads default values and restores theme to its original values.")
.helpLocation(new HelpLocation("Theming", "Reload_Theme"))
.onAction(e -> restoreCallback())
.build();
addAction(reloadDefaultsAction);
@ -89,10 +106,13 @@ public class ThemeDialog extends DialogComponentProvider {
addAction(resetValueAction);
}
private void adjustFonts(int amount) {
themeManager.adjustFonts(amount);
}
@Override
protected void dismissCallback() {
if (handleChanges()) {
INSTANCE = null;
close();
}
}
@ -118,7 +138,7 @@ public class ThemeDialog extends DialogComponentProvider {
private void restoreCallback() {
if (themeManager.hasThemeChanges()) {
int result = OptionDialog.showYesNoDialog(null, "Discard Theme Changes?",
int result = OptionDialog.showYesNoDialog(null, "Restore Theme Values?",
"This will discard all of your theme changes. Continue?");
if (result != OptionDialog.YES_OPTION) {
return;
@ -127,23 +147,11 @@ public class ThemeDialog extends DialogComponentProvider {
themeManager.restoreThemeValues();
}
private void reloadDefaultsCallback() {
if (themeManager.hasThemeChanges()) {
int result = OptionDialog.showYesNoDialog(null, "Reload Default Theme Values?",
"This will discard all of your theme changes. Continue?");
if (result != OptionDialog.YES_OPTION) {
return;
}
}
themeManager.reloadApplicationDefaults();
}
private void reset() {
colorTable.reloadAll();
fontTable.reloadAll();
iconTable.reloadAll();
updateButtons();
updateCombo();
}
private void themeComboChanged(ItemEvent e) {
@ -152,16 +160,11 @@ public class ThemeDialog extends DialogComponentProvider {
return;
}
if (!ThemeUtils.askToSaveThemeChanges(themeManager)) {
Swing.runLater(() -> updateCombo());
return;
}
String themeName = (String) e.getItem();
LafType lafType = (LafType) e.getItem();
Swing.runLater(() -> {
GTheme theme = themeManager.getTheme(themeName);
themeManager.setTheme(theme);
if (theme.getLookAndFeelType() == LafType.GTK) {
themeManager.setLookAndFeel(lafType, lafType.usesDarkDefaults());
if (lafType == LafType.GTK) {
setStatusText(
"Warning - Themes using the GTK LookAndFeel do not support changing java " +
"component colors, fonts or icons.",
@ -179,7 +182,6 @@ public class ThemeDialog extends DialogComponentProvider {
private void updateButtons() {
boolean hasChanges = themeManager.hasThemeChanges();
saveButton.setEnabled(hasChanges);
restoreButton.setEnabled(hasChanges);
}
private JComponent createMainPanel() {
@ -199,34 +201,26 @@ public class ThemeDialog extends DialogComponentProvider {
return panel;
}
private void updateCombo() {
Set<GTheme> supportedThemes = themeManager.getSupportedThemes();
List<String> themeNames =
supportedThemes.stream().map(t -> t.getName()).collect(Collectors.toList());
Collections.sort(themeNames);
combo.removeItemListener(comboListener);
combo.setModel(new DefaultComboBoxModel<String>(new Vector<String>(themeNames)));
combo.setSelectedItem(themeManager.getActiveTheme().getName());
combo.addItemListener(comboListener);
}
private Component buildThemeCombo() {
JPanel panel = new JPanel();
Set<GTheme> supportedThemes = themeManager.getSupportedThemes();
List<String> themeNames =
supportedThemes.stream().map(t -> t.getName()).collect(Collectors.toList());
Collections.sort(themeNames);
List<LafType> lafs = getSupportedLookAndFeels();
combo = new GhidraComboBox<>(themeNames);
combo.setSelectedItem(themeManager.getActiveTheme().getName());
combo = new GhidraComboBox<>(lafs);
combo.setSelectedItem(themeManager.getActiveTheme().getLookAndFeelType());
combo.addItemListener(comboListener);
panel.add(new JLabel("Theme: "), BorderLayout.WEST);
panel.add(new JLabel("Look And Feel: "), BorderLayout.WEST);
panel.add(combo);
panel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
return panel;
}
private List<LafType> getSupportedLookAndFeels() {
LafType[] lafTypes = LafType.values();
Comparator<LafType> comparator = (a, b) -> a.getName().compareTo(b.getName());
return Arrays.stream(lafTypes).filter(laf -> laf.isSupported()).sorted(comparator).toList();
}
private Component buildTabedTables() {
tabbedPane = new JTabbedPane();
@ -243,15 +237,6 @@ public class ThemeDialog extends DialogComponentProvider {
return tabbedPane;
}
private JButton createRestoreButton() {
restoreButton = new JButton("Restore");
restoreButton.setMnemonic('R');
restoreButton.setName("Restore");
restoreButton.addActionListener(e -> restoreCallback());
restoreButton.setToolTipText("Restores all previous values to current theme");
return restoreButton;
}
private JButton createSaveButton() {
saveButton = new JButton("Save");
saveButton.setMnemonic('S');
@ -270,15 +255,19 @@ public class ThemeDialog extends DialogComponentProvider {
INSTANCE.toFront();
return;
}
INSTANCE = new ThemeDialog(themeManager);
INSTANCE = new ThemeEditorDialog(themeManager);
DockingWindowManager.showDialog(INSTANCE);
}
public static ThemeEditorDialog getRunningInstance() {
return INSTANCE;
}
@Override
public void close() {
Gui.removeThemeListener(listener);
super.close();
INSTANCE = null;
}
@Override

View File

@ -46,15 +46,17 @@ public class ThemeUtils {
*/
public static boolean askToSaveThemeChanges(ThemeManager themeManager) {
if (themeManager.hasThemeChanges()) {
int result = OptionDialog.showYesNoCancelDialog(null, "Save Theme Changes?",
"You have made changes to the theme.\n Do you want to save your changes?");
int result = OptionDialog.showOptionDialog(null, "Save Theme Changes?",
"You have made changes to the current theme.\n" +
" Do you want to save or abort your changes",
"Save", "Abort");
if (result == OptionDialog.CANCEL_OPTION) {
return false;
}
if (result == OptionDialog.YES_OPTION) {
if (result == OptionDialog.OPTION_ONE) {
return saveThemeChanges(themeManager);
}
themeManager.reloadApplicationDefaults();
themeManager.restoreThemeValues();
}
return true;
}
@ -70,6 +72,9 @@ public class ThemeUtils {
String name = activeTheme.getName();
while (!canSaveToName(themeManager, name)) {
if (shouldCancelSave(themeManager, name)) {
return false;
}
name = getNameFromUser(name);
if (name == null) {
return false;
@ -78,14 +83,21 @@ public class ThemeUtils {
return saveCurrentValues(themeManager, name);
}
/**
* Resets the theme to the default, handling the case where the current theme has changes.
* @param themeManager the theme manager
*/
public static void resetThemeToDefault(ThemeManager themeManager) {
if (askToSaveThemeChanges(themeManager)) {
themeManager.setTheme(ThemeManager.getDefaultTheme());
private static boolean shouldCancelSave(ThemeManager themeManager, String name) {
if (name.isBlank()) {
int result = OptionDialog.showOptionNoCancelDialog(null, "Invalid Theme Name",
"The theme name can't be blank!\nDo you want to enter a new name?", "Yes", "Cancel",
OptionDialog.QUESTION_MESSAGE);
return result != OptionDialog.OPTION_ONE;
}
GTheme existing = themeManager.getTheme(name);
if (existing instanceof DiscoverableGTheme) {
int result = OptionDialog.showOptionNoCancelDialog(null, "Unmodifiable Theme",
"The current theme is unmodifiable!\nDo you want to save as a new theme?", "Yes",
"Cancel", OptionDialog.QUESTION_MESSAGE);
return result != OptionDialog.OPTION_ONE;
}
return false;
}
/**
@ -199,9 +211,12 @@ public class ThemeUtils {
return inputDialog.getValue();
}
private static boolean canSaveToName(ThemeManager themeManager, String name) {
public static Boolean canSaveToName(ThemeManager themeManager, String name) {
if (name.isBlank()) {
return false;
}
GTheme existing = themeManager.getTheme(name);
// if no theme exists with that name, then we are save to save it
// if no theme exists with that name, then we are safe to save it
if (existing == null) {
return true;
}
@ -225,9 +240,19 @@ public class ThemeUtils {
if (!file.exists()) {
Msg.info(ThemeUtils.class, "Saving theme to " + file);
}
LafType lafType = activeTheme.getLookAndFeelType();
boolean useDarkDefaults = activeTheme.useDarkDefaults();
GTheme newTheme = new GTheme(file, themeName, activeTheme.getLookAndFeelType(),
activeTheme.useDarkDefaults());
LafType currentLafType = themeManager.getLookAndFeelType();
// if the user has change the lookAndFeel from the current active theme, then
// save it as the lookAndFeel for the newly saved theme
if (currentLafType != activeTheme.getLookAndFeelType()) {
lafType = currentLafType;
useDarkDefaults = lafType.usesDarkDefaults();
}
GTheme newTheme = new GTheme(file, themeName, lafType, useDarkDefaults);
newTheme.load(themeManager.getNonDefaultValues());
try {
newTheme.save();
@ -242,7 +267,7 @@ public class ThemeUtils {
return true;
}
private static File getSaveFile(String themeName) {
public static File getSaveFile(String themeName) {
File dir = Application.getUserSettingsDirectory();
File themeDir = new File(dir, ThemeManager.THEME_DIR);
if (!themeDir.exists()) {

View File

@ -24,8 +24,9 @@ import javax.swing.event.ListSelectionListener;
/**
* This class provides a panel that contains a JList component.
* @param <T> The type for the items in this list
*/
public class ListPanel extends JPanel {
public class ListPanel<T> extends JPanel {
private static final long serialVersionUID = 1L;
private static final String DEFAULT_WARNING = "You must first select an item from the list.";
@ -33,7 +34,7 @@ public class ListPanel extends JPanel {
private ActionListener doubleClickActionListener;
private MouseListener mouseListener;
private JScrollPane scrollpane;
private JList list;
private JList<T> list;
/**
* Constructs a new ListPanel.
@ -45,7 +46,7 @@ public class ListPanel extends JPanel {
// Enforce a minimum size, for some amount of consistency. The magic value was
// discovered via trial-and-error.
list = new JList() {
list = new JList<>() {
@Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
@ -105,6 +106,22 @@ public class ListPanel extends JPanel {
}
}
/**
* Adds a {@link ListSelectionListener}
* @param listener the listener to add
*/
public void addListSelectionListener(ListSelectionListener listener) {
list.addListSelectionListener(listener);
}
/**
* Removes a {@link ListSelectionListener}
* @param listener the listener to remove
*/
public void removeListSelectionListener(ListSelectionListener listener) {
list.removeListSelectionListener(listener);
}
/**
* Returns true if no list items are selected.
* @return true if no list items are selected.
@ -117,7 +134,7 @@ public class ListPanel extends JPanel {
* Returns the first selected value in the list or null if nothing is selected.
* @return the first selected value in the list or null if nothing is selected.
*/
public Object getSelectedValue() {
public T getSelectedValue() {
return list.getSelectedValue();
}
@ -141,7 +158,7 @@ public class ListPanel extends JPanel {
* Selects the item.
* @param item the item to select
*/
public void setSelectedValue(Object item) {
public void setSelectedValue(T item) {
list.setSelectedValue(item, true);
}
@ -149,15 +166,15 @@ public class ListPanel extends JPanel {
* Returns an array of all the selected items.
* @return an array of all the selected items.
*/
public Object[] getSelectedValues() {
return list.getSelectedValues();
public java.util.List<T> getSelectedValues() {
return list.getSelectedValuesList();
}
/**
* replaces the list contents with the new list.
* @param dataList the new list for the contents.
*/
public void refreshList(Object[] dataList) {
public void refreshList(T[] dataList) {
list.setListData(dataList);
list.clearSelection();
}
@ -166,7 +183,7 @@ public class ListPanel extends JPanel {
* Sets the list data
* @param data the data
*/
public void setListData(Object[] data) {
public void setListData(T[] data) {
list.setListData(data);
}
@ -174,7 +191,7 @@ public class ListPanel extends JPanel {
* Sets a list model for the internal list to use.
* @param listModel the list model to use.
*/
public void setListModel(ListModel listModel) {
public void setListModel(ListModel<T> listModel) {
list.setModel(listModel);
list.clearSelection();
}
@ -183,7 +200,7 @@ public class ListPanel extends JPanel {
* Get the list model for the list.
* @return the list model for the list.
*/
public ListModel getListModel() {
public ListModel<T> getListModel() {
return (list.getModel());
}
@ -191,7 +208,7 @@ public class ListPanel extends JPanel {
* Return the JList component.
* @return the JList component.
*/
public JList getList() {
public JList<T> getList() {
return list;
}
@ -199,7 +216,7 @@ public class ListPanel extends JPanel {
* Get the cell renderer for the list.
* @param r the cell renderer to use.
*/
public void setCellRenderer(ListCellRenderer r) {
public void setCellRenderer(ListCellRenderer<T> r) {
list.setCellRenderer(r);
}
@ -284,7 +301,7 @@ public class ListPanel extends JPanel {
final JFrame frame = new JFrame("ListPanel");
frame.getContentPane().setLayout(new GridLayout(1, 1));
final ListPanel lbp = new ListPanel();
final ListPanel<String> lbp = new ListPanel<>();
final DefaultListModel<String> listModel = new DefaultListModel<>();
frame.getContentPane().add(lbp);
frame.addWindowListener(new WindowAdapter() {

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 567 B

After

Width:  |  Height:  |  Size: 567 B

View File

@ -107,7 +107,13 @@ public class ThemeUtilsTest extends AbstractDockingTest {
OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
assertNotNull(dialog);
assertEquals("Save Theme Changes?", dialog.getTitle());
pressButtonByText(dialog, "Save");
dialog = waitForDialogComponent(OptionDialog.class);
assertNotNull(dialog);
assertEquals("Unmodifiable Theme", dialog.getTitle());
pressButtonByText(dialog, "Yes");
InputDialog inputDialog = waitForDialogComponent(InputDialog.class);
assertNotNull(inputDialog);
runSwing(() -> inputDialog.setValue("Joe"));
@ -131,7 +137,7 @@ public class ThemeUtilsTest extends AbstractDockingTest {
OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
assertNotNull(dialog);
assertEquals("Save Theme Changes?", dialog.getTitle());
pressButtonByText(dialog, "No");
pressButtonByText(dialog, "Abort");
waitForSwing();
assertEquals("Bob", themeManager.getActiveTheme().getName());
}

View File

@ -71,16 +71,12 @@ public class ApplicationThemeManager extends ThemeManager {
setTheme(themePreferences.load());
}
@Override
public void reloadApplicationDefaults() {
applicationDefaults = getApplicationDefaults();
buildCurrentValues();
lookAndFeelManager.resetAll(javaDefaults);
notifyThemeChanged(new AllValuesChangedThemeEvent(false));
}
@Override
public void restoreThemeValues() {
if (activeLafType != activeTheme.getLookAndFeelType()) {
setLookAndFeel(activeTheme.getLookAndFeelType(), activeTheme.useDarkDefaults());
}
applicationDefaults = getApplicationDefaults();
buildCurrentValues();
lookAndFeelManager.resetAll(javaDefaults);
notifyThemeChanged(new AllValuesChangedThemeEvent(false));
@ -126,21 +122,43 @@ public class ApplicationThemeManager extends ThemeManager {
public void setTheme(GTheme theme) {
if (theme.hasSupportedLookAndFeel()) {
activeTheme = theme;
LafType lafType = theme.getLookAndFeelType();
activeLafType = theme.getLookAndFeelType();
useDarkDefaults = theme.useDarkDefaults();
cleanUiDefaults(); // clear out any values previous themes may have installed
lookAndFeelManager = lafType.getLookAndFeelManager(this);
lookAndFeelManager = activeLafType.getLookAndFeelManager(this);
try {
lookAndFeelManager.installLookAndFeel();
notifyThemeChanged(new AllValuesChangedThemeEvent(true));
}
catch (Exception e) {
Msg.error(this, "Error setting LookAndFeel: " + lafType.getName(), e);
Msg.error(this, "Error setting Look and Feel: " + activeLafType.getName(), e);
}
themePreferences.save(theme);
}
currentValues.checkForUnresolvedReferences();
}
@Override
public void setLookAndFeel(LafType lafType, boolean useDarkDefaults) {
if (!lafType.isSupported()) {
Msg.error(this, "Attempted to set unsupported Look and Feel: " + lafType);
return;
}
this.activeLafType = lafType;
this.useDarkDefaults = useDarkDefaults;
cleanUiDefaults();
lookAndFeelManager = lafType.getLookAndFeelManager(this);
try {
lookAndFeelManager.installLookAndFeel();
notifyThemeChanged(new AllValuesChangedThemeEvent(true));
}
catch (Exception e) {
Msg.error(this, "Error setting Look and Feel: " + lafType.getName(), e);
}
}
@Override
public void addTheme(GTheme newTheme) {
loadThemes();
@ -166,14 +184,15 @@ public class ApplicationThemeManager extends ThemeManager {
}
@Override
public Set<GTheme> getSupportedThemes() {
public List<GTheme> getSupportedThemes() {
loadThemes();
Set<GTheme> supported = new HashSet<>();
List<GTheme> supported = new ArrayList<>();
for (GTheme theme : allThemes) {
if (theme.hasSupportedLookAndFeel()) {
supported.add(theme);
}
}
Collections.sort(supported, (t1, t2) -> t1.getName().compareTo(t2.getName()));
return supported;
}
@ -262,7 +281,13 @@ public class ApplicationThemeManager extends ThemeManager {
@Override
public boolean hasThemeChanges() {
return !changedValuesMap.isEmpty();
if (!changedValuesMap.isEmpty()) {
return true;
}
if (lookAndFeelManager.getLookAndFeelType() != activeTheme.getLookAndFeelType()) {
return true;
}
return false;
}
@Override

View File

@ -23,12 +23,17 @@ import ghidra.util.classfinder.ExtensionPoint;
public abstract class DiscoverableGTheme extends GTheme implements ExtensionPoint {
static final String CLASS_PREFIX = "Class:";
protected DiscoverableGTheme(String name, LafType lookAndFeel, boolean useDarkDefaults) {
super(name, lookAndFeel, useDarkDefaults);
protected DiscoverableGTheme(String name, LafType lookAndFeel) {
super(name, lookAndFeel);
}
@Override
public String getThemeLocater() {
return CLASS_PREFIX + getClass().getName();
}
@Override
public boolean isReadOnly() {
return true;
}
}

View File

@ -47,7 +47,7 @@ public class GTheme extends GThemeValueMap {
* @param name the name for this GTheme
*/
public GTheme(String name) {
this(name, LafType.getDefaultLookAndFeel(), false);
this(name, LafType.getDefaultLookAndFeel());
}
@ -55,23 +55,22 @@ public class GTheme extends GThemeValueMap {
* Creates a new empty GTheme with the given name, {@link LookAndFeel}, and whether or not to
* use dark defaults.
* @param name the name for the new GTheme
* @param lookAndFeel the look and feel type used by this theme
* @param useDarkDefaults determines whether or
* @param lafType the look and feel type used by this theme
*/
public GTheme(String name, LafType lookAndFeel, boolean useDarkDefaults) {
this(null, name, lookAndFeel, useDarkDefaults);
public GTheme(String name, LafType lafType) {
this(null, name, lafType, lafType.usesDarkDefaults());
}
/**
* Constructor for creating a GTheme with an associated File.
* @param file the file that this theme will save to
* @param name the name of the new theme
* @param lookAndFeel the {@link LafType} for the new theme
* @param lafType the {@link LafType} for the new theme
* @param useDarkDefaults true if this new theme uses dark defaults
*/
public GTheme(File file, String name, LafType lookAndFeel, boolean useDarkDefaults) {
public GTheme(File file, String name, LafType lafType, boolean useDarkDefaults) {
this.name = name;
this.lookAndFeel = lookAndFeel;
this.lookAndFeel = lafType;
this.useDarkDefaults = useDarkDefaults;
this.file = file;
}
@ -217,6 +216,14 @@ public class GTheme extends GThemeValueMap {
writer.writeThemeToFile(file);
}
/**
* Returns true if this theme can not be changed
* @return true if this theme can not be changed
*/
public boolean isReadOnly() {
return false;
}
/**
* Reads a theme from a file. The file can be either a theme file or a zip file containing
* a theme file and optionally a set of icon files.

View File

@ -33,15 +33,21 @@ public enum LafType {
GTK("GTK+"),
MOTIF("CDE/Motif"),
FLAT_LIGHT("Flat Light"),
FLAT_DARK("Flat Dark"),
FLAT_DARK("Flat Dark", true),
WINDOWS("Windows"),
WINDOWS_CLASSIC("Windows Classic"),
MAC("Mac OS X");
private String name;
private boolean usesDarkDefaults;
private LafType(String name) {
this(name, false);
}
private LafType(String name, boolean usesDarkDefaults) {
this.name = name;
this.usesDarkDefaults = usesDarkDefaults;
}
/**
@ -52,6 +58,16 @@ public enum LafType {
return name;
}
/**
* Returns true if the LookAndFeel represented by this LafType uses application dark
* default values.
* @return true if the LookAndFeel represented by this LafType uses application dark
* default values.
*/
public boolean usesDarkDefaults() {
return usesDarkDefaults;
}
/**
* Returns the LafType for the given name or null if the given name does not match any types
* @param name the name to search a LafType for.
@ -135,4 +151,9 @@ public enum LafType {
return NIMBUS;
}
}
@Override
public String toString() {
return getName();
}
}

View File

@ -19,6 +19,7 @@ import static ghidra.util.WebColors.*;
import java.awt.Color;
import java.awt.Component;
import java.util.List;
import java.util.Set;
import javax.swing.plaf.ComponentUI;
@ -60,11 +61,6 @@ public class StubThemeManager extends ThemeManager {
}
@Override
public void reloadApplicationDefaults() {
throw new UnsupportedOperationException();
}
@Override
public void restoreThemeValues() {
throw new UnsupportedOperationException();
@ -121,7 +117,7 @@ public class StubThemeManager extends ThemeManager {
}
@Override
public Set<GTheme> getSupportedThemes() {
public List<GTheme> getSupportedThemes() {
throw new UnsupportedOperationException();
}

View File

@ -16,6 +16,7 @@
package generic.theme;
import java.awt.*;
import java.util.List;
import java.util.Set;
import javax.swing.Icon;
@ -61,9 +62,13 @@ public abstract class ThemeManager {
static final Font DEFAULT_FONT = new Font("Dialog", Font.PLAIN, 12);
static final Color DEFAULT_COLOR = Color.CYAN;
private static final int MIN_FONT_SIZE = 3;
protected static ThemeManager INSTANCE;
protected GTheme activeTheme = getDefaultTheme();
protected LafType activeLafType = activeTheme.getLookAndFeelType();
protected boolean useDarkDefaults = activeTheme.useDarkDefaults();
protected GThemeValueMap javaDefaults = new GThemeValueMap();
protected GThemeValueMap currentValues = new GThemeValueMap();
@ -100,7 +105,7 @@ public abstract class ThemeManager {
map.load(javaDefaults);
map.load(applicationDefaults.getLightValues());
if (activeTheme.useDarkDefaults()) {
if (useDarkDefaults) {
map.load(applicationDefaults.getDarkValues());
}
map.load(applicationDefaults.getLookAndFeelValues(getLookAndFeelType()));
@ -108,13 +113,6 @@ public abstract class ThemeManager {
currentValues = map;
}
/**
* Reloads the defaults from all the discoverable theme.property files.
*/
public void reloadApplicationDefaults() {
throw new UnsupportedOperationException();
}
/**
* Restores all the current application back to the values as specified by the active theme.
* In other words, reverts any changes to the active theme that haven't been saved.
@ -191,6 +189,19 @@ public abstract class ThemeManager {
throw new UnsupportedOperationException();
}
/**
* Sets the current {@link LookAndFeel}. This is used by theme editors to allow users to
* see the effects of changing LookAndFeels when configuring a theme. Setting this different
* from the activeTheme's LookAndFeel setting means the the current theme is in an unsaved
* state and causes the {@link #hasThemeChanges()} method to return true.
* @param lafType the {@link LafType} to set the LookAndFeel to
* @param useDarkDefaults true if the application should used dark defaults with this
* LookAndFeel
*/
public void setLookAndFeel(LafType lafType, boolean useDarkDefaults) {
throw new UnsupportedOperationException();
}
/**
* Adds the given theme to set of all themes.
* @param newTheme the theme to add
@ -220,7 +231,7 @@ public abstract class ThemeManager {
* Returns a set of all known themes that are supported on the current platform.
* @return a set of all known themes that are supported on the current platform.
*/
public Set<GTheme> getSupportedThemes() {
public List<GTheme> getSupportedThemes() {
throw new UnsupportedOperationException();
}
@ -237,7 +248,7 @@ public abstract class ThemeManager {
* @return the {@link LafType} for the currently active {@link LookAndFeel}
*/
public LafType getLookAndFeelType() {
return activeTheme.getLookAndFeelType();
return activeLafType;
}
/**
@ -268,7 +279,7 @@ public abstract class ThemeManager {
GThemeValueMap map = new GThemeValueMap();
map.load(javaDefaults);
map.load(applicationDefaults.getLightValues());
if (activeTheme.useDarkDefaults()) {
if (useDarkDefaults) {
map.load(applicationDefaults.getDarkValues());
}
map.load(applicationDefaults.getLookAndFeelValues(getLookAndFeelType()));
@ -439,7 +450,7 @@ public abstract class ThemeManager {
public GThemeValueMap getApplicationOverrides() {
GThemeValueMap currentDefaults = new GThemeValueMap();
currentDefaults.load(applicationDefaults.getLightValues());
if (activeTheme.useDarkDefaults()) {
if (useDarkDefaults) {
currentDefaults.load(applicationDefaults.getDarkValues());
}
currentDefaults.load(applicationDefaults.getLookAndFeelValues(getLookAndFeelType()));
@ -454,7 +465,7 @@ public abstract class ThemeManager {
public GThemeValueMap getDefaults() {
GThemeValueMap currentDefaults = new GThemeValueMap(javaDefaults);
currentDefaults.load(applicationDefaults.getLightValues());
if (activeTheme.useDarkDefaults()) {
if (useDarkDefaults) {
currentDefaults.load(applicationDefaults.getDarkValues());
}
currentDefaults.load(applicationDefaults.getLookAndFeelValues(getLookAndFeelType()));
@ -545,7 +556,7 @@ public abstract class ThemeManager {
* @return true if the current theme use dark default values.
*/
public boolean isDarkTheme() {
return activeTheme.useDarkDefaults();
return useDarkDefaults;
}
/**
@ -581,4 +592,21 @@ public abstract class ThemeManager {
Msg.error(this, message, t);
}
/**
* Adjust the size of all fonts by the given amount.
* @param amount the number to add to the current font size;
*/
public void adjustFonts(int amount) {
List<FontValue> fonts = currentValues.getFonts();
for (FontValue fontValue : fonts) {
Font directFont = fontValue.getRawValue();
if (directFont == null) {
continue; // indirect fonts will be handled when its referenced font is handled
}
int currentSize = directFont.getSize();
int newSize = Math.max(MIN_FONT_SIZE, currentSize += amount);
setFont(fontValue.getId(), directFont.deriveFont((float) newSize));
}
}
}

View File

@ -27,7 +27,7 @@ import generic.theme.LafType;
public class CDEMotifTheme extends DiscoverableGTheme {
public CDEMotifTheme() {
super("Motif Theme", LafType.MOTIF, false);
super("Motif Theme", LafType.MOTIF);
}
}

View File

@ -25,6 +25,6 @@ import generic.theme.LafType;
*/
public class FlatDarkTheme extends DiscoverableGTheme {
public FlatDarkTheme() {
super("Flat Dark Theme", LafType.FLAT_DARK, true);
super("Flat Dark Theme", LafType.FLAT_DARK);
}
}

View File

@ -26,7 +26,7 @@ import generic.theme.LafType;
public class FlatLightTheme extends DiscoverableGTheme {
public FlatLightTheme() {
super("Flat Light Theme", LafType.FLAT_LIGHT, false);
super("Flat Light Theme", LafType.FLAT_LIGHT);
}
}

View File

@ -27,7 +27,7 @@ import generic.theme.LafType;
public class GTKTheme extends DiscoverableGTheme {
public GTKTheme() {
super("GTK+ Theme", LafType.GTK, false);
super("GTK+ Theme", LafType.GTK);
}
}

View File

@ -27,6 +27,6 @@ import generic.theme.LafType;
public class MacTheme extends DiscoverableGTheme {
public MacTheme() {
super("Mac OS X Theme", LafType.MAC, false);
super("Mac OS X Theme", LafType.MAC);
}
}

View File

@ -27,7 +27,7 @@ import generic.theme.LafType;
public class MetalTheme extends DiscoverableGTheme {
public MetalTheme() {
super("Metal Theme", LafType.METAL, false);
super("Metal Theme", LafType.METAL);
}
}

View File

@ -27,7 +27,7 @@ import generic.theme.LafType;
public class NimbusTheme extends DiscoverableGTheme {
public NimbusTheme() {
super("Nimbus Theme", LafType.NIMBUS, false);
super("Nimbus Theme", LafType.NIMBUS);
}
}

View File

@ -27,6 +27,6 @@ import generic.theme.LafType;
public class WindowsClassicTheme extends DiscoverableGTheme {
public WindowsClassicTheme() {
super("Windows Classic Theme", LafType.WINDOWS_CLASSIC, false);
super("Windows Classic Theme", LafType.WINDOWS_CLASSIC);
}
}

View File

@ -27,6 +27,6 @@ import generic.theme.LafType;
public class WindowsTheme extends DiscoverableGTheme {
public WindowsTheme() {
super("Windows Theme", LafType.WINDOWS, false);
super("Windows Theme", LafType.WINDOWS);
}
}

View File

@ -48,7 +48,6 @@ public class FlatDarkUiDefaultsMapper extends FlatUiDefaultsMapper {
// our view background color so that they look like normal editable widgets
//
overrideColor("ComboBox.background", BG_VIEW_ID);
overrideColor("ComboBox.background", BG_VIEW_ID);
overrideColor("EditorPane.background", BG_VIEW_ID);
overrideColor("FormattedTextField.background", BG_VIEW_ID);
overrideColor("List.background", BG_VIEW_ID);
@ -60,9 +59,9 @@ public class FlatDarkUiDefaultsMapper extends FlatUiDefaultsMapper {
overrideColor("Tree.background", BG_VIEW_ID);
overrideColor("Tree.textBackground", BG_VIEW_ID);
overrideColor("TextArea.background", BG_VIEW_ID);
overrideColor("TextArea.foreground", BG_VIEW_ID);
overrideColor("TextArea.foreground", FG_VIEW_ID);
overrideColor("TextPane.background", BG_VIEW_ID);
overrideColor("TextPane.foreground", BG_VIEW_ID);
overrideColor("TextPane.foreground", FG_VIEW_ID);
}
}

View File

@ -82,7 +82,7 @@ public class ApplicationThemeManagerTest {
GColor gColor = new GColor("color.test.bg");
assertColor(WHITE, gColor);
themeManager.setTheme(new GTheme("Test", LafType.FLAT_DARK, true));
themeManager.setTheme(new GTheme("Test", LafType.FLAT_DARK));
assertEquals(BLACK, gColor);
themeManager.setTheme(new GTheme("Test2"));
@ -139,7 +139,7 @@ public class ApplicationThemeManagerTest {
assertColor(WHITE, gColor);
defaultValues.addColor(new ColorValue("color.test.bg", YELLOW));
themeManager.reloadApplicationDefaults();
themeManager.restoreThemeValues();
assertEquals(YELLOW, gColor);
}
@ -191,7 +191,7 @@ public class ApplicationThemeManagerTest {
@Test
public void testGetSupportedThemes() {
Set<GTheme> supportedThemes = themeManager.getSupportedThemes();
List<GTheme> supportedThemes = themeManager.getSupportedThemes();
// since we put mac specific and windows specific themes, they can't all be here
// regardless of the current platform
assertTrue(supportedThemes.size() < themes.size());

View File

@ -0,0 +1,149 @@
/* ###
* 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 ghidra.app.plugin.gui;
import java.io.File;
import java.io.IOException;
import java.util.List;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import docking.DialogComponentProvider;
import docking.theme.gui.ThemeUtils;
import docking.widgets.combobox.GhidraComboBox;
import generic.theme.GTheme;
import generic.theme.ThemeManager;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.Msg;
import ghidra.util.layout.PairLayout;
public class CreateThemeDialog extends DialogComponentProvider {
private JTextField nameField;
private ThemeManager themeManager;
private GhidraComboBox<GTheme> combo;
private GTheme newTheme;
protected CreateThemeDialog(ThemeManager themeManager) {
super("Create Theme");
this.themeManager = themeManager;
addWorkPanel(buildMainPanel());
addOKButton();
addCancelButton();
}
@Override
protected void okCallback() {
String themeName = nameField.getText().trim();
File file = ThemeUtils.getSaveFile(themeName);
GTheme baseTheme = (GTheme) combo.getSelectedItem();
newTheme = new GTheme(file, themeName, baseTheme.getLookAndFeelType(),
baseTheme.useDarkDefaults());
newTheme.load(baseTheme);
try {
newTheme.save();
}
catch (IOException e) {
Msg.showError(ThemeUtils.class, null, "I/O Error",
"Error writing theme file: " + newTheme.getFile().getAbsolutePath(), e);
newTheme = null;
}
close();
}
@Override
protected void cancelCallback() {
close();
}
private JComponent buildMainPanel() {
JPanel panel = new JPanel(new PairLayout(10, 10));
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
nameField = createNameField();
combo = buildThemeCombo();
panel.add(new JLabel("New Theme Name: "));
panel.add(nameField);
panel.add(new JLabel("Base Theme: "));
panel.add(combo);
return panel;
}
private JTextField createNameField() {
JTextField jTextField = new JTextField(20);
jTextField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
updateOk();
}
@Override
public void insertUpdate(DocumentEvent e) {
updateOk();
}
@Override
public void changedUpdate(DocumentEvent e) {
updateOk();
}
});
return jTextField;
}
private void updateOk() {
String name = nameField.getText().trim();
setOkEnabled(isValidThemeName(name));
}
private boolean isValidThemeName(String name) {
if (name.isBlank()) {
setStatusText("You must enter a theme name!");
return false;
}
GTheme existing = themeManager.getTheme(name);
// if no theme exists with that name, then we are safe to save it
if (existing != null) {
setStatusText("Theme already exists with that name!");
return false;
}
clearStatusText();
return true;
}
private GhidraComboBox<GTheme> buildThemeCombo() {
List<GTheme> supportedThemes = themeManager.getSupportedThemes();
GhidraComboBox<GTheme> ghidraComboBox = new GhidraComboBox<>(supportedThemes);
ghidraComboBox.setSelectedItem(themeManager.getActiveTheme());
return ghidraComboBox;
}
public GTheme getNewTheme(PluginTool tool, String suggestedName) {
if (suggestedName != null) {
nameField.setText(suggestedName);
nameField.selectAll();
}
updateOk();
tool.showDialog(this);
return newTheme;
}
}

View File

@ -0,0 +1,106 @@
/* ###
* 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 ghidra.app.plugin.gui;
import java.awt.BorderLayout;
import java.util.*;
import javax.swing.*;
import docking.DialogComponentProvider;
import docking.widgets.list.ListPanel;
import generic.theme.GTheme;
import generic.theme.ThemeManager;
import ghidra.util.HelpLocation;
import ghidra.util.Swing;
public class ThemeChooserDialog extends DialogComponentProvider {
private ThemeManager themeManager;
private ListPanel<GTheme> listPanel;
private GTheme originalTheme;
public ThemeChooserDialog(ThemeManager themeManager) {
super("Change Theme");
this.themeManager = themeManager;
originalTheme = themeManager.getActiveTheme();
addWorkPanel(buildMainPanel());
addOKButton();
addCancelButton();
setRememberSize(false);
setHelpLocation(new HelpLocation("Theming", "Switch_Theme"));
}
@Override
protected void okCallback() {
close();
}
protected void cancelCallback() {
GTheme activeTheme = themeManager.getActiveTheme();
if (activeTheme != originalTheme) {
themeManager.setTheme(originalTheme);
}
close();
}
private JComponent buildMainPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 10));
ThemeListModel model = new ThemeListModel();
listPanel = new ListPanel<>();
listPanel.setListModel(model);
listPanel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
GTheme activeTheme = themeManager.getActiveTheme();
listPanel.setSelectedValue(activeTheme);
listPanel.addListSelectionListener(e -> selectionChanged());
panel.add(listPanel);
return panel;
}
private void selectionChanged() {
GTheme selectedValue = listPanel.getSelectedValue();
if (selectedValue == null) {
return;
}
GTheme activeTheme = themeManager.getActiveTheme();
if (selectedValue != activeTheme) {
Swing.runLater(() -> themeManager.setTheme(selectedValue));
}
}
private class ThemeListModel extends AbstractListModel<GTheme> {
private List<GTheme> allThemes;
ThemeListModel() {
allThemes = new ArrayList<>(themeManager.getSupportedThemes());
Collections.sort(allThemes, (t1, t2) -> t1.getName().compareTo(t2.getName()));
}
@Override
public int getSize() {
return allThemes.size();
}
@Override
public GTheme getElementAt(int index) {
return allThemes.get(index);
}
}
}

View File

@ -16,8 +16,9 @@
package ghidra.app.plugin.gui;
import docking.action.builder.ActionBuilder;
import docking.theme.gui.ThemeDialog;
import docking.theme.gui.ThemeEditorDialog;
import docking.theme.gui.ThemeUtils;
import generic.theme.GTheme;
import generic.theme.ThemeManager;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.framework.main.ApplicationLevelOnlyPlugin;
@ -48,46 +49,73 @@ public class ThemeManagerPlugin extends Plugin implements ApplicationLevelOnlyPl
@Override
protected void init() {
String owner = getName();
String themeSubMenu = "Theme Actions";
String group = "theme";
new ActionBuilder("Edit Theme", owner)
.menuPath("Edit", "Theme")
new ActionBuilder("Switch Theme", owner).menuPath("Edit", "Theme", "Switch...")
.menuGroup(group, "1")
.helpLocation(new HelpLocation("Theming", "Edit_Theme"))
.onAction(e -> ThemeDialog.editTheme(themeManager))
.helpLocation(new HelpLocation("Theming", "Switch_Theme"))
.onAction(e -> switchTheme())
.buildAndInstall(tool);
new ActionBuilder("Reset", owner)
.menuPath("Edit", themeSubMenu, "Reset Theme Values")
new ActionBuilder("Configure", owner).menuPath("Edit", "Theme", "Configure...")
.menuGroup(group, "2")
.helpLocation(new HelpLocation("Theming", "Reset_Theme_Values"))
.onAction(e -> ThemeUtils.resetThemeToDefault(themeManager))
.helpLocation(new HelpLocation("Theming", "Edit_Theme"))
.onAction(e -> configure())
.buildAndInstall(tool);
new ActionBuilder("Import Theme", owner)
.menuPath("Edit", themeSubMenu, "Import...")
new ActionBuilder("New Theme", owner).menuPath("Edit", "Theme", "New...")
.menuGroup(group, "3")
.helpLocation(new HelpLocation("Theming", "New_Theme"))
.onAction(e -> createNewTheme())
.buildAndInstall(tool);
new ActionBuilder("Import Theme", owner).menuPath("Edit", "Theme", "Import...")
.menuGroup(group, "4")
.helpLocation(new HelpLocation("Theming", "Import_Theme"))
.onAction(e -> ThemeUtils.importTheme(themeManager))
.buildAndInstall(tool);
new ActionBuilder("Export Theme", owner)
.menuPath("Edit", themeSubMenu, "Export...")
.menuGroup(group, "4")
new ActionBuilder("Export Theme", owner).menuPath("Edit", "Theme", "Export...")
.menuGroup(group, "5")
.helpLocation(new HelpLocation("Theming", "Export_Theme"))
.onAction(e -> ThemeUtils.exportTheme(themeManager))
.buildAndInstall(tool);
new ActionBuilder("Delete Theme", owner)
.menuPath("Edit", themeSubMenu, "Delete...")
.menuGroup(group, "5")
new ActionBuilder("Delete Theme", owner).menuPath("Edit", "Theme", "Delete...")
.menuGroup(group, "6")
.helpLocation(new HelpLocation("Theming", "Delete_Theme"))
.onAction(e -> ThemeUtils.deleteTheme(themeManager))
.buildAndInstall(tool);
tool.setMenuGroup(new String[] { "Edit", themeSubMenu }, group, "2");
}
private void switchTheme() {
ThemeChooserDialog dialog = new ThemeChooserDialog(themeManager);
tool.showDialog(dialog);
}
private void createNewTheme() {
if (!ThemeUtils.askToSaveThemeChanges(themeManager)) {
return; // user cancelled
}
CreateThemeDialog dialog = new CreateThemeDialog(themeManager);
GTheme newTheme = dialog.getNewTheme(tool, "New Theme");
if (newTheme != null) {
themeManager.addTheme(newTheme);
themeManager.setTheme(newTheme);
configure();
}
}
private void configure() {
ThemeEditorDialog dialog = ThemeEditorDialog.getRunningInstance();
if (dialog != null) {
dialog.toFront();
return;
}
ThemeEditorDialog.editTheme(themeManager);
}
@Override

View File

@ -37,7 +37,7 @@ import ghidra.util.HelpLocation;
class ImportGhidraToolsDialog extends DialogComponentProvider {
private ListPanel listPanel;
private ListPanel<JCheckBox> listPanel;
private JPanel mainPanel;
private GCheckBox[] checkboxes;
private String[] tools;
@ -97,7 +97,7 @@ class ImportGhidraToolsDialog extends DialogComponentProvider {
SelectPanel myButtonPanel = new SelectPanel(e -> selectAll(), e -> deselectAll());
listPanel = new ListPanel();
listPanel = new ListPanel<>();
listPanel.setCellRenderer(new DataCellRenderer());
listPanel.setMouseListener(new ListMouseListener());

View File

@ -46,7 +46,7 @@ import ghidra.util.task.*;
*/
public class SaveDataDialog extends DialogComponentProvider {
private ListPanel listPanel;
private ListPanel<JCheckBox> listPanel;
private JPanel mainPanel;
private GCheckBox[] checkboxes;
private List<DomainFile> files;
@ -156,7 +156,7 @@ public class SaveDataDialog extends DialogComponentProvider {
SelectPanel myButtonPanel = new SelectPanel(e -> selectAll(), e -> deselectAll());
listPanel = new ListPanel();
listPanel = new ListPanel<>();
listPanel.setCellRenderer(new DataCellRenderer());
listPanel.setMouseListener(new ListMouseListener());

View File

@ -36,11 +36,12 @@ class DomainFilesPanel extends JPanel {
private List<DomainFile> fileList;
private GCheckBox[] checkboxes;
private ListPanel listPanel;
private ListPanel<JCheckBox> listPanel;
/**
* Constructor
* @param fileList list of DomainFile objects
* @param listTitle the title
*/
DomainFilesPanel(List<DomainFile> fileList, String listTitle) {
super();
@ -59,7 +60,7 @@ class DomainFilesPanel extends JPanel {
//
// List Panel
//
listPanel = new ListPanel();
listPanel = new ListPanel<>();
listPanel.setCellRenderer(new DataCellRenderer());
listPanel.setMouseListener(new ListMouseListener());
if (listTitle != null) {
@ -121,7 +122,7 @@ class DomainFilesPanel extends JPanel {
return;
}
JList list = (JList) e.getSource();
JList<?> list = (JList<?>) e.getSource();
int index = list.locationToIndex(e.getPoint());
if (index < 0) {
return;

View File

@ -33,8 +33,9 @@ import generic.util.Path;
import ghidra.app.plugin.core.console.ConsoleComponentProvider;
import ghidra.app.plugin.core.osgi.BundleStatusComponentProvider;
import ghidra.app.plugin.core.script.*;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.*;
import ghidra.app.services.ConsoleService;
import ghidra.python.PythonScriptProvider;
import ghidra.util.HelpLocation;
public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator {
@ -230,10 +231,11 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator
@Test
public void testPick() {
List<String> items = new ArrayList<>();
items.add("Java");
items.add("Python");
final PickProviderDialog pickDialog = new PickProviderDialog(items, "Java");
List<GhidraScriptProvider> items = new ArrayList<>();
JavaScriptProvider javaScriptProvider = new JavaScriptProvider();
items.add(javaScriptProvider);
items.add(new PythonScriptProvider());
final PickProviderDialog pickDialog = new PickProviderDialog(items, javaScriptProvider);
runSwing(() -> tool.showDialog(pickDialog), false);
PickProviderDialog dialog = waitForDialogComponent(PickProviderDialog.class);

View File

@ -22,6 +22,7 @@ import org.junit.Test;
import docking.theme.gui.*;
import generic.theme.*;
import generic.theme.GThemeDefaults.Colors.Palette;
import ghidra.app.plugin.gui.ThemeChooserDialog;
import resources.ResourceManager;
public class ThemingScreenShots extends GhidraScreenShotGenerator {
@ -34,11 +35,17 @@ public class ThemingScreenShots extends GhidraScreenShotGenerator {
}
@Test
public void testThemeDialog() {
showDialogWithoutBlocking(tool, new ThemeDialog(themeManager));
public void testThemeEditorDialog() {
showDialogWithoutBlocking(tool, new ThemeEditorDialog(themeManager));
captureDialog(1000, 500);
}
@Test
public void testThemeChooserDialog() {
showDialogWithoutBlocking(tool, new ThemeChooserDialog(themeManager));
captureDialog(250, 250);
}
@Test
public void testColorEditor() {
ColorValueEditor editor = new ColorValueEditor(e -> {
@ -63,7 +70,8 @@ public class ThemingScreenShots extends GhidraScreenShotGenerator {
public void testIconEditor() {
IconValueEditor editor = new IconValueEditor(e -> {
/**/});
IconValue value = new IconValue("icon.bomb", ResourceManager.getDefaultIcon());
IconValue value =
new IconValue("icon.reload", ResourceManager.loadIcon("images/reload.png"));
themeManager.setIcon(value);
editor.editValue(value);
captureDialog();