From d466dbf06b96ae34df768d28c0d5f9aa865568cd Mon Sep 17 00:00:00 2001 From: ghidragon <106987263+ghidragon@users.noreply.github.com> Date: Mon, 26 Sep 2022 18:46:30 -0400 Subject: [PATCH] GP-1981 externalized icons for XTRA modules. Added loadIcon method in ResourceManager. Created api for integrating Options with Theming. Fixed issue with system property mappings. Created dynamic icons for the close and drop-down menu item icons. --- .../SampleTablePlugin/certification.manifest | 1 + .../data/sample.plugin.theme.properties | 3 + .../ghidra/examples/SampleTableProvider.java | 6 +- .../Extensions/sample/certification.manifest | 1 + .../sample/data/sample.theme.properties | 13 ++ .../examples/HelloWorldComponentProvider.java | 11 +- .../ghidra/examples/KitchenSinkPlugin.java | 209 +++++++++--------- .../examples/ShowInfoComponentProvider.java | 8 +- .../examples/graph/SampleGraphPlugin.java | 8 +- .../layout/SampleGraphLayoutProvider.java | 4 +- ...leGraphPluginDependencyLayoutProvider.java | 4 +- .../core/searchmem/MemSearchPlugin.java | 21 +- .../core/searchtext/SearchTextPlugin.java | 33 +-- .../java/ghidra/app/util/PluginConstants.java | 17 +- .../Docking/data/docking.theme.properties | 4 + .../src/main/java/docking/CloseIcon.java | 67 ++++++ .../java/docking/DockableToolBarManager.java | 11 +- .../main/java/docking/DropDownMenuIcon.java | 71 ++++++ .../java/docking/util/image/ToolIconURL.java | 5 +- .../Generic/src/main/java/generic/Images.java | 22 -- .../generic/theme/AbstractThemeReader.java | 3 +- .../src/main/java/generic/theme/Gui.java | 75 ++++++- .../main/java/generic/theme/IconValue.java | 8 +- .../generic/theme/laf/LookAndFeelManager.java | 3 +- .../framework/options/AbstractOptions.java | 33 ++- .../ghidra/framework/options/Options.java | 26 +++ .../ghidra/framework/options/SubOptions.java | 12 + .../framework/options/ThemeColorOption.java | 58 +++++ .../framework/options/ThemeFontOption.java | 58 +++++ .../src/main/java/resources/IconProvider.java | 3 +- .../main/java/resources/ResourceManager.java | 96 ++++---- .../src/main/java/help/CustomTOCView.java | 3 + 32 files changed, 637 insertions(+), 260 deletions(-) create mode 100644 Ghidra/Extensions/SampleTablePlugin/data/sample.plugin.theme.properties create mode 100644 Ghidra/Extensions/sample/data/sample.theme.properties create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/CloseIcon.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/DropDownMenuIcon.java delete mode 100644 Ghidra/Framework/Generic/src/main/java/generic/Images.java create mode 100644 Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/ThemeColorOption.java create mode 100644 Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/ThemeFontOption.java diff --git a/Ghidra/Extensions/SampleTablePlugin/certification.manifest b/Ghidra/Extensions/SampleTablePlugin/certification.manifest index b9b2c11b56..c982dd70ad 100644 --- a/Ghidra/Extensions/SampleTablePlugin/certification.manifest +++ b/Ghidra/Extensions/SampleTablePlugin/certification.manifest @@ -3,6 +3,7 @@ ##MODULE IP: Oxygen Icons - LGPL 3.0 Module.manifest||GHIDRA||reviewed||END| data/ExtensionPoint.manifest||GHIDRA||||END| +data/sample.plugin.theme.properties||GHIDRA||||END| extension.properties||GHIDRA||||END| src/main/help/help/TOC_Source.xml||GHIDRA||||END| src/main/help/help/shared/arrow.gif||GHIDRA||reviewed||END| diff --git a/Ghidra/Extensions/SampleTablePlugin/data/sample.plugin.theme.properties b/Ghidra/Extensions/SampleTablePlugin/data/sample.plugin.theme.properties new file mode 100644 index 0000000000..b001219c91 --- /dev/null +++ b/Ghidra/Extensions/SampleTablePlugin/data/sample.plugin.theme.properties @@ -0,0 +1,3 @@ +[Defaults] +icon.sample.plugin.action.show.options = table.png +icon.sample.plugin.action.save = disk.png \ No newline at end of file diff --git a/Ghidra/Extensions/SampleTablePlugin/src/main/java/ghidra/examples/SampleTableProvider.java b/Ghidra/Extensions/SampleTablePlugin/src/main/java/ghidra/examples/SampleTableProvider.java index 6d071a29a7..530d0f762b 100644 --- a/Ghidra/Extensions/SampleTablePlugin/src/main/java/ghidra/examples/SampleTableProvider.java +++ b/Ghidra/Extensions/SampleTablePlugin/src/main/java/ghidra/examples/SampleTableProvider.java @@ -27,6 +27,7 @@ import docking.action.*; import docking.widgets.checkbox.GCheckBox; import docking.widgets.filechooser.GhidraFileChooserPanel; import docking.widgets.table.GFilterTable; +import generic.theme.GIcon; import ghidra.framework.options.OptionsChangeListener; import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.ComponentProviderAdapter; @@ -34,7 +35,6 @@ import ghidra.framework.plugintool.util.OptionsService; import ghidra.util.*; import ghidra.util.classfinder.ClassSearcher; import ghidra.util.layout.MiddleLayout; -import resources.ResourceManager; public class SampleTableProvider extends ComponentProviderAdapter implements OptionsChangeListener { @@ -154,7 +154,7 @@ public class SampleTableProvider extends ComponentProviderAdapter implements Opt } }; - ImageIcon icon = ResourceManager.loadImage("images/table.png"); + Icon icon = new GIcon("icon.sample.plugin.action.show.options"); optionsAction.setToolBarData(new ToolBarData(icon)); DockingAction saveTableDataAction = new DockingAction("Save Table Data", plugin.getName()) { @@ -189,7 +189,7 @@ public class SampleTableProvider extends ComponentProviderAdapter implements Opt return SwingUtilities.isDescendingFrom((Component) sourceObject, filterTable); } }; - icon = ResourceManager.loadImage("images/disk.png"); + icon = new GIcon("icons.sample.plugin.action.save"); saveTableDataAction.setToolBarData(new ToolBarData(icon)); saveTableDataAction.setPopupMenuData(new MenuData(new String[] { "Save Data" })); diff --git a/Ghidra/Extensions/sample/certification.manifest b/Ghidra/Extensions/sample/certification.manifest index f76dd9c0ce..0f1a49b26c 100644 --- a/Ghidra/Extensions/sample/certification.manifest +++ b/Ghidra/Extensions/sample/certification.manifest @@ -3,6 +3,7 @@ ##MODULE IP: Oxygen Icons - LGPL 3.0 Module.manifest||GHIDRA||reviewed||END| data/README.txt||GHIDRA||||END| +data/sample.theme.properties||GHIDRA||||END| extension.properties||GHIDRA||||END| src/main/help/help/TOC_Source.xml||GHIDRA||||END| src/main/help/help/shared/arrow.gif||GHIDRA||reviewed||END| diff --git a/Ghidra/Extensions/sample/data/sample.theme.properties b/Ghidra/Extensions/sample/data/sample.theme.properties new file mode 100644 index 0000000000..02d966ddc8 --- /dev/null +++ b/Ghidra/Extensions/sample/data/sample.theme.properties @@ -0,0 +1,13 @@ +[Defaults] +icon.sample.provider = erase16.png +icon.sample.provider.graph = color_swatch.png +icon.sample.provider.hello.world = information.png + +icon.sample.action.hello.world = information.png +icon.sample.kitchen.sink.action.hellow.world = left.png +icon.sample.kitchen.sink.action.hellow.program = right.png + +icon.sample.action.show.graph = applications-development.png + +icon.sample.graph.dependency.layout = color_swatch.png +icon.sample.hello.world.action.clear = erase16.png \ No newline at end of file diff --git a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/HelloWorldComponentProvider.java b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/HelloWorldComponentProvider.java index 415543874b..b3c8d19ac5 100644 --- a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/HelloWorldComponentProvider.java +++ b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/HelloWorldComponentProvider.java @@ -24,14 +24,13 @@ import docking.ActionContext; import docking.WindowPosition; import docking.action.*; import docking.widgets.EmptyBorderButton; +import generic.theme.GIcon; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.PluginTool; import ghidra.util.HelpLocation; import ghidra.util.Msg; -import resources.ResourceManager; public class HelloWorldComponentProvider extends ComponentProviderAdapter { - private final static String PREV_IMAGE = "images/information.png"; private final static HelpLocation HELP = new HelpLocation("SampleHelpTopic", "SampleHelpTopic_Anchor_Name"); private MyButton activeButtonObj; @@ -41,7 +40,7 @@ public class HelloWorldComponentProvider extends ComponentProviderAdapter { public HelloWorldComponentProvider(PluginTool tool, String name) { super(tool, name, name); buildMainPanel(); - setIcon(ResourceManager.loadImage("images/erase16.png")); + setIcon(new GIcon("icon.sample.provider")); setHelpLocation(HELP); setDefaultWindowPosition(WindowPosition.WINDOW); setTitle("Hello World Component"); @@ -64,13 +63,13 @@ public class HelloWorldComponentProvider extends ComponentProviderAdapter { // put in Menu called "Hello", with Menu item of "World". Since this will be a local action // the menu item will appear on the local toolbar drop down. - ImageIcon prevImage = ResourceManager.loadImage(PREV_IMAGE); - action.setMenuBarData(new MenuData(new String[] { "Misc", "Hello World" }, prevImage)); + Icon icon = new GIcon("icon.sample.action.hello.world"); + action.setMenuBarData(new MenuData(new String[] { "Misc", "Hello World" }, icon)); action.setKeyBindingData(new KeyBindingData(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.CTRL_MASK))); // puts the action on the local toolbar. - action.setToolBarData(new ToolBarData(prevImage)); + action.setToolBarData(new ToolBarData(icon)); action.setDescription("Hello World"); // set the help URL for the action diff --git a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/KitchenSinkPlugin.java b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/KitchenSinkPlugin.java index dcfd61bd58..36ab7f00e7 100644 --- a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/KitchenSinkPlugin.java +++ b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/KitchenSinkPlugin.java @@ -15,6 +15,14 @@ */ package ghidra.examples; +import java.awt.Event; +import java.awt.event.KeyEvent; + +import javax.swing.*; + +import docking.ActionContext; +import docking.action.*; +import generic.theme.GIcon; import ghidra.app.ExamplesPluginPackage; import ghidra.app.events.ProgramLocationPluginEvent; import ghidra.app.plugin.PluginCategoryNames; @@ -28,15 +36,6 @@ import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; import ghidra.util.Msg; -import java.awt.Event; -import java.awt.event.KeyEvent; - -import javax.swing.*; - -import resources.ResourceManager; -import docking.ActionContext; -import docking.action.*; - /** * Class description goes here * @@ -54,120 +53,120 @@ import docking.action.*; ) //@formatter:on public class KitchenSinkPlugin extends ProgramPlugin { - private final static String NEXT_IMAGE = "images/right.png"; - private final static String PREV_IMAGE = "images/left.png"; - private DockingAction helloProgramAction; - private Program program; + private DockingAction helloProgramAction; + private Program program; - /** - * Constructor - */ - public KitchenSinkPlugin(PluginTool tool) { + /** + * Constructor + */ + public KitchenSinkPlugin(PluginTool tool) { - super(tool); + super(tool); - // set up list of services. - setupServices(); + // set up list of services. + setupServices(); - // set up list of actions. - setupActions(); - } - - private void setupServices() { - registerServiceProvided(HelloWorldService.class, - new HelloWorldService() { - public void sayHello() { - announce("Hello"); - } - }); - } - - private void setupActions() { - DockingAction action = new DockingAction("Hello World", getName() ) { - @Override - public void actionPerformed( ActionContext context ) { - Msg.info(this, "Hello World:: action"); - announce("Hello World"); - } - }; - action.setEnabled( true ); - String helloGroup = "Hello"; - ImageIcon prevImage = ResourceManager.loadImage(PREV_IMAGE); - action.setMenuBarData( new MenuData( new String[] {"Misc", "Hello World"}, prevImage, helloGroup ) ); - action.setPopupMenuData( new MenuData( new String[] {"Hello World"}, prevImage, helloGroup ) ); - action.setKeyBindingData( new KeyBindingData( KeyStroke.getKeyStroke('H', Event.CTRL_MASK ) ) ); - action.setToolBarData( new ToolBarData( prevImage, helloGroup ) ); - action.setDescription("Hello World"); - action.setHelpLocation(new HelpLocation("SampleHelpTopic", "KS_Hello_World")); - - tool.addAction(action); - - action = new DockingAction("Hello Program", getName() ) { - @Override - public void actionPerformed( ActionContext context ) { - Msg.info(this, "Hello Program:: action"); - sayHelloProgram(); - } - }; - action.setEnabled(false); - ImageIcon nextImage = ResourceManager.loadImage(NEXT_IMAGE); - action.setMenuBarData( new MenuData( new String[]{"Misc", "Hello Program"}, nextImage, helloGroup ) ); - action.setKeyBindingData( new KeyBindingData( KeyStroke.getKeyStroke(KeyEvent.VK_P, Event.CTRL_MASK ) ) ); - action.setToolBarData( new ToolBarData( nextImage, helloGroup ) ); - action.setDescription("Hello Program"); - action.setHelpLocation(new HelpLocation("SampleHelpTopic", "KS_Hello_Program")); - tool.addAction(action); - - // remember this action so I can enable/disable it later - helloProgramAction = action; - } - - @Override - protected void programActivated(Program activatedProgram) { - helloProgramAction.setEnabled(true); - this.program = activatedProgram; + // set up list of actions. + setupActions(); } + + private void setupServices() { + registerServiceProvided(HelloWorldService.class, + new HelloWorldService() { + public void sayHello() { + announce("Hello"); + } + }); + } + + private void setupActions() { + DockingAction action = new DockingAction("Hello World", getName()) { + @Override + public void actionPerformed(ActionContext context) { + Msg.info(this, "Hello World:: action"); + announce("Hello World"); + } + }; + action.setEnabled(true); + String helloGroup = "Hello"; + Icon prevImage = new GIcon("icon.sample.kitchen.sink.action.hello.world"); + action.setMenuBarData( + new MenuData(new String[] { "Misc", "Hello World" }, prevImage, helloGroup)); + action.setPopupMenuData( + new MenuData(new String[] { "Hello World" }, prevImage, helloGroup)); + action.setKeyBindingData(new KeyBindingData(KeyStroke.getKeyStroke('H', Event.CTRL_MASK))); + action.setToolBarData(new ToolBarData(prevImage, helloGroup)); + action.setDescription("Hello World"); + action.setHelpLocation(new HelpLocation("SampleHelpTopic", "KS_Hello_World")); + + tool.addAction(action); + + action = new DockingAction("Hello Program", getName()) { + @Override + public void actionPerformed(ActionContext context) { + Msg.info(this, "Hello Program:: action"); + sayHelloProgram(); + } + }; + action.setEnabled(false); + Icon nextImage = new GIcon("icon.sample.kitchen.sink.action.hello.program"); + action.setMenuBarData( + new MenuData(new String[] { "Misc", "Hello Program" }, nextImage, helloGroup)); + action.setKeyBindingData( + new KeyBindingData(KeyStroke.getKeyStroke(KeyEvent.VK_P, Event.CTRL_MASK))); + action.setToolBarData(new ToolBarData(nextImage, helloGroup)); + action.setDescription("Hello Program"); + action.setHelpLocation(new HelpLocation("SampleHelpTopic", "KS_Hello_Program")); + tool.addAction(action); + + // remember this action so I can enable/disable it later + helloProgramAction = action; + } + @Override - protected void programDeactivated(Program deactivatedProgram) { + protected void programActivated(Program activatedProgram) { + helloProgramAction.setEnabled(true); + this.program = activatedProgram; + } + + @Override + protected void programDeactivated(Program deactivatedProgram) { if (this.program == deactivatedProgram) { helloProgramAction.setEnabled(false); this.program = null; } } - protected void sayHelloProgram() { - if (program == null) { - return; - } + protected void sayHelloProgram() { - announce("Hello " + program.getName()); - } + if (program == null) { + return; + } - protected void announce(String message) { - JOptionPane.showMessageDialog(null,message,"Hello World", - JOptionPane.INFORMATION_MESSAGE); - } - - /** + announce("Hello " + program.getName()); + } + + protected void announce(String message) { + JOptionPane.showMessageDialog(null, message, "Hello World", + JOptionPane.INFORMATION_MESSAGE); + } + + /** * If your plugin maintains configuration state, you must save that state information - * to the SaveState object in this method. For example, the Code Browser can be configured - * to show fields in different colors. This is the method where that type - * information is saved. + * to the SaveState object in this method. For example, the Code Browser can be configured + * to show fields in different colors. This is the method where that type + * information is saved. */ - @Override - public void writeConfigState(SaveState saveState) { - } + @Override + public void writeConfigState(SaveState saveState) { + } + /** * If your plugin maintains configuration state, this is where you read it - * back in. + * back in. */ - @Override - public void readConfigState(SaveState saveState) { - } + @Override + public void readConfigState(SaveState saveState) { + } } - - - - - diff --git a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/ShowInfoComponentProvider.java b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/ShowInfoComponentProvider.java index c1b5de8566..6e61ae6b17 100644 --- a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/ShowInfoComponentProvider.java +++ b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/ShowInfoComponentProvider.java @@ -23,15 +23,15 @@ import docking.ActionContext; import docking.WindowPosition; import docking.action.DockingAction; import docking.action.ToolBarData; +import generic.theme.GIcon; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.listing.*; import ghidra.program.util.ProgramLocation; -import resources.ResourceManager; public class ShowInfoComponentProvider extends ComponentProviderAdapter { - private final static ImageIcon CLEAR_ICON = ResourceManager.loadImage("images/erase16.png"); - private final static ImageIcon INFO_ICON = ResourceManager.loadImage("images/information.png"); + private final static Icon CLEAR_ICON = new GIcon("icon.sample.hello.world.action.clear"); + private final static Icon PROVIDER_ICON = new GIcon("icon.sample.provider.hello.world"); private JPanel panel; private JTextArea textArea; @@ -42,7 +42,7 @@ public class ShowInfoComponentProvider extends ComponentProviderAdapter { public ShowInfoComponentProvider(PluginTool tool, String name) { super(tool, name, name); create(); - setIcon(INFO_ICON); + setIcon(PROVIDER_ICON); setDefaultWindowPosition(WindowPosition.BOTTOM); setTitle("Show Info"); setVisible(true); diff --git a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/SampleGraphPlugin.java b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/SampleGraphPlugin.java index 8b5bb9b706..8ce17a82d1 100644 --- a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/SampleGraphPlugin.java +++ b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/SampleGraphPlugin.java @@ -15,17 +15,17 @@ */ package ghidra.examples.graph; -import javax.swing.ImageIcon; +import javax.swing.Icon; import docking.ActionContext; import docking.action.DockingAction; import docking.action.ToolBarData; +import generic.theme.GIcon; import ghidra.app.ExamplesPluginPackage; import ghidra.app.plugin.PluginCategoryNames; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; import ghidra.util.HelpLocation; -import resources.ResourceManager; /** * Sample plugin to demonstrate a plugin with a dockable GUI graph component @@ -64,8 +64,7 @@ public class SampleGraphPlugin extends Plugin { showProvider(); } }; - - ImageIcon icon = ResourceManager.loadImage("images/applications-development.png"); + Icon icon = new GIcon("icon.sample.action.show.graph"); showProviderAction.setToolBarData(new ToolBarData(icon, "View")); showProviderAction.setHelpLocation(DEFAULT_HELP); tool.addAction(showProviderAction); @@ -75,7 +74,6 @@ public class SampleGraphPlugin extends Plugin { provider.setVisible(true); } - @Override protected void dispose() { provider.dispose(); diff --git a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/layout/SampleGraphLayoutProvider.java b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/layout/SampleGraphLayoutProvider.java index 8bb3a27ec5..9e09fefab3 100644 --- a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/layout/SampleGraphLayoutProvider.java +++ b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/layout/SampleGraphLayoutProvider.java @@ -21,12 +21,12 @@ import java.util.Collection; import javax.swing.Icon; import edu.uci.ics.jung.algorithms.layout.Layout; +import generic.theme.GIcon; import ghidra.examples.graph.*; import ghidra.graph.viewer.layout.AbstractLayoutProvider; import ghidra.graph.viewer.layout.VisualGraphLayout; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -import resources.ResourceManager; /** * The layout provider for the {@link SampleGraphPlugin}. @@ -34,7 +34,7 @@ import resources.ResourceManager; public abstract class SampleGraphLayoutProvider extends AbstractLayoutProvider { - private static final Icon DEFAULT_ICON = ResourceManager.loadImage("images/color_swatch.png"); + private static final Icon DEFAULT_ICON = new GIcon("icon.sample.provider.graph"); @Override public abstract VisualGraphLayout getLayout(SampleGraph g, diff --git a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/layout/SampleGraphPluginDependencyLayoutProvider.java b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/layout/SampleGraphPluginDependencyLayoutProvider.java index 612f7a18cb..14b7bd72a7 100644 --- a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/layout/SampleGraphPluginDependencyLayoutProvider.java +++ b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/layout/SampleGraphPluginDependencyLayoutProvider.java @@ -17,12 +17,12 @@ package ghidra.examples.graph.layout; import javax.swing.Icon; +import generic.theme.GIcon; import ghidra.examples.graph.*; import ghidra.graph.viewer.layout.AbstractLayoutProvider; import ghidra.graph.viewer.layout.VisualGraphLayout; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -import resources.ResourceManager; /** * A layout provider for the {@link SampleGraphPlugin} @@ -31,7 +31,7 @@ public class SampleGraphPluginDependencyLayoutProvider extends AbstractLayoutProvider { private static final String NAME = "Plugin Dependency Layout"; - private static final Icon DEFAULT_ICON = ResourceManager.loadImage("images/color_swatch.png"); + private static final Icon DEFAULT_ICON = new GIcon("icon.sample.graph.dependency.layout"); @Override public VisualGraphLayout getLayout(SampleGraph g, TaskMonitor monitor) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/MemSearchPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/MemSearchPlugin.java index c195a299bb..1ccd817b04 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/MemSearchPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchmem/MemSearchPlugin.java @@ -94,8 +94,6 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener, private int searchLimit; private static final Icon SEARCH_MARKER_ICON = new GIcon("icon.base.search.marker"); - private Color defaultHighlightColor; - private Color activeHighlightColor; private boolean doHighlight; private int byteGroupSize; private String byteDelimiter; @@ -389,10 +387,10 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener, opt.registerOption(PluginConstants.SEARCH_HIGHLIGHT_NAME, true, null, "Toggles highlight search results"); - opt.registerOption(PluginConstants.SEARCH_HIGHLIGHT_COLOR_NAME, - PluginConstants.SEARCH_HIGHLIGHT_COLOR, null, "The search result highlight color"); - opt.registerOption(PluginConstants.SEARCH_HIGHLIGHT_CURRENT_COLOR_NAME, - PluginConstants.SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR, null, + opt.registerThemeColorOption(PluginConstants.SEARCH_HIGHLIGHT_COLOR_OPTION_NAME, + PluginConstants.SEARCH_HIGHLIGHT_COLOR_ID, null, "The search result highlight color"); + opt.registerThemeColorOption(PluginConstants.SEARCH_HIGHLIGHT_CURRENT_COLOR_OPTION_NAME, + PluginConstants.SEARCH_HIGHLIGHT_CURRENT_COLOR_ID, null, "The search result highlight color for the currently selected match"); opt.addOptionsChangeListener(this); @@ -413,10 +411,6 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener, prepopulateSearch = opt.getBoolean(PluginConstants.PRE_POPULATE_MEM_SEARCH, true); autoRestrictSelection = opt.getBoolean(PluginConstants.AUTO_RESTRICT_SELECTION, true); doHighlight = opt.getBoolean(PluginConstants.SEARCH_HIGHLIGHT_NAME, true); - defaultHighlightColor = opt.getColor(PluginConstants.SEARCH_HIGHLIGHT_COLOR_NAME, - PluginConstants.SEARCH_HIGHLIGHT_COLOR); - activeHighlightColor = opt.getColor(PluginConstants.SEARCH_HIGHLIGHT_CURRENT_COLOR_NAME, - PluginConstants.SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR); opt = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); byteGroupSize = opt.getInt(BytesFieldFactory.BYTE_GROUP_SIZE_MSG, 1); @@ -747,7 +741,7 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener, private Color getHighlightColor(Address highlightStart, int highlightLength) { ProgramLocation location = navigatable != null ? navigatable.getLocation() : null; if (!(location instanceof BytesFieldLocation)) { - return defaultHighlightColor; + return PluginConstants.SEARCH_HIGHLIGHT_COLOR; } BytesFieldLocation byteLoc = (BytesFieldLocation) location; @@ -755,11 +749,12 @@ public class MemSearchPlugin extends Plugin implements OptionsChangeListener, if (highlightStart.hasSameAddressSpace(byteAddress)) { long diff = byteAddress.subtract(highlightStart); if (diff >= 0 && diff < highlightLength) { - return activeHighlightColor; // the current location is in the highlight + // the current location is in the highlight + return PluginConstants.SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR; } } - return defaultHighlightColor; + return PluginConstants.SEARCH_HIGHLIGHT_COLOR; } private boolean checkRemoveHighlights() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java index 44e851b813..e44b83f771 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/searchtext/SearchTextPlugin.java @@ -15,7 +15,8 @@ */ package ghidra.app.plugin.core.searchtext; -import java.awt.*; +import java.awt.Component; +import java.awt.KeyboardFocusManager; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; @@ -99,8 +100,6 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList private int searchLimit; private SearchTask currentTask; private String lastSearchedText; - private Color highlightColor; - private Color currentAddrHighlightColor; private boolean doHighlight; private Navigatable navigatable; @@ -424,12 +423,6 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList } searchLimit = newSearchLimit; } - else if (optionName.equals(PluginConstants.SEARCH_HIGHLIGHT_CURRENT_COLOR_NAME)) { - currentAddrHighlightColor = (Color) newValue; - } - else if (optionName.equals(PluginConstants.SEARCH_HIGHLIGHT_COLOR_NAME)) { - highlightColor = (Color) newValue; - } else if (optionName.equals(PluginConstants.SEARCH_HIGHLIGHT_NAME)) { doHighlight = ((Boolean) newValue).booleanValue(); } @@ -442,22 +435,16 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList opt.registerOption(PluginConstants.SEARCH_HIGHLIGHT_NAME, true, loc, "Determines whether to highlight the matched string for a search in the listing."); - opt.registerOption(PluginConstants.SEARCH_HIGHLIGHT_COLOR_NAME, - PluginConstants.SEARCH_HIGHLIGHT_COLOR, loc, - "Color to use when highlighting the matched string for a search in the listing."); - opt.registerOption(PluginConstants.SEARCH_HIGHLIGHT_CURRENT_COLOR_NAME, - PluginConstants.SEARCH_HIGHLIGHT_COLOR, loc, - "Color to use for highlighting when the match string occurs at the current address."); + opt.registerThemeColorOption(PluginConstants.SEARCH_HIGHLIGHT_COLOR_OPTION_NAME, + PluginConstants.SEARCH_HIGHLIGHT_COLOR_ID, null, "The search result highlight color"); + opt.registerThemeColorOption(PluginConstants.SEARCH_HIGHLIGHT_CURRENT_COLOR_OPTION_NAME, + PluginConstants.SEARCH_HIGHLIGHT_CURRENT_COLOR_ID, null, + "The search result highlight color for the currently selected match"); searchLimit = opt.getInt(GhidraOptions.OPTION_SEARCH_LIMIT, PluginConstants.DEFAULT_SEARCH_LIMIT); doHighlight = opt.getBoolean(PluginConstants.SEARCH_HIGHLIGHT_NAME, true); - highlightColor = opt.getColor(PluginConstants.SEARCH_HIGHLIGHT_COLOR_NAME, - PluginConstants.SEARCH_HIGHLIGHT_COLOR); - currentAddrHighlightColor = - opt.getColor(PluginConstants.SEARCH_HIGHLIGHT_CURRENT_COLOR_NAME, - PluginConstants.SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR); opt.setOptionsHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Search_Text")); @@ -661,12 +648,14 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList int start = matcher.start(); int end = matcher.end() - 1; if (start <= cursorTextOffset && end >= cursorTextOffset) { - list.add(new Highlight(start, end, currentAddrHighlightColor)); + list.add(new Highlight(start, end, + PluginConstants.SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR)); } else if (loc == null) { // only add in matches around current match if loc is null // meaning that this is a one at a time search and not a table // of results. - list.add(new Highlight(start, end, highlightColor)); + list.add(new Highlight(start, end, + PluginConstants.SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR)); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/PluginConstants.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/PluginConstants.java index 310d8d2a15..a11d2be67e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/PluginConstants.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/PluginConstants.java @@ -77,17 +77,10 @@ public interface PluginConstants { * Name of the Options object for Search. */ public static final String SEARCH_OPTION_NAME = "Search"; - /** - * Option name for highlight color - */ - public static final String SEARCH_HIGHLIGHT_COLOR_NAME = " Highlight Color"; - /** * Option name for highlight color used when something to highlight is at the current * address. */ - public static final String SEARCH_HIGHLIGHT_CURRENT_COLOR_NAME = - "Highlight Color for Current Match"; /** * Option name for whether to highlight search results. */ @@ -96,13 +89,19 @@ public interface PluginConstants { /** * Color for highlighting for searches. */ - public static final Color SEARCH_HIGHLIGHT_COLOR = new GColor("color.bg.search.highlight"); + public static final String SEARCH_HIGHLIGHT_COLOR_OPTION_NAME = " Highlight Color"; + public static final String SEARCH_HIGHLIGHT_COLOR_ID = "color.bg.search.highlight"; + public static final Color SEARCH_HIGHLIGHT_COLOR = new GColor(SEARCH_HIGHLIGHT_COLOR_ID); /** * Default highlight color used when something to highlight is at the current * address. */ + public static final String SEARCH_HIGHLIGHT_CURRENT_COLOR_OPTION_NAME = + "Highlight Color for Current Match"; + public static final String SEARCH_HIGHLIGHT_CURRENT_COLOR_ID = + "color.bg.search.current.line.highlight"; public static final Color SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR = - new GColor("color.bg.search.current.line.highlight"); + new GColor(SEARCH_HIGHLIGHT_CURRENT_COLOR_ID); } diff --git a/Ghidra/Framework/Docking/data/docking.theme.properties b/Ghidra/Framework/Docking/data/docking.theme.properties index 95dbd1ce53..e30b607fe0 100644 --- a/Ghidra/Framework/Docking/data/docking.theme.properties +++ b/Ghidra/Framework/Docking/data/docking.theme.properties @@ -88,6 +88,8 @@ color.fg.fieldpanel = color.fg color.bg.fieldpanel.selection = color.bg.selection color.bg.fieldpanel.highlight = color.bg.highlight color.bg.fieldpanel.selection.and.highlight = green +// docking buttons +color.fg.button = black // Icons files icon.empty = EmptyIcon16.gif @@ -207,4 +209,6 @@ color.bg.tableheader.gradient.end = darkGray color.bg.tableheader.gradient.start.primary = color.bg color.bg.tableheader.gradient.end.primary = darkBlue +// docking buttons +color.fg.button = darkGray diff --git a/Ghidra/Framework/Docking/src/main/java/docking/CloseIcon.java b/Ghidra/Framework/Docking/src/main/java/docking/CloseIcon.java new file mode 100644 index 0000000000..77f9db4ad7 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/CloseIcon.java @@ -0,0 +1,67 @@ +/* ### + * 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; + +import java.awt.*; + +import javax.swing.Icon; + +/** + * Icon for a close button + */ +public class CloseIcon implements Icon { + private int size; + private int margin; + private Color color; + + /** + * Creates a close icon. + * @param size the width and height of the icon + * @param margin the margin around the "x" + * @param color the color of the "x" + */ + public CloseIcon(int size, int margin, Color color) { + this.size = size; + this.margin = margin; + this.color = color; + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + g.setColor(color); + int xStart = x + margin; + int yStart = y + margin; + int xEnd = x + size - margin; + int yEnd = y + size - margin; + g.drawLine(xStart, yStart, xEnd, yEnd); + g.drawLine(xStart, yEnd, xEnd, yStart); + g.drawLine(xStart + 1, yStart, xEnd + 1, yEnd); + g.drawLine(xStart + 1, yEnd, xEnd + 1, yStart); + g.drawLine(xStart - 1, yStart, xEnd - 1, yEnd); + g.drawLine(xStart - 1, yEnd, xEnd - 1, yStart); + } + + @Override + public int getIconWidth() { + return size; + } + + @Override + public int getIconHeight() { + return size; + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockableToolBarManager.java b/Ghidra/Framework/Docking/src/main/java/docking/DockableToolBarManager.java index 82e8c35169..89ef71a1e1 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/DockableToolBarManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/DockableToolBarManager.java @@ -15,8 +15,7 @@ */ package docking; -import java.awt.Dimension; -import java.awt.FlowLayout; +import java.awt.*; import java.util.Collections; import java.util.Iterator; @@ -24,17 +23,17 @@ import javax.swing.*; import docking.action.*; import docking.menu.*; +import generic.theme.GColor; import ghidra.util.exception.AssertException; import ghidra.util.task.SwingUpdateManager; -import resources.ResourceManager; /** * Manages to toolbar for the dockable components. */ class DockableToolBarManager { - private static final ImageIcon CLOSE_ICON = ResourceManager.loadImage("images/close16.gif"); - private static final ImageIcon MENU_ICON = ResourceManager.loadImage("images/menu16.gif"); - + private static final Color BUTTON_COLOR = new GColor("color.fg.button"); + private static final Icon CLOSE_ICON = new CloseIcon(16, 4, BUTTON_COLOR); + private static final Icon MENU_ICON = new DropDownMenuIcon(16, 4, 4, BUTTON_COLOR); private GenericHeader dockableHeader; private ToolBarManager toolBarManager; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DropDownMenuIcon.java b/Ghidra/Framework/Docking/src/main/java/docking/DropDownMenuIcon.java new file mode 100644 index 0000000000..bf9692d96a --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/DropDownMenuIcon.java @@ -0,0 +1,71 @@ +/* ### + * 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; + +import java.awt.*; + +import javax.swing.Icon; + +/** + * Icon for a drop down menu button (triangle pointing down) + */ +public class DropDownMenuIcon implements Icon { + private int size; + private int xMargin; + private int yMargin; + private Color color; + + /** + * Creates a drop down menu icon. + * @param size the width and height of the icon + * @param xMargin the margin around triangle base + * @param yMargin the margin around triangle height + * @param color the color of the triangle + */ + public DropDownMenuIcon(int size, int xMargin, int yMargin, Color color) { + this.size = size; + this.xMargin = xMargin; + this.yMargin = yMargin; + this.color = color; + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + g.setColor(color); + + // draw a triangle pointing down + int p1x = x + size / 2; + int p1y = y + size - yMargin; + int p2x = x + xMargin; + int p2y = y + yMargin; + int p3x = x + size - xMargin + 1; + int p3y = y + yMargin; + int xPoints[] = { p1x, p2x, p3x }; + int yPoints[] = { p1y, p2y, p3y }; + g.fillPolygon(xPoints, yPoints, 3); + } + + @Override + public int getIconWidth() { + return size; + } + + @Override + public int getIconHeight() { + return size; + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/util/image/ToolIconURL.java b/Ghidra/Framework/Docking/src/main/java/docking/util/image/ToolIconURL.java index a949db7312..3231181e55 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/util/image/ToolIconURL.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/util/image/ToolIconURL.java @@ -21,7 +21,6 @@ import java.util.Hashtable; import javax.swing.ImageIcon; -import generic.Images; import resources.ResourceManager; import resources.icons.UnresolvedIcon; @@ -66,7 +65,7 @@ public class ToolIconURL implements Comparable { */ public ToolIconURL(String location) { if (location == null) { - location = Images.BOMB; + location = ResourceManager.BOMB; } this.location = location; @@ -168,7 +167,7 @@ public class ToolIconURL implements Comparable { // O.K., we will scale the icon. However, if it is the default icon, we know we have // a 'large' version of that. if (unscaledIcon instanceof UnresolvedIcon) { - return ResourceManager.loadImage(Images.BIG_BOMB); + return ResourceManager.loadImage(ResourceManager.BIG_BOMB); } return ResourceManager.getScaledIcon(unscaledIcon, LARGE_ICON_SIZE, LARGE_ICON_SIZE); diff --git a/Ghidra/Framework/Generic/src/main/java/generic/Images.java b/Ghidra/Framework/Generic/src/main/java/generic/Images.java deleted file mode 100644 index 13e762713b..0000000000 --- a/Ghidra/Framework/Generic/src/main/java/generic/Images.java +++ /dev/null @@ -1,22 +0,0 @@ -/* ### - * IP: GHIDRA - * REVIEWED: YES - * - * 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; - -public class Images { - public static String BOMB = "images/core.png"; - public static String BIG_BOMB = "images/core24.png"; -} diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/AbstractThemeReader.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/AbstractThemeReader.java index a3846f0492..80b5b95b4d 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/AbstractThemeReader.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/AbstractThemeReader.java @@ -99,7 +99,8 @@ public abstract class AbstractThemeReader { return IconValue.parse(key, value); } catch (ParseException e) { - error(lineNumber, "Could not parse Icon value: " + value + "because " + e.getMessage()); + error(lineNumber, + "Could not parse Icon value: \"" + value + "\" because: " + e.getMessage()); } return null; } diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/Gui.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/Gui.java index 8ee0d48dee..27570ac605 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/Gui.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/Gui.java @@ -61,6 +61,7 @@ public class Gui { private static GThemeValueMap applicationDarkDefaults = new GThemeValueMap(); private static GThemeValueMap javaDefaults = new GThemeValueMap(); private static GThemeValueMap currentValues = new GThemeValueMap(); + private static GThemeValueMap systemValues = new GThemeValueMap(); private static ThemeFileLoader themeFileLoader = new ThemeFileLoader(); private static ThemePreferenceManager themePreferenceManager = new ThemePreferenceManager(); @@ -114,6 +115,72 @@ public class Gui { notifyThemeChanged(new AllValuesChangedThemeEvent(false)); } + /** + * Restores the current color value for the given color id to the value established by the + * current theme. + * @param id the color id to restore back to the original theme value + */ + public static void restoreColor(String id) { + if (changedValuesMap.containsColor(id)) { + Gui.setColor(changedValuesMap.getColor(id)); + } + } + + /** + * Restores the current font value for the given font id to the value established by the + * current theme. + * @param id the font id to restore back to the original theme value + */ + public static void restoreFont(String id) { + if (changedValuesMap.containsFont(id)) { + Gui.setFont(changedValuesMap.getFont(id)); + } + } + + /** + * Restores the current icon value for the given icon id to the value established by the + * current theme. + * @param id the icon id to restore back to the original theme value + */ + public static void restoreIcon(String id) { + if (changedValuesMap.containsIcon(id)) { + Gui.setIcon(changedValuesMap.getIcon(id)); + } + } + + /** + * Returns true if the color associated with the given id has been changed from the current + * theme value for that id. + * @param id the color id to check if it has been changed + * @return true if the color associated with the given id has been changed from the current + * theme value for that id. + */ + public static boolean isChangedColor(String id) { + return changedValuesMap.containsColor(id); + } + + /** + * Returns true if the font associated with the given id has been changed from the current + * theme value for that id. + * @param id the font id to check if it has been changed + * @return true if the font associated with the given id has been changed from the current + * theme value for that id. + */ + public static boolean isChangedFont(String id) { + return changedValuesMap.containsFont(id); + } + + /** + * Returns true if the Icon associated with the given id has been changed from the current + * theme value for that id. + * @param id the Icon id to check if it has been changed + * @return true if the Icon associated with the given id has been changed from the current + * theme value for that id. + */ + public static boolean isChangedIcon(String id) { + return changedValuesMap.containsIcon(id); + } + /** * Sets the application's active theme to the given theme. * @param theme the theme to make active @@ -125,8 +192,8 @@ public class Gui { lookAndFeelManager = lookAndFeel.getLookAndFeelManager(); try { lookAndFeelManager.installLookAndFeel(); - notifyThemeChanged(new AllValuesChangedThemeEvent(true)); themePreferenceManager.saveThemeToPreferences(theme); + notifyThemeChanged(new AllValuesChangedThemeEvent(true)); } catch (Exception e) { Msg.error(Gui.class, @@ -388,6 +455,10 @@ public class Gui { return gIcon; } + public static void setSystemDefaults(GThemeValueMap map) { + systemValues = map; + } + /** * Sets the map of JavaDefaults defined by the current {@link LookAndFeel}. * @param map the default theme values defined by the {@link LookAndFeel} @@ -442,6 +513,7 @@ public class Gui { */ public static GThemeValueMap getDefaults() { GThemeValueMap currentDefaults = new GThemeValueMap(javaDefaults); + currentDefaults.load(systemValues); currentDefaults.load(applicationDefaults); if (activeTheme.useDarkDefaults()) { currentDefaults.load(applicationDarkDefaults); @@ -626,6 +698,7 @@ public class Gui { GThemeValueMap map = new GThemeValueMap(); map.load(javaDefaults); + map.load(systemValues); map.load(applicationDefaults); if (activeTheme.useDarkDefaults()) { map.load(applicationDarkDefaults); diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/IconValue.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/IconValue.java index 4f2451cbaf..09a426078f 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/IconValue.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/IconValue.java @@ -149,11 +149,15 @@ public class IconValue extends ThemeValue { return new IconValue(id, icon, modifier); } - private static Icon getIcon(String baseIconString) { + private static Icon getIcon(String baseIconString) throws ParseException { if (EMPTY_ICON_STRING.equals(baseIconString)) { return new EmptyIcon(STANDARD_EMPTY_ICON_SIZE, STANDARD_EMPTY_ICON_SIZE); } - return ResourceManager.loadImage(baseIconString); + Icon icon = ResourceManager.loadIcon(baseIconString); + if (icon == null) { + throw new ParseException("Can't find icon for \"" + baseIconString + "\"", 0); + } + return icon; } private static IconValue parseRefIcon(String id, String value) throws ParseException { diff --git a/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/LookAndFeelManager.java b/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/LookAndFeelManager.java index 6b2afa1b26..27088002c1 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/LookAndFeelManager.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/theme/laf/LookAndFeelManager.java @@ -84,6 +84,7 @@ public abstract class LookAndFeelManager { IllegalAccessException, UnsupportedLookAndFeelException { cleanUiDefaults(); + Gui.setSystemDefaults(systemToLafMap); doInstallLookAndFeel(); installJavaDefaults(); fixupLookAndFeelIssues(); @@ -262,7 +263,6 @@ public abstract class LookAndFeelManager { */ private void installJavaDefaults() { GThemeValueMap javaDefaults = extractJavaDefaults(); - javaDefaults.load(systemToLafMap); // add in our system color mappings ThemeGrouper grouper = getThemeGrouper(); grouper.group(javaDefaults); Gui.setJavaDefaults(javaDefaults); @@ -331,7 +331,6 @@ public abstract class LookAndFeelManager { Icon icon = UIManager.getIcon(id); values.addIcon(new IconValue(id, icon)); } - return values; } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/AbstractOptions.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/AbstractOptions.java index b199b3f091..dd25542adf 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/AbstractOptions.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/AbstractOptions.java @@ -131,6 +131,10 @@ public abstract class AbstractOptions implements Options { public synchronized void registerOption(String optionName, OptionType type, Object defaultValue, HelpLocation help, String description, PropertyEditor editor) { + if (type == OptionType.COLOR_TYPE) { +// Msg.warn(this, "Registering color: " + optionName, +// ReflectionUtilities.createJavaFilteredThrowable()); + } if (type == OptionType.NO_TYPE) { throw new IllegalArgumentException( "Can't register an option of type: " + OptionType.NO_TYPE); @@ -151,7 +155,7 @@ public abstract class AbstractOptions implements Options { ReflectionUtilities.createJavaFilteredThrowable()); } - Option currentOption = getExistingComptibleOption(optionName, type, defaultValue); + Option currentOption = getExistingComptibleOption(optionName, type); if (currentOption != null) { currentOption.updateRegistration(description, help, defaultValue, editor); return; @@ -163,8 +167,31 @@ public abstract class AbstractOptions implements Options { valueMap.put(optionName, option); } - private Option getExistingComptibleOption(String optionName, OptionType type, - Object defaultValue) { + @Override + public void registerThemeColorOption(String optionName, String colorId, HelpLocation help, + String description) { + Option currentOption = getExistingComptibleOption(optionName, OptionType.COLOR_TYPE); + if (currentOption != null && currentOption instanceof ThemeColorOption) { + currentOption.updateRegistration(description, help, null, null); + return; + } + Option option = new ThemeColorOption(optionName, colorId, description, help); + valueMap.put(optionName, option); + } + + @Override + public void registerThemeFontOption(String optionName, String fontId, HelpLocation help, + String description) { + Option currentOption = getExistingComptibleOption(optionName, OptionType.FONT_TYPE); + if (currentOption != null && currentOption instanceof ThemeFontOption) { + currentOption.updateRegistration(description, help, null, null); + return; + } + Option option = new ThemeFontOption(optionName, fontId, description, help); + valueMap.put(optionName, option); + } + + private Option getExistingComptibleOption(String optionName, OptionType type) { // There are several cases where an existing option may exist when registering an option // 1) the option was accessed before it was registered diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java index 256d38df63..8c21f59f29 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java @@ -149,6 +149,32 @@ public interface Options { public void registerOption(String optionName, OptionType type, Object defaultValue, HelpLocation help, String description, PropertyEditor editor); + /** + * Registers a theme color as an option. Doing this allows the color to be edited in the + * options Gui, but all changes result in a theme change and not an options change. In other + * words, changes to this option causes a theme change and is saved to a theme instead of in + * the tool with normal options + * @param optionName the option name to bind to a theme color id + * @param colorId the theme color id to be affected if a users changes this color option + * @param help the {@link HelpLocation} for more information on how this color is used + * @param description a short description of how this color is used + */ + public void registerThemeColorOption(String optionName, String colorId, HelpLocation help, + String description); + + /** + * Registers a theme font as an option. Doing this allows the font to be edited in the + * options Gui, but all changes result in a theme change and not an options change. In other + * words, changes to this option causes a theme change and is saved to a theme instead of in + * the tool with normal options + * @param optionName the option name to bind to a theme font id + * @param fontId the theme font id to be affected if a users changes this font option + * @param help the {@link HelpLocation} for more information on how this font is used + * @param description a short description of how this font is used + */ + public void registerThemeFontOption(String optionName, String fontId, HelpLocation help, + String description); + /** * Register the options editor that will handle the editing for all the options or a sub group of options. * @param editor the custom editor panel to be used to edit the options or sub group of options. diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/SubOptions.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/SubOptions.java index 734bbccf2f..9d3cdbbffe 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/SubOptions.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/SubOptions.java @@ -92,6 +92,18 @@ public class SubOptions implements Options { options.registerOption(prefix + optionName, type, defaultValue, help, description, editor); } + @Override + public void registerThemeColorOption(String optionName, String colorId, HelpLocation help, + String description) { + options.registerThemeColorOption(prefix + optionName, colorId, help, description); + } + + @Override + public void registerThemeFontOption(String optionName, String fontId, HelpLocation help, + String description) { + options.registerThemeFontOption(prefix + optionName, fontId, help, description); + } + @Override public void putObject(String optionName, Object obj) { options.putObject(prefix + optionName, obj); diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/ThemeColorOption.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/ThemeColorOption.java new file mode 100644 index 0000000000..40af93f4f5 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/ThemeColorOption.java @@ -0,0 +1,58 @@ +/* ### + * 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.framework.options; + +import java.awt.Color; + +import generic.theme.Gui; +import ghidra.util.HelpLocation; + +/** + * Options implementation for theme color options. A ThemeColorOption is an option that, when + * changed, affects the current theme and is saved in the theme, instead of being saved with + * normal non-theme related options. + */ +public class ThemeColorOption extends Option { + + private String colorId; + + public ThemeColorOption(String optionName, String colorId, String description, + HelpLocation help) { + super(optionName, OptionType.COLOR_TYPE, description, help, null, true, null); + this.colorId = colorId; + } + + @Override + public Color getCurrentValue() { + return Gui.getColor(colorId); + } + + @Override + public void doSetCurrentValue(Object value) { + Gui.setColor(colorId, (Color) value); + } + + @Override + public boolean isDefault() { + return !Gui.isChangedColor(colorId); + } + + @Override + public void restoreDefault() { + Gui.restoreColor(colorId); + } + +} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/ThemeFontOption.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/ThemeFontOption.java new file mode 100644 index 0000000000..5174dd862e --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/ThemeFontOption.java @@ -0,0 +1,58 @@ +/* ### + * 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.framework.options; + +import java.awt.Font; + +import generic.theme.Gui; +import ghidra.util.HelpLocation; + +/** + * Options implementation for theme font options. A ThemeFontOption is an option that, when + * changed, affects the current theme and is saved in the theme, instead of being saved with + * normal non-theme related options. + */ +public class ThemeFontOption extends Option { + + private String fontId; + + public ThemeFontOption(String optionName, String fontId, String description, + HelpLocation help) { + super(optionName, OptionType.FONT_TYPE, description, help, null, true, null); + this.fontId = fontId; + } + + @Override + public Font getCurrentValue() { + return Gui.getFont(fontId); + } + + @Override + public void doSetCurrentValue(Object value) { + Gui.setFont(fontId, (Font) value); + } + + @Override + public boolean isDefault() { + return !Gui.isChangedFont(fontId); + } + + @Override + public void restoreDefault() { + Gui.restoreFont(fontId); + } + +} diff --git a/Ghidra/Framework/Generic/src/main/java/resources/IconProvider.java b/Ghidra/Framework/Generic/src/main/java/resources/IconProvider.java index 1ea123f154..823271da59 100644 --- a/Ghidra/Framework/Generic/src/main/java/resources/IconProvider.java +++ b/Ghidra/Framework/Generic/src/main/java/resources/IconProvider.java @@ -24,7 +24,6 @@ import java.net.URL; import javax.swing.Icon; import javax.swing.ImageIcon; -import generic.Images; import generic.util.image.ImageUtils; import ghidra.util.Msg; @@ -123,6 +122,6 @@ public class IconProvider { } private URL getDefaultUrl() { - return ResourceManager.getResource(Images.BOMB); + return ResourceManager.getResource(ResourceManager.BOMB); } } diff --git a/Ghidra/Framework/Generic/src/main/java/resources/ResourceManager.java b/Ghidra/Framework/Generic/src/main/java/resources/ResourceManager.java index 39ba9ff0b6..ccf832795c 100644 --- a/Ghidra/Framework/Generic/src/main/java/resources/ResourceManager.java +++ b/Ghidra/Framework/Generic/src/main/java/resources/ResourceManager.java @@ -15,13 +15,13 @@ */ package resources; -import java.awt.Image; -import java.awt.MediaTracker; +import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; import java.net.*; import java.nio.file.Path; import java.util.*; +import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.stream.Collectors; @@ -30,7 +30,6 @@ import javax.swing.*; import org.apache.commons.lang3.StringUtils; -import generic.Images; import generic.theme.GIcon; import ghidra.framework.Application; import ghidra.util.Msg; @@ -48,15 +47,17 @@ 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 iconMap = new HashMap<>(); + public static final String BOMB = "images/core.png"; + public static final String BIG_BOMB = "images/core24.png"; + public static final String EXTERNAL_ICON_PREFIX = "[EXTERNAL]"; + + private static final Map iconMap = new HashMap<>(); private static List defaultSearchPaths; private static List testSearchPaths; private static ClassLoader classLoader = ResourceManager.class.getClassLoader(); + private static final ImageIcon DEFAULT_ICON = loadDefaultIcon(); /** * Finds a resource with a given name. This method returns null if no @@ -441,26 +442,6 @@ public class ResourceManager { if (icon instanceof GIcon) { return ((GIcon) icon).getId(); } - - /* - TODO - not sure why we wanted just the name and not the entire URL? Delete this - after a bit - - if (iconName == null) { - return null; - } - - int pos = iconName.lastIndexOf(File.separator); - if (pos >= 0) { - iconName = iconName.substring(pos + 1); - } - else { - pos = iconName.lastIndexOf("/"); - if (pos >= 0) { - iconName = iconName.substring(pos + 1); - } - } - */ return iconName; } @@ -496,7 +477,7 @@ public class ResourceManager { if (loadImage == null) { return null; } - return (ImageIcon) getScaledIcon(loadImage, width, height); + return getScaledIcon(loadImage, width, height); } /** @@ -520,24 +501,43 @@ public class ResourceManager { return icon; } + /** + * Load the icon specified by iconPath. The iconPath can be either a path to a resource on + * the classpath or a relative or absolute path to an icon on the file system. If the iconPath + * is a path to a classpath resource, then it will be searched directly or also with an "images/" + * prepended to the path. For example, if there exists an icon "home.gif" on the classpath that + * was stored in the standard "images" resource directory, then it exists on the classpath + * as "images/home.gif". That icon will be found if the iconPath is either "images/home.gif" or + * just as "home.gif". + * + * @param iconPath name of file to load, e.g., "images/home.gif" + * @return an Icon from the given iconPath or null, if no such icon can be found + */ + + public static Icon loadIcon(String iconPath) { + ImageIcon icon = iconMap.get(iconPath); + if (icon == null) { + icon = doLoadIcon(iconPath); + iconMap.put(iconPath, icon == null ? DEFAULT_ICON : icon); + } + + return icon == DEFAULT_ICON ? null : icon; + } + /** * Load the image specified by filename; returns the default bomb icon * if problems occur trying to load the file. * - * @param filename name of file to load, e.g., "images/home.gif" + * @param iconPath name of file to load, e.g., "images/home.gif" * @return the image icon stored in the bytes */ - public static ImageIcon loadImage(String filename) { - ImageIcon icon = iconMap.get(filename); + public static ImageIcon loadImage(String iconPath) { + ImageIcon icon = iconMap.get(iconPath); if (icon == null) { - icon = doLoadIcon(filename); - if (icon == null) { - Msg.warn(ResourceManager.class, "Can't resolve icon: " + filename); - icon = new UnresolvedIcon(filename, getDefaultIcon()); - } - iconMap.put(filename, icon); + icon = doLoadIcon(iconPath); + iconMap.put(iconPath, icon == null ? DEFAULT_ICON : icon); } - return icon; + return icon == null ? new UnresolvedIcon(iconPath, DEFAULT_ICON) : icon; } public static Set getLoadedUrlIcons() { @@ -551,7 +551,7 @@ public class ResourceManager { return icons; } - private static ImageIcon doLoadIcon(String path) { + private static UrlImageIcon 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)) { @@ -628,14 +628,6 @@ public class ResourceManager { } public static ImageIcon getDefaultIcon() { - if (DEFAULT_ICON == null) { - URL url = getResource(DEFAULT_ICON_FILENAME); - if (url == null) { - Msg.error(ResourceManager.class, - "Could not find default icon: " + DEFAULT_ICON_FILENAME); - } - DEFAULT_ICON = new UrlImageIcon(DEFAULT_ICON_FILENAME, url); - } return DEFAULT_ICON; } @@ -645,6 +637,16 @@ public class ResourceManager { return list; } + private static ImageIcon loadDefaultIcon() { + URL url = getResource(BOMB); + if (url != null) { + return new UrlImageIcon(BOMB, url); + } + Msg.error(ResourceManager.class, + "Could not find default icon: " + BOMB); + return getImageIcon(new ColorIcon3D(Color.RED, 16, 16)); + } + private static void filterImages(Set set) { Iterator it = set.iterator(); while (it.hasNext()) { diff --git a/Ghidra/Framework/Help/src/main/java/help/CustomTOCView.java b/Ghidra/Framework/Help/src/main/java/help/CustomTOCView.java index 942bffb27d..4764282c01 100644 --- a/Ghidra/Framework/Help/src/main/java/help/CustomTOCView.java +++ b/Ghidra/Framework/Help/src/main/java/help/CustomTOCView.java @@ -74,6 +74,9 @@ public class CustomTOCView extends TOCView { } public HelpModel getHelpModel() { + if (ui == null) { + return null; + } return ui.getHelpModel(); }