diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/CursorBackgroundColorModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/CursorBackgroundColorModel.java
index 32ac0957b0..fb97c718fb 100644
--- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/CursorBackgroundColorModel.java
+++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/listing/CursorBackgroundColorModel.java
@@ -36,7 +36,7 @@ class CursorBackgroundColorModel implements ListingBackgroundColorModel {
private AddressIndexMap addressIndexMap;
@AutoOptionConsumed(category = {}, name = GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR)
- private Color cursorColor = GhidraOptions.DEFAULT_CURSOR_LINE_COLOR;
+ private Color cursorLineColor = GhidraOptions.DEFAULT_CURSOR_LINE_COLOR;
@AutoOptionConsumed(category = {}, name = GhidraOptions.HIGHLIGHT_CURSOR_LINE)
private boolean doHighlight = true;
@SuppressWarnings("unused")
@@ -61,7 +61,7 @@ class CursorBackgroundColorModel implements ListingBackgroundColorModel {
if (!Objects.equals(cursorAddress, address)) {
return defaultBackgroundColor;
}
- return cursorColor;
+ return cursorLineColor;
}
@Override
diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest
index 614ae99c4f..bc1a3136e7 100644
--- a/Ghidra/Features/Base/certification.manifest
+++ b/Ghidra/Features/Base/certification.manifest
@@ -16,6 +16,10 @@ data/ElfFunctionsThatDoNotReturn||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END|
data/MachOFunctionsThatDoNotReturn||GHIDRA||||END|
data/PEFunctionsThatDoNotReturn||GHIDRA||||END|
+data/base.functiongraph.theme.properties||GHIDRA||||END|
+data/base.listing.theme.properties||GHIDRA||||END|
+data/base.theme.properties||GHIDRA||||END|
+data/decompiler.theme.properties||GHIDRA||||END|
data/file_extension_icons.xml||GHIDRA||||END|
data/functionTags.xml||GHIDRA||||END|
data/ms_pe_rich_products.xml||GHIDRA||||END|
diff --git a/Ghidra/Features/Base/data/base.functiongraph.theme.properties b/Ghidra/Features/Base/data/base.functiongraph.theme.properties
new file mode 100644
index 0000000000..12fd2810c0
--- /dev/null
+++ b/Ghidra/Features/Base/data/base.functiongraph.theme.properties
@@ -0,0 +1,31 @@
+[Defaults]
+
+
+color.bg.functiongraph = color.bg
+
+color.bg.functiongraph.vertex.group = rgb(226, 255, 155)
+color.bg.functiongraph.vertex.entry = color.palette.lightgreen
+color.bg.functiongraph.vertex.exit = color.palette.lightred
+color.bg.functiongraph.vertex.picked = color.palette.yellow
+
+color.bg.functiongraph.edge.fall-through = color.flowtype.fall-through
+color.bg.functiongraph.edge.fall-through.highlight = rgb(255, 127, 127)
+color.bg.functiongraph.edge.jump.conditional = color.flowtype.jump.conditional
+color.bg.functiongraph.edge.jump.conditional.highlight = green
+color.bg.functiongraph.edge.jump.unconditional = color.flowtype.jump.unconditional
+color.bg.functiongraph.edge.jump.unconditional.highlight = rgb(127, 127, 255)
+
+
+
+[Dark Defaults]
+
+
+color.bg.functiongraph.vertex.group = rgb(226, 222, 179) // TODO confirm value
+
+color.bg.functiongraph.edge.fall-through.highlight = rgb(165, 76, 80)
+color.bg.functiongraph.edge.jump.conditional.highlight = rgb(95, 160, 196)
+color.bg.functiongraph.edge.jump.unconditional.highlight = rgb(140, 162, 88)
+
+
+
+
diff --git a/Ghidra/Features/Base/data/base.listing.theme.properties b/Ghidra/Features/Base/data/base.listing.theme.properties
new file mode 100644
index 0000000000..5f0a1349d2
--- /dev/null
+++ b/Ghidra/Features/Base/data/base.listing.theme.properties
@@ -0,0 +1,109 @@
+[Defaults]
+
+color.bg.listing = color.bg
+color.bg.currentline.listing = color.bg.currentline
+color.bg.selection.listing = color.bg.selection
+color.bg.highlight.listing = color.bg.highlight
+
+
+color.cursor.focused.listing = color.cursor.focused
+color.cursor.unfocused.listing = color.cursor.unfocused
+
+color.fg.listing.address = color.fg
+color.fg.listing.ref.bad = red
+color.fg.listing.bytes = blue
+color.fg.listing.constant = turquoise
+color.fg.listing.label.unreferenced = black
+color.fg.listing.entrypoint = magenta
+color.fg.listing.comment.auto = lightGray
+color.fg.listing.comment.eol = blue
+color.fg.listing.comment.repeatable = darkOrange
+color.fg.listing.comment.ref-repeatable = comflowerBlue
+color.fg.listing.comment.plate = gray
+color.fg.listing.comment.post = blue
+color.fg.listing.comment.pre = indigo
+color.fg.listing.ref.ext.resolved = teal
+color.fg.listing.fieldname = color.fg
+color.fg.listing.function.callfixup = fuchsia
+color.fg.listing.function.name = blue
+color.fg.listing.function.param = black
+color.fg.listing.function.tag = mediumVioletRed
+color.fg.listing.function.param.auto = gray
+color.fg.listing.function.return-type = black
+color.fg.listing.function.param.custom = indigo
+color.fg.listing.function.param.dynamic = teal
+color.fg.listing.label.local = green
+color.fg.listing.label.non-primary = olive
+color.fg.listing.label.primary = darkBlue
+color.fg.listing.mnemonic.override = deepPink
+color.fg.listing.mnemonic = navy
+color.fg.listing.mnemonic.unimplemented = navy
+color.fg.listing.flow-arrow.inactive = lightGray
+color.fg.listing.flow-arrow.active = color.fg
+color.fg.listing.flow-arrow.selected = limeGreen
+color.fg.listing.separator = color.fg
+color.fg.listing.variable = purple
+color.fg.listing.version-tracking = purple
+color.fg.listing.xref = darkGreen
+color.fg.listing.xref.offcut = gray
+color.fg.listing.xref.read = blue
+color.fg.listing.xref.write = darkOrange
+color.fg.listing.xref.other = color.fg
+color.fg.listing.register = olive
+color.fg.listing.underline = comflowerBlue
+color.fg.listing.pcode.label = blue
+color.fg.listing.pcode.space = blue
+color.fg.listing.pcode.varnode = blue
+color.fg.listing.pcode.userop = blue
+
+
+
+[Dark Defaults]
+
+#color.fg.listing.address = color.fg
+#color.fg.listing.ref.bad = red
+#color.fg.listing.bytes = blue
+#color.fg.listing.constant = turquoise
+#color.fg.listing.label.unreferenced = black
+#color.fg.listing.entrypoint = magenta
+#color.fg.listing.comment.auto = rgb(95,129,157)
+#color.fg.listing.comment.eol = blue
+#color.fg.listing.comment.repeatable = darkOrange
+#color.fg.listing.comment.ref-repeatable = comflowerBlue
+#color.fg.listing.comment.plate = gray
+#color.fg.listing.comment.post = blue
+#color.fg.listing.comment.pre = indigo
+#color.fg.listing.ref.ext.resolved = teal
+#color.fg.listing.fieldname = color.fg
+#color.fg.listing.function.callfixup = fuchsia
+#color.fg.listing.function.name = blue
+#color.fg.listing.function.param = black
+#color.fg.listing.function.tag = mediumVioletRed
+#color.fg.listing.function.param.auto = gray
+#color.fg.listing.function.return-type = black
+#color.fg.listing.function.param.custom = indigo
+#color.fg.listing.function.param.dynamic = teal
+#color.fg.listing.label.local = green
+#color.fg.listing.label.non-primary = olive
+#color.fg.listing.label.primary = darkBlue
+#color.fg.listing.mnemonic.override = deepPink
+#color.fg.listing.mnemonic = lightSlateGray
+#color.fg.listing.mnemonic.unimplemented = navy
+#color.fg.listing.flow-arrow.inactive = lightGray
+#color.fg.listing.flow-arrow.active = color.fg
+#color.fg.listing.flow-arrow.selected = limeGreen
+#color.fg.listing.separator = color.fg
+#color.fg.listing.variable = purple
+#color.fg.listing.version-tracking = purple
+#color.fg.listing.xref = darkGreen
+#color.fg.listing.xref.offcut = gray
+#color.fg.listing.xref.read = blue
+#color.fg.listing.xref.write = darkOrange
+#color.fg.listing.xref.other = color.fg
+#color.fg.listing.register = olive
+#color.fg.listing.underline = comflowerBlue
+#color.fg.listing.pcode.label = blue
+#color.fg.listing.pcode.space = blue
+#color.fg.listing.pcode.varnode = blue
+#color.fg.listing.pcode.userop = blue
+
diff --git a/Ghidra/Features/Base/data/base.theme.properties b/Ghidra/Features/Base/data/base.theme.properties
new file mode 100644
index 0000000000..bec484b2a6
--- /dev/null
+++ b/Ghidra/Features/Base/data/base.theme.properties
@@ -0,0 +1,66 @@
+[Defaults]
+
+color.bg.undefined = rgb(220, 220, 220) // bg for clients displaying undefined functions
+
+color.flowtype.fall-through = red
+color.flowtype.jump.conditional = #007C00 // dark green
+color.flowtype.jump.unconditional = blue
+
+color.bg.table.selection.bundle = lightSkyBlue
+color.fg.table.selection.bundle = black
+color.fg.table.bundle.disabled = darkGray
+color.fg.table.bundle.error = red
+color.fg.table.bundle.busy = gray
+color.fg.table.bundle.inactive = black
+color.fg.table.bundle.active = green
+
+color.bg.splash.infopanel = color.bg
+
+color.bg.table.selected.ghidratable = color.bg
+color.fg.table.ghidratable.equate.bad = red
+color.fg.table.ghidratable.equate = blue
+color.fg.table.ghidratable.suggestion = silver
+
+color.fg.consoletextpane = color.fg
+color.fg.error.consoletextpane = color.fg.error
+
+color.fg.infopanel.version = color.fg
+
+color.fg.interpreterpanel = color.fg
+color.fg.interpreterpanel.error = color.fg.error
+color.fg.listing.highlighter.default = yellow
+color.fg.listing.highlighter.scoped-read = rgb(204,204, 0)
+color.fg.listing.highlighter.scoped-write = green
+
+color.bg.markerservice = color.bg
+
+color.bg.search.highlight = rgb(255,255,200)
+color.bg.search.current-line.highlight = yellow
+
+[Dark Defaults]
+
+color.bg = rgb(40, 42, 46) // TODO this should be in a more generic module
+
+color.flowtype.fall-through = rgb(164, 66, 66)
+color.flowtype.jump.conditional = rgb(95, 129, 157)
+color.flowtype.jump.unconditional = rgb(140, 148, 64)
+
+
+color.bg.table.selection.bundle = darkBlue
+color.fg.table.selection.bundle = gray
+color.fg.table.bundle.disabled = lightGray
+color.fg.table.bundle.error = indianRed
+color.fg.table.bundle.busy = gray
+color.fg.table.bundle.inactive = lightGray
+color.fg.table.bundle.active = limeGreen
+
+
+color.fg.table.ghidratable.equate.bad = indianRed
+color.fg.table.ghidratable.equate = royalBlue
+color.fg.table.ghidratable.suggestion = darkGray
+
+color.bg.search.highlight = rgb(189,183,107)
+color.bg.search.current-line.highlight = gold
+
+color.fg.listing.highlighter.scoped-read = rgb(100,100, 0)
+color.fg.listing.highlighter.scoped-write = forestGreen
\ No newline at end of file
diff --git a/Ghidra/Features/Base/data/decompiler.theme.properties b/Ghidra/Features/Base/data/decompiler.theme.properties
new file mode 100644
index 0000000000..ce4e83c2b4
--- /dev/null
+++ b/Ghidra/Features/Base/data/decompiler.theme.properties
@@ -0,0 +1,31 @@
+[Defaults]
+color.bg.decompiler = color.bg
+color.fg.decompiler = color.fg
+
+color.fg.decompiler.keyword = #0001e6
+color.fg.decompiler.function.name = blue
+color.fg.decompiler.comment = blueViolet
+color.fg.decompiler.variable = #999900 // close to oliveDrab
+color.fg.decompiler.constant = forestGreen
+color.fg.decompiler.type = mediumBlue
+color.fg.decompiler.parameter = darkMagenta
+color.fg.decompiler.global = darkCyan
+
+color.bg.decompiler.middle-mouse = rgba(255,255,0,.5)
+color.bg.decompiler.current-variable = rgba(255,255,0,0.5)
+
+[Dark Defaults]
+
+color.fg.decompiler.keyword = peru
+color.fg.decompiler.function.name = cadetBlue
+color.fg.decompiler.comment = lightSlateGray
+color.fg.decompiler.variable = #999900 // close to oliveDrab
+color.fg.decompiler.constant = forestGreen
+color.fg.decompiler.type = blue
+color.fg.decompiler.parameter = darkMagenta
+color.fg.decompiler.global = darkCyan
+
+
+color.bg.decompiler.middle-mouse = rgb(55,59,65)
+color.bg.decompiler.current-variable = rgb(55, 59, 65)
+
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/GhidraOptions.java b/Ghidra/Features/Base/src/main/java/ghidra/GhidraOptions.java
index 476d3c14c1..d518d1ca7b 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/GhidraOptions.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/GhidraOptions.java
@@ -18,6 +18,7 @@ package ghidra;
import java.awt.Color;
import java.awt.event.MouseEvent;
+import docking.theme.GColor;
import ghidra.framework.options.Options;
/**
@@ -156,7 +157,7 @@ public interface GhidraOptions {
final String HIGHLIGHT_CURSOR_LINE_COLOR = "Cursor." + HIGHLIGHT_CURSOR_LINE_COLOR_OPTION_NAME;
- final Color DEFAULT_CURSOR_LINE_COLOR = new Color(232, 242, 254);
+ final Color DEFAULT_CURSOR_LINE_COLOR = new GColor("color.bg.currentline.listing");
final String HIGHLIGHT_CURSOR_LINE_OPTION_NAME = "Highlight Cursor Line";
@@ -176,6 +177,7 @@ public interface GhidraOptions {
public static enum CURSOR_MOUSE_BUTTON_NAMES {
LEFT(MouseEvent.BUTTON1), MIDDLE(MouseEvent.BUTTON2), RIGHT(MouseEvent.BUTTON3);
+
private int mouseEventID;
CURSOR_MOUSE_BUTTON_NAMES(int mouseEventID) {
@@ -190,10 +192,9 @@ public interface GhidraOptions {
// end cursor highlight
final String OPTION_SELECTION_COLOR = "Selection Colors.Selection Color";
- final Color DEFAULT_SELECTION_COLOR = new Color(180, 255, 180);
+ final Color DEFAULT_SELECTION_COLOR = new GColor("color.bg.selection.listing");
final String OPTION_HIGHLIGHT_COLOR = "Selection Colors.Highlight Color";
- final Color DEFAULT_HIGHLIGHT_COLOR = new Color(255, 255, 180);
+ final Color DEFAULT_HIGHLIGHT_COLOR = new GColor("color.bg.highlight.listing");
final String APPLY_ENABLED = "apply.enabled";
-
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java
index a2e47229dd..c70b6a1be4 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java
@@ -26,6 +26,7 @@ import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import docking.action.DockingAction;
+import docking.theme.GColor;
import docking.widgets.fieldpanel.*;
import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.support.FieldLocation;
@@ -61,12 +62,17 @@ public abstract class AbstractCodeBrowserPlugin
ex
implements CodeViewerService, CodeFormatService, OptionsChangeListener, FormatModelListener,
DomainObjectListener, CodeBrowserPluginInterface {
- private static final Color CURSOR_LINE_COLOR = GhidraOptions.DEFAULT_CURSOR_LINE_COLOR;
private static final String CURSOR_COLOR = "Cursor.Cursor Color - Focused";
private static final String UNFOCUSED_CURSOR_COLOR = "Cursor.Cursor Color - Unfocused";
private static final String BLINK_CURSOR = "Cursor.Blink Cursor";
private static final String MOUSE_WHEEL_HORIZONTAL_SCROLLING = "Mouse.Horizontal Scrolling";
+ //@formatter:off
+ public static final Color IFOCUSED_CURSOR_COLOR = new GColor("color.cursor.focused.listing");
+ public static final Color IUNFOCUSED_CURSOR_COLOR = new GColor("color.cursor.unfocused.listing");
+ public static final Color ICURRENT_LINE_HIGHLIGHT_COLOR = new GColor("color.bg.currentline.listing");
+ //@formatter:on
+
// - Icon -
private ImageIcon CURSOR_LOC_ICON =
ResourceManager.loadImage("images/cursor_arrow_flipped.gif");
@@ -554,14 +560,15 @@ public abstract class AbstractCodeBrowserPlugin
ex
GhidraOptions.DEFAULT_HIGHLIGHT_COLOR, helpLocation,
"The highlight color in the browser.");
- fieldOptions.registerOption(CURSOR_COLOR, Color.RED, helpLocation,
+ fieldOptions.registerOption(CURSOR_COLOR, IFOCUSED_CURSOR_COLOR, helpLocation,
"The color of the cursor in the browser.");
- fieldOptions.registerOption(UNFOCUSED_CURSOR_COLOR, Color.PINK, helpLocation,
+ fieldOptions.registerOption(UNFOCUSED_CURSOR_COLOR, IUNFOCUSED_CURSOR_COLOR, helpLocation,
"The color of the cursor in the browser when the browser does not have focus.");
fieldOptions.registerOption(BLINK_CURSOR, true, helpLocation,
"When selected, the cursor will blink when the containing window is focused.");
- fieldOptions.registerOption(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR, CURSOR_LINE_COLOR,
- helpLocation, "The background color of the line where the cursor is located");
+ fieldOptions.registerOption(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR,
+ ICURRENT_LINE_HIGHLIGHT_COLOR, helpLocation,
+ "The background color of the line where the cursor is located");
fieldOptions.registerOption(GhidraOptions.HIGHLIGHT_CURSOR_LINE, true, helpLocation,
"Toggles highlighting background color of line containing the cursor");
@@ -588,10 +595,10 @@ public abstract class AbstractCodeBrowserPlugin
ex
highlightMarkers.setMarkerColor(color);
}
- color = fieldOptions.getColor(CURSOR_COLOR, Color.RED);
+ color = fieldOptions.getColor(CURSOR_COLOR, IFOCUSED_CURSOR_COLOR);
fieldPanel.setFocusedCursorColor(color);
- color = fieldOptions.getColor(UNFOCUSED_CURSOR_COLOR, Color.PINK);
+ color = fieldOptions.getColor(UNFOCUSED_CURSOR_COLOR, IUNFOCUSED_CURSOR_COLOR);
fieldPanel.setNonFocusCursorColor(color);
Boolean isBlinkCursor = fieldOptions.getBoolean(BLINK_CURSOR, true);
@@ -601,8 +608,8 @@ public abstract class AbstractCodeBrowserPlugin
ex
fieldOptions.getBoolean(MOUSE_WHEEL_HORIZONTAL_SCROLLING, true);
fieldPanel.setHorizontalScrollingEnabled(horizontalScrollingEnabled);
- cursorHighlightColor =
- fieldOptions.getColor(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR, CURSOR_LINE_COLOR);
+ cursorHighlightColor = fieldOptions.getColor(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR,
+ ICURRENT_LINE_HIGHLIGHT_COLOR);
isHighlightCursorLine = fieldOptions.getBoolean(GhidraOptions.HIGHLIGHT_CURSOR_LINE, true);
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/ListingHighlightProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/ListingHighlightProvider.java
index ec521a8159..826741b40c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/ListingHighlightProvider.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/ListingHighlightProvider.java
@@ -26,6 +26,7 @@ import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
+import docking.theme.GColor;
import docking.widgets.fieldpanel.field.FieldElement;
import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.fieldpanel.support.Highlight;
@@ -51,18 +52,15 @@ import ghidra.util.datastruct.Stack;
public class ListingHighlightProvider
implements ButtonPressedListener, OptionsChangeListener, HighlightProvider {
-
- private static final String DISPLAY_HIGHLIGHT_NAME =
- CURSOR_HIGHLIGHT_GROUP + DELIMITER + "Enabled";
-
- private static final String SCOPED_WRITE_HIGHLIGHT_COLOR =
- CURSOR_HIGHLIGHT_GROUP + DELIMITER + "Scoped Write Highlight Color";
-
- private static final String SCOPED_READ_HIGHLIGHT_COLOR =
- CURSOR_HIGHLIGHT_GROUP + DELIMITER + "Scoped Read Highlight Color";
-
- private static final String SCOPE_REGISTER_OPERAND =
- CURSOR_HIGHLIGHT_GROUP + DELIMITER + "Scope Register Operand";
+ //@formatter:off
+ private static final Color DEFAULT_HIGHLIGHT_COLOR = new GColor("color.fg.listing.highlighter.default");
+ private static final Color DEFAULT_SCOPED_READ_COLOR = new GColor("color.fg.listing.highlighter.scoped-read");
+ private static final Color DEFAULT_SCOPED_WRITE_COLOR = new GColor("color.fg.listing.highlighter.scoped-write");
+ private static final String DISPLAY_HIGHLIGHT_NAME = CURSOR_HIGHLIGHT_GROUP + DELIMITER + "Enabled";
+ private static final String SCOPED_WRITE_HIGHLIGHT_COLOR = CURSOR_HIGHLIGHT_GROUP + DELIMITER + "Scoped Write Highlight Color";
+ private static final String SCOPED_READ_HIGHLIGHT_COLOR = CURSOR_HIGHLIGHT_GROUP + DELIMITER + "Scoped Read Highlight Color";
+ private static final String SCOPE_REGISTER_OPERAND = CURSOR_HIGHLIGHT_GROUP + DELIMITER + "Scope Register Operand";
+ //@formatter:on
private static char[] UNDERSCORE_AND_PERIOD_OK = new char[] { '.', '_' };
private static char[] UNDERSCORE_OK = new char[] { '_' };
@@ -871,11 +869,11 @@ public class ListingHighlightProvider
ToolOptions opt = tool.getOptions(CATEGORY_BROWSER_FIELDS);
HelpLocation hl = new HelpLocation("CodeBrowserPlugin", "Cursor_Text_Highlight");
- opt.registerOption(HIGHLIGHT_COLOR_NAME, Color.YELLOW, hl,
+ opt.registerOption(HIGHLIGHT_COLOR_NAME, DEFAULT_HIGHLIGHT_COLOR, hl,
"The color to use to highlight text.");
- opt.registerOption(SCOPED_WRITE_HIGHLIGHT_COLOR, new Color(204, 204, 0), hl,
+ opt.registerOption(SCOPED_WRITE_HIGHLIGHT_COLOR, DEFAULT_SCOPED_WRITE_COLOR, hl,
"The color to use for showing a register being written.");
- opt.registerOption(SCOPED_READ_HIGHLIGHT_COLOR, new Color(0, 255, 0), hl,
+ opt.registerOption(SCOPED_READ_HIGHLIGHT_COLOR, DEFAULT_SCOPED_READ_COLOR, hl,
"The color to use for showing a register being read.");
opt.registerOption(SCOPE_REGISTER_OPERAND, true, hl,
@@ -895,11 +893,11 @@ public class ListingHighlightProvider
setHighlightString(null, null);
}
- textMatchingHighlightColor = opt.getColor(HIGHLIGHT_COLOR_NAME, Color.YELLOW);
-
+ textMatchingHighlightColor = opt.getColor(HIGHLIGHT_COLOR_NAME, DEFAULT_HIGHLIGHT_COLOR);
scopeWriteHighlightColor =
- opt.getColor(SCOPED_WRITE_HIGHLIGHT_COLOR, new Color(204, 204, 0));
- scopeReadHighlightColor = opt.getColor(SCOPED_READ_HIGHLIGHT_COLOR, new Color(0, 255, 0));
+ opt.getColor(SCOPED_WRITE_HIGHLIGHT_COLOR, DEFAULT_SCOPED_WRITE_COLOR);
+ scopeReadHighlightColor =
+ opt.getColor(SCOPED_READ_HIGHLIGHT_COLOR, DEFAULT_SCOPED_READ_COLOR);
/////////////////////////////////////////////////////
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/MarkerServiceBackgroundColorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/MarkerServiceBackgroundColorModel.java
index 3515975d23..17e073cfa1 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/MarkerServiceBackgroundColorModel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/MarkerServiceBackgroundColorModel.java
@@ -18,6 +18,7 @@ package ghidra.app.plugin.core.codebrowser;
import java.awt.Color;
import java.math.BigInteger;
+import docking.theme.GColor;
import docking.widgets.fieldpanel.support.BackgroundColorModel;
import ghidra.app.services.MarkerService;
import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel;
@@ -33,7 +34,7 @@ public class MarkerServiceBackgroundColorModel implements ListingBackgroundColor
private MarkerService markerService;
private Program program;
private AddressIndexMap indexMap;
- private Color defaultBackgroundColor = Color.WHITE;
+ private Color defaultBackgroundColor = new GColor("color.bg.markerservice");
public MarkerServiceBackgroundColorModel(MarkerService markerService, Program program,
AddressIndexMap indexMap) {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterPanel.java
index bf7f1f7ff1..a695204fe8 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterPanel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/InterpreterPanel.java
@@ -28,6 +28,7 @@ import javax.swing.text.*;
import docking.DockingUtils;
import docking.actions.KeyBindingUtils;
+import docking.theme.GColor;
import generic.util.WindowUtilities;
import ghidra.app.plugin.core.console.CodeCompletion;
import ghidra.framework.options.OptionsChangeListener;
@@ -37,6 +38,7 @@ import ghidra.util.*;
public class InterpreterPanel extends JPanel implements OptionsChangeListener {
+ private static final String COLOR_ID = "interpreterpanel.color";
private static final String COMPLETION_WINDOW_TRIGGER_LABEL = "Completion Window Trigger";
private static final String COMPLETION_WINDOW_TRIGGER_DESCRIPTION =
"The key binding used to show the auto-complete window " +
@@ -46,8 +48,8 @@ public class InterpreterPanel extends JPanel implements OptionsChangeListener {
"This is the font that will be used in the Console. " +
"Double-click the font example to change it.";
- private static final Color NORMAL_COLOR = Color.black;
- private static final Color ERROR_COLOR = Color.red;
+ private static final Color NORMAL_COLOR = new GColor("color.fg.interpreterpanel");
+ private static final Color ERROR_COLOR = new GColor("color.fg.interpreterpanel.error");
public enum TextType {
STDOUT, STDERR, STDIN;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java
index 67d27bef6b..650a85e19e 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusComponentProvider.java
@@ -15,17 +15,18 @@
*/
package ghidra.app.plugin.core.osgi;
-import java.awt.*;
+import java.awt.BorderLayout;
+import java.awt.Dimension;
import java.io.File;
import java.io.PrintWriter;
import java.util.*;
-import java.util.List;
import java.util.stream.Collectors;
import javax.swing.*;
import javax.swing.event.TableModelEvent;
import docking.action.builder.ActionBuilder;
+import docking.theme.GColor;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.table.GTable;
@@ -105,8 +106,8 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
bundleStatusTable = new GTable(bundleStatusTableModel);
bundleStatusTable.setName("BUNDLESTATUS_TABLE");
- bundleStatusTable.setSelectionBackground(new Color(204, 204, 255));
- bundleStatusTable.setSelectionForeground(Color.BLACK);
+ bundleStatusTable.setSelectionBackground(new GColor("color.bg.table.selection.bundle"));
+ bundleStatusTable.setSelectionForeground(new GColor("color.fg.table.selection.bundle"));
bundleStatusTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
// give actions a chance to update status when selection changed
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java
index 7f7a957baa..ee2c68f757 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/osgi/BundleStatusTableModel.java
@@ -27,6 +27,7 @@ import javax.swing.event.TableModelEvent;
import org.osgi.framework.Bundle;
+import docking.theme.GColor;
import docking.widgets.table.*;
import generic.jar.ResourceFile;
import generic.util.Path;
@@ -42,11 +43,14 @@ import ghidra.util.table.column.GColumnRenderer;
*/
public class BundleStatusTableModel
extends GDynamicColumnTableModel> {
- private static final Color COLOR_BUNDLE_ERROR = Color.RED;
- private static final Color COLOR_BUNDLE_DISABLED = Color.DARK_GRAY;
- private static final Color COLOR_BUNDLE_BUSY = Color.GRAY;
- private static final Color COLOR_BUNDLE_INACTIVE = Color.BLACK;
- private static final Color COLOR_BUNDLE_ACTIVE = new Color(0.0f, .6f, 0.0f); // a dark green
+
+ //@formatter:off
+ private static final Color COLOR_BUNDLE_ERROR = new GColor("color.fg.table.bundle.error");
+ private static final Color COLOR_BUNDLE_DISABLED = new GColor("color.fg.table.bundle.disabled");
+ private static final Color COLOR_BUNDLE_BUSY = new GColor("color.fg.table.bundle.busy");
+ private static final Color COLOR_BUNDLE_INACTIVE = new GColor("color.fg.table.bundle.inactive");
+ private static final Color COLOR_BUNDLE_ACTIVE = new GColor("color.fg.table.bundle.active");
+ //@formatter:on
private BundleHost bundleHost;
private BundleStatusComponentProvider provider;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/printing/CodeUnitPrintable.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/printing/CodeUnitPrintable.java
index 8a6a2115e9..a14a7519ea 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/printing/CodeUnitPrintable.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/printing/CodeUnitPrintable.java
@@ -49,8 +49,7 @@ public class CodeUnitPrintable implements Printable {
private static final PaintContext PAINT_CONTEXT = new PaintContext();
static {
PAINT_CONTEXT.setForegroundColor(Color.BLACK);
- PAINT_CONTEXT.setDefaultBackgroundColor(Color.WHITE);
- PAINT_CONTEXT.setBackgroundColor(Color.white);
+ PAINT_CONTEXT.setBackgroundColor(Color.WHITE);
PAINT_CONTEXT.setCursorColor(Color.RED);
PAINT_CONTEXT.setSelectionColor(new Color(180, 255, 180));
PAINT_CONTEXT.setHighlightColor(new Color(255, 255, 150));
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/gui/LookAndFeelPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/gui/LookAndFeelPlugin.java
deleted file mode 100644
index b71ec19dda..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/gui/LookAndFeelPlugin.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.gui;
-
-import static ghidra.docking.util.DockingWindowsLookAndFeelUtils.*;
-
-import java.util.List;
-
-import docking.options.editor.StringWithChoicesEditor;
-import docking.tool.ToolConstants;
-import docking.widgets.OptionDialog;
-import ghidra.app.CorePluginPackage;
-import ghidra.app.plugin.PluginCategoryNames;
-import ghidra.docking.util.DockingWindowsLookAndFeelUtils;
-import ghidra.framework.main.ApplicationLevelOnlyPlugin;
-import ghidra.framework.main.FrontEndTool;
-import ghidra.framework.options.*;
-import ghidra.framework.plugintool.*;
-import ghidra.framework.plugintool.util.PluginStatus;
-import ghidra.framework.preferences.Preferences;
-import ghidra.util.*;
-
-//@formatter:off
-@PluginInfo(
- status = PluginStatus.RELEASED,
- packageName = CorePluginPackage.NAME,
- category = PluginCategoryNames.SUPPORT,
- shortDescription = "Sets the GUI look and feel",
- description = "Adds a Tool Option to allow the user to adjust the GUI look and feel settings. " +
- "Ghidra may have to be restarted to see the effect. This plugin is available " +
- "only in the Ghidra Project Window."
-)
-//@formatter:on
-public class LookAndFeelPlugin extends Plugin implements ApplicationLevelOnlyPlugin, OptionsChangeListener {
-
- private String selectedLookAndFeel;
- private boolean useInvertedColors;
- public final static String LOOK_AND_FEEL_NAME = "Swing Look And Feel";
- private final static String USE_INVERTED_COLORS_NAME = "Use Inverted Colors";
- private final static String OPTIONS_TITLE = ToolConstants.TOOL_OPTIONS;
-
- private static boolean issuedLafNotification;
- private static boolean issuedPreferredDarkThemeLafNotification;
-
- public LookAndFeelPlugin(PluginTool tool) {
- super(tool);
-
- SystemUtilities.assertTrue(tool instanceof FrontEndTool,
- "Plugin added to the wrong type of tool");
- initLookAndFeelOptions();
- }
-
- private void initLookAndFeelOptions() {
-
- ToolOptions opt = tool.getOptions(OPTIONS_TITLE);
-
- selectedLookAndFeel = getInstalledLookAndFeelName();
- List lookAndFeelNames = getLookAndFeelNames();
- opt.registerOption(LOOK_AND_FEEL_NAME, OptionType.STRING_TYPE, selectedLookAndFeel,
- new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, "Look_And_Feel"),
- "Set the look and feel for Ghidra. After you change the " +
- "look and feel, you will have to restart Ghidra to see the effect.",
- new StringWithChoicesEditor(lookAndFeelNames));
- selectedLookAndFeel = opt.getString(LOOK_AND_FEEL_NAME, selectedLookAndFeel);
-
- useInvertedColors = getUseInvertedColorsPreference();
- opt.registerOption(USE_INVERTED_COLORS_NAME, OptionType.BOOLEAN_TYPE, useInvertedColors,
- new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, "Use_Inverted_Colors"),
- "Indicates to invert all drawn colors. This provides the ability to create an " +
- "effective 'Dark Theme' appearance. (Note: you may have to change your " +
- "Look and Feel to achieve the best rendering.)\n\n" +
- "PROTOTYPE - This feature is an example prototype " +
- "and contains many known rendering issues that cause some widgets to be " +
- "unreadable.");
- useInvertedColors = opt.getBoolean(USE_INVERTED_COLORS_NAME, useInvertedColors);
-
- opt.addOptionsChangeListener(this);
- }
-
- @Override
- public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
- Object newValue) {
-
- if (optionName.equals(LOOK_AND_FEEL_NAME)) {
- String newLookAndFeel = (String) newValue;
- if (!newLookAndFeel.equals(selectedLookAndFeel)) {
- issueLaFNotification();
- }
-
- saveLookAndFeel((String) newValue);
- }
-
- if (optionName.equals(USE_INVERTED_COLORS_NAME)) {
- boolean newUseInvertedColors = (Boolean) newValue;
- if (newUseInvertedColors != useInvertedColors) {
-
- issueLaFNotification();
- }
-
- useInvertedColors = newUseInvertedColors;
- Preferences.setProperty(USE_INVERTED_COLORS_KEY, Boolean.toString(useInvertedColors));
- Preferences.store();
-
- if (useInvertedColors) {
- issuePreferredDarkThemeLaFNotification();
- }
- }
- }
-
- private void saveLookAndFeel(String lookAndFeelName) {
- selectedLookAndFeel = lookAndFeelName;
- Preferences.setProperty(LAST_LOOK_AND_FEEL_KEY, selectedLookAndFeel);
- Preferences.store();
- }
-
- private void issuePreferredDarkThemeLaFNotification() {
- if (issuedPreferredDarkThemeLafNotification) {
- return;
- }
-
- issuedPreferredDarkThemeLafNotification = true;
-
- if (DockingWindowsLookAndFeelUtils.METAL_LOOK_AND_FEEL.equals(selectedLookAndFeel)) {
- return;
- }
-
- int choice = OptionDialog.showYesNoDialog(null, "Change Look and Feel?", "The '" +
- USE_INVERTED_COLORS_NAME + "' setting works best with the " +
- "'Metal' Look and Feel.\nWould you like to switch to that Look and Feel upon restart?");
- if (choice == OptionDialog.YES_OPTION) {
- SystemUtilities.runSwingLater(() -> {
-
- saveLookAndFeel(DockingWindowsLookAndFeelUtils.METAL_LOOK_AND_FEEL);
- });
- }
- }
-
- private void issueLaFNotification() {
- if (issuedLafNotification) {
- return;
- }
-
- issuedLafNotification = true;
- Msg.showInfo(getClass(), null, "Look And Feel Updated",
- "The new Look and Feel will take effect \nafter you exit and restart Ghidra.");
- }
-
- @Override
- public void dispose() {
- ToolOptions opt = tool.getOptions(OPTIONS_TITLE);
- opt.removeOptionsChangeListener(this);
- super.dispose();
- }
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/gui/ThemeManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/gui/ThemeManagerPlugin.java
new file mode 100644
index 0000000000..b17b173f65
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/gui/ThemeManagerPlugin.java
@@ -0,0 +1,154 @@
+/* ###
+ * 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.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import docking.action.builder.ActionBuilder;
+import docking.options.editor.StringWithChoicesEditor;
+import docking.theme.GTheme;
+import docking.theme.GThemeDialog;
+import docking.theme.Gui;
+import docking.tool.ToolConstants;
+import ghidra.app.CorePluginPackage;
+import ghidra.app.plugin.PluginCategoryNames;
+import ghidra.docking.util.LookAndFeelUtils;
+import ghidra.framework.main.FrontEndOnly;
+import ghidra.framework.main.FrontEndTool;
+import ghidra.framework.options.OptionType;
+import ghidra.framework.options.OptionsChangeListener;
+import ghidra.framework.options.ToolOptions;
+import ghidra.framework.plugintool.Plugin;
+import ghidra.framework.plugintool.PluginInfo;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.framework.plugintool.util.PluginStatus;
+import ghidra.util.HelpLocation;
+import ghidra.util.Msg;
+import ghidra.util.SystemUtilities;
+
+//@formatter:off
+@PluginInfo(
+ status = PluginStatus.RELEASED,
+ packageName = CorePluginPackage.NAME,
+ category = PluginCategoryNames.SUPPORT,
+ shortDescription = "Manages themes for the Ghdira GUI",
+ description = "Adds actions and options to manage Themes within Ghidra. " +
+ "This plugin is available only in the Ghidra Project Window."
+)
+//@formatter:on
+public class ThemeManagerPlugin extends Plugin implements FrontEndOnly, OptionsChangeListener {
+
+ public final static String THEME_OPTIONS_NAME = "Theme";
+ private final static String OPTIONS_TITLE = ToolConstants.TOOL_OPTIONS;
+
+ private boolean issuedRestartNotification;
+// private static boolean issuedPreferredDarkThemeLafNotification;
+
+ public ThemeManagerPlugin(PluginTool tool) {
+ super(tool);
+
+ SystemUtilities.assertTrue(tool instanceof FrontEndTool,
+ "Plugin added to the wrong type of tool");
+ initThemeOptions();
+ }
+
+ @Override
+ protected void init() {
+ new ActionBuilder("Dump UI Properties", getName())
+ .menuPath("Edit", "Dump UI Properies")
+ .onAction(e -> LookAndFeelUtils.dumpUIProperties())
+ .buildAndInstall(tool);
+
+ new ActionBuilder("Show Properties", getName())
+ .menuPath("Edit", "Theme Properties")
+ .onAction(e -> showThemeProperties())
+ .buildAndInstall(tool);
+
+ }
+
+ private void showThemeProperties() {
+ GThemeDialog dialog = new GThemeDialog();
+ tool.showDialog(dialog);
+ }
+
+ private void initThemeOptions() {
+
+ ToolOptions opt = tool.getOptions(OPTIONS_TITLE);
+
+ GTheme activeTheme = Gui.getActiveTheme();
+ List themeNames = getAllThemeNames();
+
+ opt.registerOption(THEME_OPTIONS_NAME, OptionType.STRING_TYPE, activeTheme.getName(),
+ new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, "Look_And_Feel"),
+ "Set the look and feel for Ghidra. After you change the " +
+ "look and feel, you will have to restart Ghidra to see the effect.",
+ new StringWithChoicesEditor(themeNames));
+
+ opt.addOptionsChangeListener(this);
+ }
+
+ private List getAllThemeNames() {
+ Set allThemes = Gui.getAllThemes();
+ List themeNames =
+ allThemes.stream().map(t -> t.getName()).collect(Collectors.toList());
+ Collections.sort(themeNames);
+ return themeNames;
+ }
+
+ @Override
+ public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
+ Object newValue) {
+
+ if (optionName.equals(THEME_OPTIONS_NAME)) {
+ String newThemeName = (String) newValue;
+ if (!newThemeName.equals(Gui.getActiveTheme().getName())) {
+ issueRestartNeededMessage();
+ }
+
+ saveLookAndFeel((String) newValue);
+ }
+
+ }
+
+ private void saveLookAndFeel(String themeName) {
+ Set allThemes = Gui.getAllThemes();
+ for (GTheme theme : allThemes) {
+ if (theme.getName().equals(themeName)) {
+ Gui.saveThemeToPreferneces(theme);
+ }
+ }
+ }
+
+ private void issueRestartNeededMessage() {
+ if (issuedRestartNotification) {
+ return;
+ }
+
+ issuedRestartNotification = true;
+ Msg.showInfo(getClass(), null, "Look And Feel Updated",
+ "The new Look and Feel will take effect \nafter you exit and restart Ghidra.");
+ }
+
+ @Override
+ public void dispose() {
+ ToolOptions opt = tool.getOptions(OPTIONS_TITLE);
+ opt.removeOptionsChangeListener(this);
+ super.dispose();
+ }
+}
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 98fbd60360..aaa3afd179 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
@@ -17,6 +17,8 @@ package ghidra.app.util;
import java.awt.Color;
+import docking.theme.GColor;
+
/**
* Miscellaneous defined constants
*
@@ -94,11 +96,13 @@ public interface PluginConstants {
/**
* Color for highlighting for searches.
*/
- public static final Color SEARCH_HIGHLIGHT_COLOR = new Color(255, 255, 200);
+ public static final Color SEARCH_HIGHLIGHT_COLOR = new GColor("color.bg.search.highlight");
+
/**
* Default highlight color used when something to highlight is at the current
* address.
*/
- public static final Color SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR = Color.YELLOW;
+ public static final Color SEARCH_HIGHLIGHT_CURRENT_ADDR_COLOR =
+ new GColor("color.bg.search.current-line.highlight");
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bean/SetEquateDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bean/SetEquateDialog.java
index d89ac48c81..659a2c8337 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/bean/SetEquateDialog.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/bean/SetEquateDialog.java
@@ -148,17 +148,17 @@ public class SetEquateDialog extends DialogComponentProvider {
int refCount = eqRowObject.getRefCount();
if (refCount > 0) {
if (eqRowObject.getEntryName().contains(EquateManager.ERROR_TAG)) {
- c.setForeground(isSelected ? Color.WHITE : Color.RED);
+ c.setForeground(isSelected ? this.SELECTED_CELL_COLOR : this.BAD_EQUATE_COLOR);
}
else {
Equate e = eqRowObject.getEquate();
if (e != null && !e.isEnumBased()) {
- c.setForeground(isSelected ? Color.WHITE : Color.BLUE.brighter());
+ c.setForeground(isSelected ? this.SELECTED_CELL_COLOR : this.EQUATE_COLOR);
}
}
}
else {
- c.setForeground(isSelected ? Color.WHITE : Color.GRAY.darker());
+ c.setForeground(isSelected ? this.SELECTED_CELL_COLOR : this.SUGGESTION_COLOR);
}
return c;
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/PropertyBasedBackgroundColorModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/PropertyBasedBackgroundColorModel.java
index e754a0f45d..96ab5492c0 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/PropertyBasedBackgroundColorModel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/PropertyBasedBackgroundColorModel.java
@@ -20,6 +20,7 @@ import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
+import docking.theme.GColor;
import docking.widgets.fieldpanel.support.BackgroundColorModel;
import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.framework.model.DomainObjectChangedEvent;
@@ -40,7 +41,7 @@ public class PropertyBasedBackgroundColorModel
public static final String COLOR_PROPERTY_NAME = "LISTING_COLOR";
private IntRangeMap colorMap;
private AddressIndexMap indexMap;
- private Color defaultBackgroundColor = Color.WHITE;
+ private Color defaultBackgroundColor = new GColor("color.bg.listing");
private Map colorCache = new HashMap<>();
private Program program;
private boolean enabled = false;
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/options/OptionsGui.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/options/OptionsGui.java
index d6593229d3..13831d605e 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/options/OptionsGui.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/options/OptionsGui.java
@@ -27,6 +27,7 @@ import java.util.stream.IntStream;
import javax.swing.*;
import javax.swing.border.Border;
+import docking.theme.GColor;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GComboBox;
import docking.widgets.fieldpanel.*;
@@ -43,95 +44,150 @@ import ghidra.util.SystemUtilities;
*/
public class OptionsGui extends JPanel {
private static final long serialVersionUID = 1L;
- private static final Color DARK_GREEN = new Color(0, 128, 0);
- private static final Color BLUE_GREEN = new Color(0, 128, 64);
- private static final Color DARK_BLUE = new Color(0, 0, 128);
- private static final Color PALE_BLUE = new Color(128, 128, 255);
- private static final Color YELLOW_ORANGE = new Color(155, 150, 50);
- private static final Color PURPLE = new Color(155, 50, 155);
- private static final Color DEEP_PURPLE = new Color(75, 0, 130);
- private static final Color DARK_PURPLE = new Color(102, 0, 102);
- private static final Color DARK_CYAN = new Color(0, 102, 102);
- private static final Color DARK_ORANGE = new Color(255, 128, 0);
- private static final Color DARK_RED = new Color(130, 0, 75);
private static final Highlight[] NO_HIGHLIGHTS = new Highlight[0];
private static final HighlightFactory hlFactory =
(field, text, cursorTextOffset) -> NO_HIGHLIGHTS;
+ public static final ScreenElement BACKGROUND =
+ new ScreenElement("Background", new GColor("color.bg.listing"));
public static final ScreenElement COMMENT_AUTO =
- new ScreenElement("Comment, Automatic", Color.LIGHT_GRAY);
- public static final ScreenElement ADDRESS = new ScreenElement("Address", Color.BLACK);
- public static final ScreenElement BACKGROUND = new ScreenElement("Background", Color.WHITE);
+ new ScreenElement("Comment, Automatic", new GColor("color.fg.listing.comment.auto"));
+ public static final ScreenElement ADDRESS =
+ new ScreenElement("Address", new GColor("color.fg.listing.address"));
+
public static final ScreenElement BAD_REF_ADDR =
- new ScreenElement("Bad Reference Address", Color.RED);
- public static final ScreenElement BYTES = new ScreenElement("Bytes", Color.BLUE);
- public static final ScreenElement CONSTANT = new ScreenElement("Constant", BLUE_GREEN);
- public static final ScreenElement LABELS_UNREFD =
- new ScreenElement("Labels, Unreferenced", Color.BLACK);
- public static final ScreenElement ENTRY_POINT = new ScreenElement("Entry Point", Color.MAGENTA);
- public static final ScreenElement COMMENT_EOL =
- new ScreenElement("Comment, EOL", "EOL Comment", Color.BLUE);
- public static final ScreenElement EXT_REF_RESOLVED =
- new ScreenElement("External Reference, Resolved", Color.CYAN.darker().darker());
- public static final ScreenElement FIELD_NAME = new ScreenElement("Field Name", Color.BLACK);
+ new ScreenElement("Bad Reference Address", new GColor("color.fg.listing.ref.bad"));
+
+ public static final ScreenElement BYTES =
+ new ScreenElement("Bytes", new GColor("color.fg.listing.bytes"));
+
+ public static final ScreenElement CONSTANT =
+ new ScreenElement("Constant", new GColor("color.fg.listing.constant"));
+
+ public static final ScreenElement LABELS_UNREFD = new ScreenElement("Labels, Unreferenced",
+ new GColor("color.fg.listing.label.unreferenced"));
+
+ public static final ScreenElement ENTRY_POINT =
+ new ScreenElement("Entry Point", new GColor("color.fg.listing.entrypoint"));
+
+ public static final ScreenElement COMMENT_EOL = new ScreenElement("Comment, EOL", "EOL Comment",
+ new GColor("color.fg.listing.comment.auto"));
+
+ public static final ScreenElement EXT_REF_RESOLVED = new ScreenElement(
+ "External Reference, Resolved", new GColor("color.fg.listing.ref.ext.resolved"));
+
+ public static final ScreenElement FIELD_NAME =
+ new ScreenElement("Field Name", new GColor("color.fg.listing.fieldname"));
+
public static final ScreenElement FUN_CALL_FIXUP =
- new ScreenElement("Function Call-Fixup", new Color(255, 0, 204));
- public static final ScreenElement FUN_NAME = new ScreenElement("Function Name", Color.BLUE);
+ new ScreenElement("Function Call-Fixup", new GColor("color.fg.listing.function.callfixup"));
+
+ public static final ScreenElement FUN_NAME =
+ new ScreenElement("Function Name", new GColor("color.fg.listing.function.name"));
+
public static final ScreenElement FUN_PARAMS =
- new ScreenElement("Function Parameters", Color.BLACK);
- public static final ScreenElement FUN_TAG = new ScreenElement("Function Tag", DARK_RED);
- public static final ScreenElement FUN_AUTO_PARAMS =
- new ScreenElement("Function Auto-Parameters", Color.GRAY);
- public static final ScreenElement FUN_RET_TYPE =
- new ScreenElement("Function Return Type", Color.BLACK);
+ new ScreenElement("Function Parameters", new GColor("color.fg.listing.function.param"));
+
+ public static final ScreenElement FUN_TAG =
+ new ScreenElement("Function Tag", new GColor("color.fg.listing.function.tag"));
+
+ public static final ScreenElement FUN_AUTO_PARAMS = new ScreenElement(
+ "Function Auto-Parameters", new GColor("color.fg.listing.function.param.auto"));
+
+ public static final ScreenElement FUN_RET_TYPE = new ScreenElement("Function Return Type",
+ new GColor("color.fg.listing.function.return-type"));
+
public static final ScreenElement COMMENT_REPEATABLE =
- new ScreenElement("Comment, Repeatable", DARK_ORANGE);
- public static final ScreenElement COMMENT_REF_REPEAT =
- new ScreenElement("Comment, Referenced Repeatable", new Color(190, 190, 255));
- public static final ScreenElement LABELS_LOCAL = new ScreenElement("Labels, Local", BLUE_GREEN);
+ new ScreenElement("Comment, Repeatable", new GColor("color.fg.listing.comment.repeatable"));
+
+ public static final ScreenElement COMMENT_REF_REPEAT = new ScreenElement(
+ "Comment, Referenced Repeatable", new GColor("color.fg.listing.comment.ref-repeatable"));
+
+ public static final ScreenElement LABELS_LOCAL =
+ new ScreenElement("Labels, Local", new GColor("color.fg.listing.label.local"));
+
public static final ScreenElement MNEMONIC_OVERRIDE =
- new ScreenElement("Mnemonic, Override", new Color(255, 0, 204));
- public static final ScreenElement MNEMONIC = new ScreenElement("Mnemonic", DARK_BLUE);
- public static final ScreenElement UNIMPL =
- new ScreenElement("Unimplemented Mnemonic", Color.RED);
- public static final ScreenElement FLOW_ARROW_NON_ACTIVE =
- new ScreenElement("Flow Arrow, Not Active", new Color(160, 160, 160));
+ new ScreenElement("Mnemonic, Override", new GColor("color.fg.listing.mnemonic.override"));
+
+ public static final ScreenElement MNEMONIC =
+ new ScreenElement("Mnemonic", new GColor("color.fg.listing.mnemonic"));
+
+ public static final ScreenElement UNIMPL = new ScreenElement("Unimplemented Mnemonic",
+ new GColor("color.fg.listing.mnemonic.unimplemented"));
+
+ public static final ScreenElement FLOW_ARROW_NON_ACTIVE = new ScreenElement(
+ "Flow Arrow, Not Active", new GColor("color.fg.listing.flow-arrow.inactive"));
+
public static final ScreenElement FLOW_ARROW_ACTIVE =
- new ScreenElement("Flow Arrow, Active", Color.BLACK);
- public static final ScreenElement FLOW_ARROW_SELECTED =
- new ScreenElement("Flow Arrow, Selected", new Color(0, 200, 0));
+ new ScreenElement("Flow Arrow, Active", new GColor("color.fg.listing.flow-arrow.active"));
+
+ public static final ScreenElement FLOW_ARROW_SELECTED = new ScreenElement(
+ "Flow Arrow, Selected", new GColor("color.fg.listing.flow-arrow.selected"));
+
public static final ScreenElement LABELS_NON_PRIMARY =
- new ScreenElement("Labels, Non-primary", YELLOW_ORANGE);
- public static final ScreenElement COMMENT_PLATE =
- new ScreenElement("Comment, Plate", "Plate Comment", Color.GRAY);
- public static final ScreenElement COMMENT_POST =
- new ScreenElement("Comment, Post", "Post-Comment", Color.BLUE);
- public static final ScreenElement COMMENT_PRE =
- new ScreenElement("Comment, Pre", "Pre-Comment", DEEP_PURPLE);
+ new ScreenElement("Labels, Non-primary", new GColor("color.fg.listing.label.non-primary"));
+
+ public static final ScreenElement COMMENT_PLATE = new ScreenElement("Comment, Plate",
+ "Plate Comment", new GColor("color.fg.listing.comment.plate"));
+
+ public static final ScreenElement COMMENT_POST = new ScreenElement("Comment, Post",
+ "Post-Comment", new GColor("color.fg.listing.comment.post"));
+
+ public static final ScreenElement COMMENT_PRE = new ScreenElement("Comment, Pre", "Pre-Comment",
+ new GColor("color.fg.listing.comment.pre"));
+
public static final ScreenElement LABELS_PRIMARY =
- new ScreenElement("Labels, Primary", DARK_BLUE);
- public static final ScreenElement SEPARATOR = new ScreenElement("Separator", Color.BLACK);
- public static final ScreenElement VARIABLE = new ScreenElement("Variable", PURPLE);
- public static final ScreenElement PARAMETER_CUSTOM =
- new ScreenElement("Parameter, Custom Storage", DARK_PURPLE);
- public static final ScreenElement PARAMETER_DYNAMIC =
- new ScreenElement("Parameter, Dynamic Storage", DARK_CYAN);
- public static final ScreenElement VERSION_TRAK = new ScreenElement("Version Track", PURPLE);
- public static final ScreenElement XREF = new ScreenElement("XRef", DARK_GREEN);
- public static final ScreenElement XREF_OFFCUT = new ScreenElement("XRef, Offcut", Color.GRAY);
- public static final ScreenElement XREF_READ = new ScreenElement("XRef Read", Color.BLUE);
- public static final ScreenElement XREF_WRITE = new ScreenElement("XRef Write", DARK_ORANGE);
- public static final ScreenElement XREF_OTHER = new ScreenElement("XRef Other", Color.BLACK);
- public static final ScreenElement REGISTERS = new ScreenElement("Registers", YELLOW_ORANGE);
- public static final ScreenElement UNDERLINE = new ScreenElement("Underline", PALE_BLUE);
+ new ScreenElement("Labels, Primary", new GColor("color.fg.listing.label.primary"));
+
+ public static final ScreenElement SEPARATOR =
+ new ScreenElement("Separator", new GColor("color.fg.listing.separator"));
+
+ public static final ScreenElement VARIABLE =
+ new ScreenElement("Variable", new GColor("color.fg.listing.variable"));
+
+ public static final ScreenElement PARAMETER_CUSTOM = new ScreenElement(
+ "Parameter, Custom Storage", new GColor("color.fg.listing.function.param.custom"));
+
+ public static final ScreenElement PARAMETER_DYNAMIC = new ScreenElement(
+ "Parameter, Dynamic Storage", new GColor("color.fg.listing.function.param.dynamic"));
+
+ public static final ScreenElement VERSION_TRAK =
+ new ScreenElement("Version Track", new GColor("color.fg.listing.version-tracking"));
+
+ public static final ScreenElement XREF =
+ new ScreenElement("XRef", new GColor("color.fg.listing.xref"));
+
+ public static final ScreenElement XREF_OFFCUT =
+ new ScreenElement("XRef, Offcut", new GColor("color.fg.listing.xref.offcut"));
+
+ public static final ScreenElement XREF_READ =
+ new ScreenElement("XRef Read", new GColor("color.fg.listing.xref.read"));
+
+ public static final ScreenElement XREF_WRITE =
+ new ScreenElement("XRef Write", new GColor("color.fg.listing.xref.write"));
+
+ public static final ScreenElement XREF_OTHER =
+ new ScreenElement("XRef Other", new GColor("color.fg.listing.xref.other"));
+
+ public static final ScreenElement REGISTERS =
+ new ScreenElement("Registers", new GColor("color.fg.listing.register"));
+
+ public static final ScreenElement UNDERLINE =
+ new ScreenElement("Underline", new GColor("color.fg.listing.underline"));
+
public static final ScreenElement PCODE_LINE_LABEL =
- new ScreenElement("P-code Line Label", Color.BLUE);
+ new ScreenElement("P-code Line Label", new GColor("color.fg.listing.pcode.label"));
+
public static final ScreenElement PCODE_ADDR_SPACE =
- new ScreenElement("P-code Address Space", Color.BLUE);
+ new ScreenElement("P-code Address Space", new GColor("color.fg.listing.pcode.space"));
+
public static final ScreenElement PCODE_RAW_VARNODE =
- new ScreenElement("P-code Raw Varnode", Color.BLUE);
+ new ScreenElement("P-code Raw Varnode", new GColor("color.fg.listing.pcode.varnode"));
+
public static final ScreenElement PCODE_USEROP =
- new ScreenElement("P-code Userop", Color.BLUE);
+ new ScreenElement("P-code Userop", new GColor("color.fg.listing.pcode.userop"));
+
+ //@formatter:on
static ScreenElement[] elements = { ADDRESS, BACKGROUND, BAD_REF_ADDR, BYTES, COMMENT_AUTO,
COMMENT_EOL, COMMENT_PLATE, COMMENT_POST, COMMENT_PRE, COMMENT_REPEATABLE,
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/GhidraApplicationConfiguration.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/GhidraApplicationConfiguration.java
index 25aa525ef2..82a4ec08fb 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/framework/GhidraApplicationConfiguration.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/GhidraApplicationConfiguration.java
@@ -15,16 +15,12 @@
*/
package ghidra.framework;
-import java.awt.Taskbar;
-import java.awt.Toolkit;
-import java.lang.reflect.Field;
-
import docking.DockingErrorDisplay;
import docking.DockingWindowManager;
import docking.framework.ApplicationInformationDisplayFactory;
import docking.framework.SplashScreen;
+import docking.theme.Gui;
import docking.widgets.PopupKeyStorePasswordProvider;
-import ghidra.docking.util.DockingWindowsLookAndFeelUtils;
import ghidra.formats.gfilesystem.crypto.CryptoProviders;
import ghidra.formats.gfilesystem.crypto.PopupGUIPasswordProvider;
import ghidra.framework.main.GhidraApplicationInformationDisplayFactory;
@@ -46,10 +42,7 @@ public class GhidraApplicationConfiguration extends HeadlessGhidraApplicationCon
@Override
protected void initializeApplication() {
-
- DockingWindowsLookAndFeelUtils.loadFromPreferences();
-
- platformSpecificFixups();
+ Gui.initialize();
if (showSplashScreen) {
showUserAgreement();
@@ -63,17 +56,6 @@ public class GhidraApplicationConfiguration extends HeadlessGhidraApplicationCon
CryptoProviders.getInstance().registerCryptoProvider(new PopupGUIPasswordProvider());
}
- private static void platformSpecificFixups() {
-
- // Set the dock icon for macOS
- if (Taskbar.isTaskbarSupported()) {
- Taskbar taskbar = Taskbar.getTaskbar();
- if (taskbar.isSupported(Taskbar.Feature.ICON_IMAGE)) {
- taskbar.setIconImage(ApplicationInformationDisplayFactory.getLargestWindowIcon());
- }
- }
- }
-
private static void showUserAgreement() {
String value = Preferences.getProperty(USER_AGREEMENT_PROPERTY_NAME);
if ("ACCEPT".equals(value)) {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/ConsoleTextPane.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/ConsoleTextPane.java
index 4c9212a952..5077f6bbc7 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/ConsoleTextPane.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/ConsoleTextPane.java
@@ -15,13 +15,13 @@
*/
package ghidra.framework.main;
-import java.awt.Color;
import java.awt.Font;
import java.util.LinkedList;
import javax.swing.JTextPane;
import javax.swing.text.*;
+import docking.theme.GColor;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.Msg;
@@ -113,7 +113,8 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener
private void updateFromOptions(Options options) {
int newLimit = options.getInt(MAXIMUM_CHARACTERS_OPTION_NAME, DEFAULT_MAXIMUM_CHARS);
- truncationFactor = options.getDouble(TRUNCATION_FACTOR_OPTION_NAME, DEFAULT_TRUNCATION_FACTOR);
+ truncationFactor =
+ options.getDouble(TRUNCATION_FACTOR_OPTION_NAME, DEFAULT_TRUNCATION_FACTOR);
setMaximumCharacterLimit(newLimit);
}
@@ -141,7 +142,7 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener
private void doAddMessage(MessageWrapper newMessage) {
synchronized (messageList) {
- if ( !messageList.isEmpty() ) {
+ if (!messageList.isEmpty()) {
MessageWrapper lastMessage = messageList.getLast();
if (lastMessage.merge(newMessage)) {
return;
@@ -211,7 +212,8 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener
outputAttributeSet.addAttribute(StyleConstants.FontSize, font.getSize());
outputAttributeSet.addAttribute(StyleConstants.Italic, font.isItalic());
outputAttributeSet.addAttribute(StyleConstants.Bold, font.isBold());
- outputAttributeSet.addAttribute(StyleConstants.Foreground, Color.BLACK);
+ outputAttributeSet.addAttribute(StyleConstants.Foreground,
+ new GColor("color.fg.consoletextpane"));
errorAttributeSet = new SimpleAttributeSet();
errorAttributeSet.addAttribute(CUSTOM_ATTRIBUTE_KEY, ERROR_ATTRIBUTE_VALUE);
@@ -219,7 +221,8 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener
errorAttributeSet.addAttribute(StyleConstants.FontSize, font.getSize());
errorAttributeSet.addAttribute(StyleConstants.Italic, font.isItalic());
errorAttributeSet.addAttribute(StyleConstants.Bold, font.isBold());
- errorAttributeSet.addAttribute(StyleConstants.Foreground, Color.RED);
+ errorAttributeSet.addAttribute(StyleConstants.Foreground,
+ new GColor("color.fg.error.consoletextpane"));
}
private void doUpdate() {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/InfoPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/InfoPanel.java
index 2e27f4e91e..cba0ccd294 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/framework/main/InfoPanel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/framework/main/InfoPanel.java
@@ -25,6 +25,7 @@ import javax.swing.event.HyperlinkEvent;
import javax.swing.text.View;
import docking.DockingUtils;
+import docking.theme.GColor;
import docking.widgets.*;
import docking.widgets.label.*;
import generic.util.WindowUtilities;
@@ -58,7 +59,7 @@ class InfoPanel extends JPanel {
InfoPanel() {
getAboutInfo();
- bgColor = new Color(243, 250, 255);
+ bgColor = new GColor("color.bg.splash.infopanel");
create();
}
@@ -174,7 +175,7 @@ class InfoPanel extends JPanel {
Font font = versionLabel.getFont();
font = font.deriveFont(14f).deriveFont(Font.BOLD);
versionLabel.setFont(font);
- versionLabel.setForeground(Color.BLACK);
+ versionLabel.setForeground(new GColor("color.fg.infopanel.version"));
return versionLabel;
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTableCellRenderer.java b/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTableCellRenderer.java
index 93be27e470..214fda2ac9 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTableCellRenderer.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/util/table/GhidraTableCellRenderer.java
@@ -1,6 +1,5 @@
/* ###
* 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.
@@ -16,25 +15,30 @@
*/
package ghidra.util.table;
-import ghidra.program.model.address.Address;
-import ghidra.program.model.listing.Program;
-import ghidra.program.model.mem.Memory;
-import ghidra.program.model.symbol.ExternalLocation;
-import ghidra.program.model.symbol.Symbol;
-
import java.awt.Color;
import java.awt.Font;
import javax.swing.JTable;
import javax.swing.table.TableModel;
+import docking.theme.GColor;
import docking.widgets.table.GTableCellRenderer;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.mem.Memory;
+import ghidra.program.model.symbol.ExternalLocation;
+import ghidra.program.model.symbol.Symbol;
public class GhidraTableCellRenderer extends GTableCellRenderer {
// Defaults as defined by OptionsGui class - would be nice to use the tool options
- private static final Color BAD_REF_ADDR_COLOR = Color.red;
- private static final Color EXT_REF_RESOLVED_COLOR = Color.CYAN.darker().darker();
+ private static final Color BAD_REF_ADDR_COLOR = new GColor("color.fg.listing.ref.bad");
+ private static final Color EXT_REF_RESOLVED_COLOR =
+ new GColor("color.fg.listing.ref.ext.resolved");
+ public Color SELECTED_CELL_COLOR = new GColor("color.bg.table.selected.ghidratable");
+ public Color BAD_EQUATE_COLOR = new GColor("color.fg.table.ghidratable.equate.bad");
+ public Color EQUATE_COLOR = new GColor("color.fg.table.ghidratable.equate");
+ public Color SUGGESTION_COLOR = new GColor("color.fg.table.ghidratable.suggestion");
public GhidraTableCellRenderer() {
// default constructor
diff --git a/Ghidra/Features/ByteViewer/certification.manifest b/Ghidra/Features/ByteViewer/certification.manifest
index 65455bf81c..b6da5c7ed5 100644
--- a/Ghidra/Features/ByteViewer/certification.manifest
+++ b/Ghidra/Features/ByteViewer/certification.manifest
@@ -3,6 +3,7 @@
##MODULE IP: Oxygen Icons - LGPL 3.0
Module.manifest||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||reviewed||END|
+data/byteviewer.theme.properties||GHIDRA||||END|
src/main/help/help/TOC_Source.xml||GHIDRA||reviewed||END|
src/main/help/help/shared/arrow.gif||GHIDRA||reviewed||END|
src/main/help/help/shared/close16.gif||GHIDRA||reviewed||END|
diff --git a/Ghidra/Features/ByteViewer/data/byteviewer.theme.properties b/Ghidra/Features/ByteViewer/data/byteviewer.theme.properties
new file mode 100644
index 0000000000..30ee3e4c8f
--- /dev/null
+++ b/Ghidra/Features/ByteViewer/data/byteviewer.theme.properties
@@ -0,0 +1,17 @@
+[Defaults]
+
+color.bg.byteviewer = color.bg
+
+color.fg.byteviewer.novalue = blue
+color.fg.byteviewer.changed = red
+color.cursor.focused.byteviewer.current = color.cursor.focused
+color.cursor.focused.byteviewer.noncurrent = black
+color.cursor.unfocused.byteviewer = color.cursor.unfocused
+
+[Dark Defaults]
+color.fg.byteviewer.novalue = DarkBlue
+color.fg.byteviewer.changed = indianRed
+color.cursor.focused.byteviewer.current = color.cursor.focused
+color.cursor.focused.byteviewer.noncurrent = gray
+color.cursor.unfocused.byteviewer = color.cursor.unfocused
+
diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponentProvider.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponentProvider.java
index 42a0ac0ec5..7318787a32 100644
--- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponentProvider.java
+++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerComponentProvider.java
@@ -25,6 +25,7 @@ import java.util.List;
import javax.swing.JComponent;
import docking.action.ToggleDockingAction;
+import docking.theme.GColor;
import ghidra.GhidraOptions;
import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES;
import ghidra.app.plugin.core.format.*;
@@ -55,11 +56,17 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
static final Font DEFAULT_FONT = new Font("Monospaced", Font.PLAIN, 12);
static final int DEFAULT_BYTES_PER_LINE = 16;
- static final Color DEFAULT_MISSING_VALUE_COLOR = Color.blue;
- static final Color DEFAULT_EDIT_COLOR = Color.red;
- static final Color DEFAULT_CURRENT_CURSOR_COLOR = Color.magenta.brighter();
- static final Color DEFAULT_CURSOR_COLOR = Color.black;
- static final Color DEFAULT_NONFOCUS_CURSOR_COLOR = Color.darkGray;
+
+ //@formatter:off
+ static final String FG = "byteviewer.color.fg";
+ static final String CURSOR = "byteviewer.color.cursor";
+ static final Color DEFAULT_MISSING_VALUE_COLOR = new GColor("color.fg.byteviewer.novalue");
+ static final Color DEFAULT_EDIT_COLOR = new GColor("color.fg.byteviewer.changed");
+ static final Color DEFAULT_CURRENT_CURSOR_COLOR = new GColor("color.cursor.focused.byteviewer.current");
+ static final Color DEFAULT_CURSOR_COLOR = new GColor("color.cursor.focused.byteviewer.noncurrent");
+ static final Color DEFAULT_NONFOCUS_CURSOR_COLOR = new GColor("color.cursor.unfocused.byteviewer");
+ //@formatter:on
+
private static final Color DEFAULT_CURSOR_LINE_COLOR = GhidraOptions.DEFAULT_CURSOR_LINE_COLOR;
static final String DEFAULT_INDEX_NAME = "Addresses";
@@ -101,8 +108,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
private Map> dataFormatModelClassMap;
protected ByteViewerComponentProvider(PluginTool tool, AbstractByteViewerPlugin> plugin,
- String name,
- Class> contextType) {
+ String name, Class> contextType) {
super(tool, name, plugin.getName(), contextType);
this.plugin = plugin;
@@ -265,7 +271,8 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
CURSOR_HIGHLIGHT_BUTTON_NAME, GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES.MIDDLE);
panel.setHighlightButton(mouseButton.getMouseEventID());
- panel.setMouseButtonHighlightColor(opt.getColor(HIGHLIGHT_COLOR_NAME, Color.YELLOW));
+ panel.setMouseButtonHighlightColor(
+ opt.getColor(HIGHLIGHT_COLOR_NAME, DEFAULT_HIGHLIGHT_COLOR));
opt.addOptionsChangeListener(this);
}
diff --git a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPanel.java b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPanel.java
index b395cd35a2..0b7e5dd76c 100644
--- a/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPanel.java
+++ b/Ghidra/Features/ByteViewer/src/main/java/ghidra/app/plugin/core/byteviewer/ByteViewerPanel.java
@@ -23,6 +23,7 @@ import java.util.List;
import javax.swing.*;
import javax.swing.event.*;
+import docking.theme.GColor;
import docking.widgets.fieldpanel.*;
import docking.widgets.fieldpanel.field.EmptyTextField;
import docking.widgets.fieldpanel.field.Field;
@@ -259,8 +260,8 @@ public class ByteViewerPanel extends JPanel
String start = blocks[0].getLocationRepresentation(BigInteger.ZERO);
startField.setText(start);
ByteBlock lastBlock = blocks[blocks.length - 1];
- endField.setText(lastBlock.getLocationRepresentation(
- lastBlock.getLength().subtract(BigInteger.ONE)));
+ endField.setText(lastBlock
+ .getLocationRepresentation(lastBlock.getLength().subtract(BigInteger.ONE)));
indexPanelWidth = getIndexPanelWidth(blocks);
int center = indexPanelWidth / 2;
@@ -833,7 +834,8 @@ public class ByteViewerPanel extends JPanel
columnHeader.addColumn(ByteViewerComponentProvider.DEFAULT_INDEX_NAME, indexPanel);
scrollp.setColumnHeaderComp(columnHeader);
- compPanel.setBackground(Color.WHITE);
+
+ compPanel.setBackground(new GColor("color.bg.byteviewer"));
statusPanel = createStatusPanel();
add(scrollp, BorderLayout.CENTER);
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java
index fb5d59265f..43effd8eed 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileOptions.java
@@ -24,6 +24,7 @@ import java.awt.Font;
import java.awt.event.MouseEvent;
import java.io.IOException;
+import docking.theme.GColor;
import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES;
import ghidra.app.util.HelpTopics;
import ghidra.framework.options.Options;
@@ -298,51 +299,52 @@ public class DecompileOptions {
}
}
+ //@formatter:off
private final static IntegerFormatEnum INTEGERFORMAT_OPTIONDEFAULT = IntegerFormatEnum.BestFit; // Must match PrintLanguage::resetDefaultsInternal
private IntegerFormatEnum integerFormat;
- private final static Color HIGHLIGHT_MIDDLE_MOUSE_DEF = new Color(255, 255, 0, 128);
+ private final static Color HIGHLIGHT_MIDDLE_MOUSE_DEF = new GColor("color.bg.decompiler.middle-mouse");
private Color middleMouseHighlightColor;
private int middleMouseHighlightButton = MouseEvent.BUTTON2;
- private final static String HIGHLIGHT_CURRENT_VARIABLE_MSG =
- "Display.Color for Current Variable Highlight";
- private final static Color HIGHLIGHT_CURRENT_VARIABLE_DEF = new Color(255, 255, 0, 128);
+ private final static String HIGHLIGHT_CURRENT_VARIABLE_MSG ="Display.Color for Current Variable Highlight";
+ private final static Color HIGHLIGHT_CURRENT_VARIABLE_DEF = new GColor("color.bg.decompiler.current-variable");
private Color currentVariableHighlightColor;
private final static String HIGHLIGHT_KEYWORD_MSG = "Display.Color for Keywords";
- private final static Color HIGHLIGHT_KEYWORD_DEF = Color.decode("0x0001E6");
+ private final static Color HIGHLIGHT_KEYWORD_DEF = new GColor("color.fg.decompiler.keyword");
private Color keywordColor;
private final static String HIGHLIGHT_FUNCTION_MSG = "Display.Color for Function names";
- private final static Color HIGHLIGHT_FUNCTION_DEF = Color.decode("0x0000FF");
+ private final static Color HIGHLIGHT_FUNCTION_DEF = new GColor("color.fg.decompiler.keyword");
private Color functionColor;
private final static String HIGHLIGHT_COMMENT_MSG = "Display.Color for Comments";
- private final static Color HIGHLIGHT_COMMENT_DEF = Color.decode("0x9600FF");
+ private final static Color HIGHLIGHT_COMMENT_DEF = new GColor("color.fg.decompiler.comment");
private Color commentColor;
private final static String HIGHLIGHT_VARIABLE_MSG = "Display.Color for Variables";
- private final static Color HIGHLIGHT_VARIABLE_DEF = Color.decode("0x999900");
+ private final static Color HIGHLIGHT_VARIABLE_DEF = new GColor( "color.fg.decompiler.variable");
private Color variableColor;
private final static String HIGHLIGHT_CONST_MSG = "Display.Color for Constants";
- private final static Color HIGHLIGHT_CONST_DEF = Color.decode("0x008E00");
+ private final static Color HIGHLIGHT_CONST_DEF = new GColor( "color.fg.decompiler.constant");
private Color constantColor;
private final static String HIGHLIGHT_TYPE_MSG = "Display.Color for Types";
- private final static Color HIGHLIGHT_TYPE_DEF = Color.decode("0x0033CC");
+ private final static Color HIGHLIGHT_TYPE_DEF = new GColor( "color.fg.decompiler.type");
private Color typeColor;
private final static String HIGHLIGHT_PARAMETER_MSG = "Display.Color for Parameters";
- private final static Color HIGHLIGHT_PARAMETER_DEF = Color.decode("0x9B009B");
+ private final static Color HIGHLIGHT_PARAMETER_DEF = new GColor( "color.fg.decompiler.parameter");
private Color parameterColor;
private final static String HIGHLIGHT_GLOBAL_MSG = "Display.Color for Globals";
- private final static Color HIGHLIGHT_GLOBAL_DEF = Color.decode("0x009999");
+ private final static Color HIGHLIGHT_GLOBAL_DEF = new GColor( "color.fg.decompiler.global");
private Color globalColor;
private final static String HIGHLIGHT_SPECIAL_MSG = "Display.Color for Special";
private final static Color HIGHLIGHT_SPECIAL_DEF = Color.decode("0xCC0033");
private Color specialColor;
private final static String HIGHLIGHT_DEFAULT_MSG = "Display.Color Default";
- private final static Color HIGHLIGHT_DEFAULT_DEF = Color.BLACK;
+ private final static Color HIGHLIGHT_DEFAULT_DEF = new GColor("color.fg.decompiler");
private Color defaultColor;
+ //@formatter:on
private static final String CODE_VIEWER_BACKGROUND_COLOR_MSG = "Display.Background Color";
- private static final Color CODE_VIEWER_BACKGROUND_COLOR = Color.WHITE;
+ private static final Color CODE_VIEWER_BACKGROUND_COLOR = new GColor("color.bg.decompiler");
private Color codeViewerBackgroundColor;
private static final String SEARCH_HIGHLIGHT_MSG =
diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGComponent.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGComponent.java
index ba23771b28..4d3ac63160 100644
--- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGComponent.java
+++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FGComponent.java
@@ -21,6 +21,7 @@ import java.util.Map.Entry;
import org.jdom.Element;
+import docking.theme.GColor;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.picking.PickedState;
@@ -45,9 +46,12 @@ import ghidra.util.UndefinedFunction;
public class FGComponent extends GraphComponent {
- private static final Color END_COLOR = new Color(255, 127, 127);
- private static final Color START_COLOR = new Color(127, 255, 127);
- private static final Color UNDEFINED_FUNCTION_COLOR = new Color(220, 220, 220);
+ //@formatter:off
+ private static final Color PICKED_COLOR = new GColor("color.bg.functiongraph.vertex.picked");
+ private static final Color START_COLOR = new GColor("color.bg.functiongraph.vertex.entry");
+ private static final Color END_COLOR = new GColor("color.bg.functiongraph.vertex.exit");
+ private static final Color UNDEFINED_FUNCTION_COLOR = new GColor("color.bg.undefined");
+ //@formatter:on
/**
* A somewhat arbitrary value that is used to signal a 'big' graph, which is one that will
@@ -206,7 +210,7 @@ public class FGComponent extends GraphComponent
// for background colors when we are zoomed to far to render the listing
PickedState pickedVertexState = viewer.getPickedVertexState();
renderContext.setVertexFillPaintTransformer(new FGVertexPickableBackgroundPaintTransformer(
- pickedVertexState, Color.YELLOW, START_COLOR, END_COLOR));
+ pickedVertexState, PICKED_COLOR, START_COLOR, END_COLOR));
// edge label rendering
com.google.common.base.Function edgeLabelTransformer = e -> e.getLabel();
@@ -233,7 +237,7 @@ public class FGComponent extends GraphComponent
viewer.setBackground(UNDEFINED_FUNCTION_COLOR);
}
else {
- viewer.setBackground(Color.WHITE);
+ viewer.setBackground(new GColor("color.bg.functiongraph"));
}
}
@@ -257,7 +261,7 @@ public class FGComponent extends GraphComponent
PickedState pickedVertexState = viewer.getPickedVertexState();
renderContext.setVertexFillPaintTransformer(new FGVertexPickableBackgroundPaintTransformer(
- pickedVertexState, Color.YELLOW, START_COLOR, END_COLOR));
+ pickedVertexState, PICKED_COLOR, START_COLOR, END_COLOR));
viewer.setGraphOptions(vgOptions);
diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/transformer/FGVertexPickableBackgroundPaintTransformer.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/transformer/FGVertexPickableBackgroundPaintTransformer.java
index 05717b396b..80ac0e133e 100644
--- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/transformer/FGVertexPickableBackgroundPaintTransformer.java
+++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/transformer/FGVertexPickableBackgroundPaintTransformer.java
@@ -17,9 +17,11 @@ package ghidra.app.plugin.core.functiongraph.graph.jung.transformer;
import java.awt.Color;
import java.awt.Paint;
+import java.util.Objects;
import com.google.common.base.Function;
+import docking.theme.Gui;
import edu.uci.ics.jung.visualization.picking.PickedInfo;
import ghidra.app.plugin.core.functiongraph.graph.FGVertexType;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
@@ -31,8 +33,8 @@ public class FGVertexPickableBackgroundPaintTransformer implements Function info, Color pickedColor,
Color startColor, Color endColor) {
- if (info == null) {
- throw new IllegalArgumentException("PickedInfo instance must be non-null");
- }
- this.info = info;
+ this.info = Objects.requireNonNull(info);
this.pickedColor = pickedColor;
this.entryColor = startColor;
this.exitColor = endColor;
- this.pickedStartColor = mix(pickedColor, startColor);
- this.pickedEndColor = mix(pickedColor, endColor);
+ this.pickedEntryColor = mix(pickedColor, startColor);
+ this.pickedExitColor = mix(pickedColor, endColor);
}
@Override
@@ -69,20 +68,31 @@ public class FGVertexPickableBackgroundPaintTransformer implements Function this.focusedVertex)
- .build();
+ this.singleSelectedVertexPaintable = SingleSelectedVertexPaintable.builder(viewer)
+ .selectionStrokeMin(4.f)
+ .selectionPaint(getSelectedVertexColor())
+ .selectedVertexFunction(vs -> this.focusedVertex)
+ .build();
// draws the selection highlights
viewer.addPreRenderPaintable(multiSelectedVertexPaintable);
@@ -352,8 +351,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
.buildAndInstallLocal(componentProvider);
// create an icon button to reset the view transformations to identity (scaled to layout)
- new ActionBuilder("Reset View", ACTION_OWNER)
- .description("Fit Graph to Window")
+ new ActionBuilder("Reset View", ACTION_OWNER).description("Fit Graph to Window")
.toolBarIcon(DefaultDisplayGraphIcons.FIT_TO_WINDOW)
.onAction(context -> centerAndScale())
.buildAndInstallLocal(componentProvider);
@@ -363,8 +361,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
ToggleDockingAction lensToggle = new ToggleActionBuilder("View Magnifier", ACTION_OWNER)
.description("Show View Magnifier")
.toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON)
- .onAction(context -> magnifyViewSupport.activate(
- ((AbstractButton) context.getSourceObject()).isSelected()))
+ .onAction(context -> magnifyViewSupport
+ .activate(((AbstractButton) context.getSourceObject()).isSelected()))
.build();
magnifyViewSupport.addItemListener(
itemEvent -> lensToggle.setSelected(itemEvent.getStateChange() == ItemEvent.SELECTED));
@@ -388,40 +386,35 @@ public class DefaultGraphDisplay implements GraphDisplay {
}
private void createPopupActions() {
- new ActionBuilder("Select Vertex", ACTION_OWNER)
- .popupMenuPath("Select Vertex")
+ new ActionBuilder("Select Vertex", ACTION_OWNER).popupMenuPath("Select Vertex")
.popupMenuGroup("selection", "1")
.withContext(VertexGraphActionContext.class)
.enabledWhen(c -> !isSelected(c.getClickedVertex()))
.onAction(c -> viewer.getSelectedVertexState().select(c.getClickedVertex()))
.buildAndInstallLocal(componentProvider);
- new ActionBuilder("Deselect Vertex", ACTION_OWNER)
- .popupMenuPath("Deselect Vertex")
+ new ActionBuilder("Deselect Vertex", ACTION_OWNER).popupMenuPath("Deselect Vertex")
.popupMenuGroup("selection", "2")
.withContext(VertexGraphActionContext.class)
.enabledWhen(c -> isSelected(c.getClickedVertex()))
.onAction(c -> viewer.getSelectedVertexState().deselect(c.getClickedVertex()))
.buildAndInstallLocal(componentProvider);
- new ActionBuilder("Select Edge", ACTION_OWNER)
- .popupMenuPath("Select Edge")
+ new ActionBuilder("Select Edge", ACTION_OWNER).popupMenuPath("Select Edge")
.popupMenuGroup("selection", "1")
.withContext(EdgeGraphActionContext.class)
.enabledWhen(c -> !isSelected(c.getClickedEdge()))
.onAction(c -> selectEdge(c.getClickedEdge()))
.buildAndInstallLocal(componentProvider);
- new ActionBuilder("Deselect Edge", ACTION_OWNER)
- .popupMenuPath("Deselect Edge")
+ new ActionBuilder("Deselect Edge", ACTION_OWNER).popupMenuPath("Deselect Edge")
.popupMenuGroup("selection", "2")
.withContext(EdgeGraphActionContext.class)
.enabledWhen(c -> isSelected(c.getClickedEdge()))
.onAction(c -> deselectEdge(c.getClickedEdge()))
.buildAndInstallLocal(componentProvider);
- new ActionBuilder("Edge Source", ACTION_OWNER)
- .popupMenuPath("Go To Edge Source")
+ new ActionBuilder("Edge Source", ACTION_OWNER).popupMenuPath("Go To Edge Source")
.popupMenuGroup("Go To")
.withContext(EdgeGraphActionContext.class)
.onAction(c -> {
@@ -430,8 +423,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
})
.buildAndInstallLocal(componentProvider);
- new ActionBuilder("Edge Target", ACTION_OWNER)
- .popupMenuPath("Go To Edge Target")
+ new ActionBuilder("Edge Target", ACTION_OWNER).popupMenuPath("Go To Edge Target")
.popupMenuGroup("Go To")
.withContext(EdgeGraphActionContext.class)
.onAction(c -> {
@@ -440,12 +432,12 @@ public class DefaultGraphDisplay implements GraphDisplay {
})
.buildAndInstallLocal(componentProvider);
- hideSelectedAction = new ToggleActionBuilder("Hide Selected", ACTION_OWNER)
- .popupMenuPath("Hide Selected")
- .popupMenuGroup("z", "1")
- .description("Toggles whether or not to show selected vertices and edges")
- .onAction(c -> manageVertexDisplay())
- .buildAndInstallLocal(componentProvider);
+ hideSelectedAction =
+ new ToggleActionBuilder("Hide Selected", ACTION_OWNER).popupMenuPath("Hide Selected")
+ .popupMenuGroup("z", "1")
+ .description("Toggles whether or not to show selected vertices and edges")
+ .onAction(c -> manageVertexDisplay())
+ .buildAndInstallLocal(componentProvider);
hideUnselectedAction = new ToggleActionBuilder("Hide Unselected", ACTION_OWNER)
.popupMenuPath("Hide Unselected")
@@ -454,8 +446,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
.onAction(c -> manageVertexDisplay())
.buildAndInstallLocal(componentProvider);
- new ActionBuilder("Invert Selection", ACTION_OWNER)
- .popupMenuPath("Invert Selection")
+ new ActionBuilder("Invert Selection", ACTION_OWNER).popupMenuPath("Invert Selection")
.popupMenuGroup("z", "3")
.description("Inverts the current selection")
.onAction(c -> invertSelection())
@@ -493,8 +484,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
.onAction(c -> growSelection(getAllComponentVerticesFromSelected()))
.buildAndInstallLocal(componentProvider);
- new ActionBuilder("Clear Selection", ACTION_OWNER)
- .popupMenuPath("Clear Selection")
+ new ActionBuilder("Clear Selection", ACTION_OWNER).popupMenuPath("Clear Selection")
.popupMenuGroup("z", "5")
.keyBinding("escape")
.enabledWhen(c -> hasSelection())
@@ -516,8 +506,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
.onAction(c -> groupSelectedVertices())
.buildAndInstallLocal(componentProvider);
- new ActionBuilder("Expand Selected", ACTION_OWNER)
- .popupMenuPath("Expand Selected Vertices")
+ new ActionBuilder("Expand Selected", ACTION_OWNER).popupMenuPath("Expand Selected Vertices")
.popupMenuGroup("zz", "6")
.description("Expands all selected collapsed vertices into their previous form")
.onAction(c -> ungroupSelectedVertices())
@@ -579,8 +568,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
private void askToNameGroupVertex(AttributedVertex vertex) {
String name = vertex.getName();
- String userName = OptionDialog.showInputMultilineDialog(null, "Enter Group Vertex Text",
- "Text", name);
+ String userName =
+ OptionDialog.showInputMultilineDialog(null, "Enter Group Vertex Text", "Text", name);
updateVertexName(vertex, userName != null ? userName : name);
}
@@ -602,8 +591,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
}
private boolean hasSelection() {
- return !(viewer.getSelectedVertices().isEmpty() &&
- viewer.getSelectedEdges().isEmpty());
+ return !(viewer.getSelectedVertices().isEmpty() && viewer.getSelectedEdges().isEmpty());
}
private boolean isSelected(AttributedVertex v) {
@@ -648,15 +636,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
// select all the edges that connect the supplied vertices
private void selectEdgesConnecting(Collection vertices) {
- Set edges = graph.edgeSet()
- .stream()
- .filter(
- e -> {
- AttributedVertex source = graph.getEdgeSource(e);
- AttributedVertex target = graph.getEdgeTarget(e);
- return vertices.contains(source) && vertices.contains(target);
- })
- .collect(Collectors.toSet());
+ Set edges = graph.edgeSet().stream().filter(e -> {
+ AttributedVertex source = graph.getEdgeSource(e);
+ AttributedVertex target = graph.getEdgeTarget(e);
+ return vertices.contains(source) && vertices.contains(target);
+ }).collect(Collectors.toSet());
viewer.getSelectedEdgeState().select(edges);
}
@@ -813,12 +797,9 @@ public class DefaultGraphDisplay implements GraphDisplay {
private SatelliteVisualizationViewer createSatelliteViewer(
VisualizationViewer parentViewer) {
Dimension viewerSize = parentViewer.getSize();
- Dimension satelliteSize = new Dimension(
- viewerSize.width / 4, viewerSize.height / 4);
+ Dimension satelliteSize = new Dimension(viewerSize.width / 4, viewerSize.height / 4);
final SatelliteVisualizationViewer satellite =
- SatelliteVisualizationViewer.builder(parentViewer)
- .viewSize(satelliteSize)
- .build();
+ SatelliteVisualizationViewer.builder(parentViewer).viewSize(satelliteSize).build();
//
// JUNGRAPHT CHANGE 3
@@ -1124,10 +1105,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
componentProvider.setTitle(title);
int count = graph.getVertexCount();
if (count > options.getMaxNodeCount()) {
- Msg.showWarn(this, null, "Graph Not Rendered - Too many nodes!",
- "Exceeded limit of " + options.getMaxNodeCount() + " nodes.\n\n Graph contained " +
- count +
- " nodes!");
+ Msg.showWarn(this, null, "Graph Not Rendered - Too many nodes!", "Exceeded limit of " +
+ options.getMaxNodeCount() + " nodes.\n\n Graph contained " + count + " nodes!");
graph = new AttributedGraph("Aborted", graph.getGraphType(), "Too Many Nodes");
graph.addVertex("1", "Graph Aborted");
}
@@ -1293,7 +1272,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
graphRenderer.initializeViewer(vv);
vv.getComponent().requestFocus();
- vv.setBackground(Color.WHITE);
+ vv.setBackground(BACKGROUND_COLOR);
MouseListener[] mouseListeners = vv.getComponent().getMouseListeners();
for (MouseListener mouseListener : mouseListeners) {
vv.getComponent().removeMouseListener(mouseListener);
@@ -1334,8 +1313,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
public void addAction(DockingActionIf action) {
if (containsAction(action)) {
- Msg.warn(this, "Action with same name and owner already exixts in graph: " +
- action.getFullName());
+ Msg.warn(this,
+ "Action with same name and owner already exixts in graph: " + action.getFullName());
return;
}
@@ -1387,20 +1366,17 @@ public class DefaultGraphDisplay implements GraphDisplay {
MutableSelectedState selectedVertexState =
viewer.getSelectedVertexState();
if (hideSelected && hideUnselected) {
- viewer.getRenderContext()
- .setVertexIncludePredicate(v -> false);
+ viewer.getRenderContext().setVertexIncludePredicate(v -> false);
}
else if (hideSelected) {
viewer.getRenderContext()
.setVertexIncludePredicate(Predicate.not(selectedVertexState::isSelected));
}
else if (hideUnselected) {
- viewer.getRenderContext()
- .setVertexIncludePredicate(selectedVertexState::isSelected);
+ viewer.getRenderContext().setVertexIncludePredicate(selectedVertexState::isSelected);
}
else {
- viewer.getRenderContext()
- .setVertexIncludePredicate(v -> true);
+ viewer.getRenderContext().setVertexIncludePredicate(v -> true);
}
viewer.repaint();
}
@@ -1467,8 +1443,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
@Override
public AttributedVertex getVertex(MouseEvent event) {
- LayoutModel layoutModel =
- vv.getVisualizationModel().getLayoutModel();
+ LayoutModel layoutModel = vv.getVisualizationModel().getLayoutModel();
Point2D p = vv.getTransformSupport().inverseTransform(vv, event.getPoint());
AttributedVertex vertex =
vv.getPickSupport().getVertex(layoutModel, p.getX(), p.getY());
@@ -1477,8 +1452,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
@Override
public AttributedEdge getEdge(MouseEvent event) {
- LayoutModel layoutModel =
- vv.getVisualizationModel().getLayoutModel();
+ LayoutModel layoutModel = vv.getVisualizationModel().getLayoutModel();
Point2D p = vv.getTransformSupport().inverseTransform(vv, event.getPoint());
AttributedEdge edge = vv.getPickSupport().getEdge(layoutModel, p.getX(), p.getY());
return edge;
diff --git a/Ghidra/Features/ProgramDiff/certification.manifest b/Ghidra/Features/ProgramDiff/certification.manifest
index 94046642d7..cc80bd6c37 100644
--- a/Ghidra/Features/ProgramDiff/certification.manifest
+++ b/Ghidra/Features/ProgramDiff/certification.manifest
@@ -6,6 +6,7 @@
##MODULE IP: Oxygen Icons - LGPL 3.0
##MODULE IP: Tango Icons - Public Domain
Module.manifest||GHIDRA||||END|
+data/programdiff.theme.properties||GHIDRA||||END|
src/main/help/help/TOC_Source.xml||GHIDRA||||END|
src/main/help/help/shared/arrow.gif||GHIDRA||reviewed||END|
src/main/help/help/shared/close16.gif||GHIDRA||reviewed||END|
diff --git a/Ghidra/Features/ProgramDiff/data/programdiff.theme.properties b/Ghidra/Features/ProgramDiff/data/programdiff.theme.properties
new file mode 100644
index 0000000000..eee5b4157d
--- /dev/null
+++ b/Ghidra/Features/ProgramDiff/data/programdiff.theme.properties
@@ -0,0 +1,7 @@
+[Defaults]
+
+color.bg.programdiff.highlight = moccasin
+
+[Dark Defaults]
+
+color.bg.programdiff.highlight = darkRed
diff --git a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java
index 77fc090912..2ef85deafb 100644
--- a/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java
+++ b/Ghidra/Features/ProgramDiff/src/main/java/ghidra/app/plugin/core/diff/ProgramDiffPlugin.java
@@ -26,6 +26,7 @@ import javax.swing.text.*;
import javax.swing.tree.TreeSelectionModel;
import docking.DockingUtils;
+import docking.theme.GColor;
import docking.widgets.EventTrigger;
import docking.widgets.OptionDialog;
import docking.widgets.fieldpanel.FieldPanel;
@@ -95,7 +96,7 @@ public class ProgramDiffPlugin extends ProgramPlugin
private static final String SELECTION_GROUP = "Selection Colors";
private static final String DIFF_HIGHLIGHT_COLOR_NAME =
SELECTION_GROUP + Options.DELIMITER + "Difference Color";
- private Color diffHighlightColor = new Color(255, 230, 180); // light orange
+ private Color diffHighlightColor = new GColor("color.bg.programdiff.highlight");
private Color cursorHighlightColor;
protected static final HelpService help = Help.getHelpService();
diff --git a/Ghidra/Framework/Docking/Module.manifest b/Ghidra/Framework/Docking/Module.manifest
index e69de29bb2..98a3c453e5 100644
--- a/Ghidra/Framework/Docking/Module.manifest
+++ b/Ghidra/Framework/Docking/Module.manifest
@@ -0,0 +1,2 @@
+MODULE FILE LICENSE: lib/flatlaf-2.1.jar Apache License 2.0
+
diff --git a/Ghidra/Framework/Docking/build.gradle b/Ghidra/Framework/Docking/build.gradle
index 139ec36c1f..fdf880918f 100644
--- a/Ghidra/Framework/Docking/build.gradle
+++ b/Ghidra/Framework/Docking/build.gradle
@@ -27,6 +27,7 @@ dependencies {
api project(':Generic')
api project(':Help')
+ api 'com.formdev:flatlaf:2.2'
// include code from src/test in Generic
testImplementation project(path: ':Generic', configuration: 'testArtifacts')
diff --git a/Ghidra/Framework/Docking/certification.manifest b/Ghidra/Framework/Docking/certification.manifest
index 0c37be739f..6efff61d3a 100644
--- a/Ghidra/Framework/Docking/certification.manifest
+++ b/Ghidra/Framework/Docking/certification.manifest
@@ -21,6 +21,8 @@ src/main/help/help/shared/tip.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (
src/main/help/help/shared/undo.png||GHIDRA||||END|
src/main/help/help/shared/warning.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/help/help/topics/PlacheholderTopic/Placeholder.htm||GHIDRA||||END|
+data/docking.palette.theme.properties||GHIDRA||||END|
+data/docking.theme.properties||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|
diff --git a/Ghidra/Framework/Docking/data/ExtensionPoint.manifest b/Ghidra/Framework/Docking/data/ExtensionPoint.manifest
index a7001f5a80..a6370472eb 100644
--- a/Ghidra/Framework/Docking/data/ExtensionPoint.manifest
+++ b/Ghidra/Framework/Docking/data/ExtensionPoint.manifest
@@ -1,3 +1,4 @@
ColumnConstraintProvider
TypeMapper
TableColumn
+Theme
diff --git a/Ghidra/Framework/Docking/data/docking.palette.theme.properties b/Ghidra/Framework/Docking/data/docking.palette.theme.properties
new file mode 100644
index 0000000000..0b59d5d95d
--- /dev/null
+++ b/Ghidra/Framework/Docking/data/docking.palette.theme.properties
@@ -0,0 +1,5 @@
+[Defaults]
+
+color.palette.lightgreen = rgb(127, 255, 127)
+color.palette.lightred = rgb(255, 127, 127)
+color.palette.yellow = yellow
\ No newline at end of file
diff --git a/Ghidra/Framework/Docking/data/docking.theme.properties b/Ghidra/Framework/Docking/data/docking.theme.properties
new file mode 100644
index 0000000000..a084009ede
--- /dev/null
+++ b/Ghidra/Framework/Docking/data/docking.theme.properties
@@ -0,0 +1,64 @@
+[Defaults]
+
+color.bg = white
+color.fg = black
+color.fg.error = red
+color.fg.disabled = lightGray
+
+color.bg.selection = rgb(180, 255, 180) // pale green
+color.bg.highlight = rgb(255,255,150) // pale yellow
+
+color.bg.currentline = rgb(232,242,254)
+color.cursor.focused = red
+color.cursor.unfocused = pink
+
+color.bg.table.row = color.bg
+color.bg.table.row.alt = rgb(237,243,254)
+
+color.bg.tableheader.gradient.start = color.bg
+color.bg.tableheader.gradient.end = lightGray
+color.bg.tableheader.gradient.start.primary = rgb(205, 227, 244)
+color.bg.tableheader.gradient.end.primary = rgb(126, 186, 233)
+
+color.bg.textfield.hint.valid = color.bg
+color.bg.textfield.hint.invalid = rgb(255,225,225)
+color.fg.textfield.hint = color.fg
+
+color.bg.selection.help = lightSteelBlue
+
+// extensions
+
+color.bg.splash = color.bg
+color.bg.filechooser = color.bg
+color.fg.filechooser = color.fg
+
+color.bg.fieldpanel = color.bg
+color.fg.fieldpanel = color.fg
+color.bg.fieldpanel.selection = color.bg.selection
+color.bg.fieldpanel.highlight = color.bg.highlight
+color.bg.fieldpanel.selection-highlight = green
+
+
+
+[Dark Defaults]
+
+color.bg = rgb(40, 42, 46)
+color.fg = gray
+color.bg.currentline = rgb(60,60,70)
+
+color.cursor.focused = indianRed
+color.cursor.unfocussed = darkGray
+
+color.bg.textfield.hint.invalid = maroon
+
+color.bg.selection = teal
+color.bg.highlight = rgb(110,110,0)
+
+color.bg.fieldpanel.select-highlight = darkGreen
+
+color.bg.tableheader.gradient.start = color.bg
+color.bg.tableheader.gradient.end = darkGray
+color.bg.tableheader.gradient.start.primary = color.bg
+color.bg.tableheader.gradient.end.primary = darkBlue
+color.bg.table.row.alt = rgb(45,47,65) //TODO
+
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingUtils.java
index e44ac282c9..24966af593 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/DockingUtils.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingUtils.java
@@ -37,7 +37,7 @@ import docking.widgets.list.GList;
import docking.widgets.list.GListCellRenderer;
import docking.widgets.table.GTableCellRenderer;
import docking.widgets.tree.support.GTreeRenderer;
-import ghidra.docking.util.DockingWindowsLookAndFeelUtils;
+import ghidra.docking.util.LookAndFeelUtils;
import ghidra.util.HTMLUtilities;
import resources.ResourceManager;
@@ -125,7 +125,7 @@ public class DockingUtils {
public static JSeparator createToolbarSeparator() {
Dimension sepDim = new Dimension(2, ICON_SIZE + 2);
JSeparator separator = new JSeparator(SwingConstants.VERTICAL);
- if (DockingWindowsLookAndFeelUtils.isUsingAquaUI(separator.getUI())) {
+ if (LookAndFeelUtils.isUsingAquaUI(separator.getUI())) {
separator.setUI(new BasicSeparatorUI());
}
separator.setPreferredSize(sepDim); // ugly work around to force height of separator
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/framework/DockingApplicationConfiguration.java b/Ghidra/Framework/Docking/src/main/java/docking/framework/DockingApplicationConfiguration.java
index aa57f15fe5..2d364215d2 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/framework/DockingApplicationConfiguration.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/framework/DockingApplicationConfiguration.java
@@ -16,8 +16,8 @@
package docking.framework;
import docking.DockingErrorDisplay;
+import docking.theme.Gui;
import docking.widgets.PopupKeyStorePasswordProvider;
-import ghidra.docking.util.DockingWindowsLookAndFeelUtils;
import ghidra.framework.ApplicationConfiguration;
import ghidra.net.ApplicationKeyManagerFactory;
import ghidra.util.ErrorDisplay;
@@ -48,7 +48,7 @@ public class DockingApplicationConfiguration extends ApplicationConfiguration {
protected void initializeApplication() {
super.initializeApplication();
- DockingWindowsLookAndFeelUtils.loadFromPreferences();
+ Gui.initialize();
if (showSplashScreen) {
SplashScreen.showSplashScreen();
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/framework/SplashScreen.java b/Ghidra/Framework/Docking/src/main/java/docking/framework/SplashScreen.java
index 6deff243de..18d3f1cd16 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/framework/SplashScreen.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/framework/SplashScreen.java
@@ -23,6 +23,7 @@ import javax.swing.*;
import javax.swing.border.BevelBorder;
import docking.*;
+import docking.theme.GColor;
import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel;
import generic.util.WindowUtilities;
@@ -37,8 +38,6 @@ import utility.application.ApplicationLayout;
*/
public class SplashScreen extends JWindow {
- private static final Color DEFAULT_BACKGROUND_COLOR = new Color(243, 250, 255);
-
private static SplashScreen splashWindow; // splash window displayed while ghidra is coming up
private static DockingFrame hiddenFrame;
private static JLabel statusLabel;
@@ -250,7 +249,7 @@ public class SplashScreen extends JWindow {
List list = ApplicationInformationDisplayFactory.getWindowIcons();
hiddenFrame.setIconImages(list);
hiddenFrame.setUndecorated(true);
- hiddenFrame.setTransient();
+ hiddenFrame.setTransient();
}
return hiddenFrame;
}
@@ -286,7 +285,7 @@ public class SplashScreen extends JWindow {
private JPanel createMainPanel() {
JPanel mainPanel = new JPanel(new BorderLayout());
- mainPanel.setBackground(DEFAULT_BACKGROUND_COLOR);
+ mainPanel.setBackground(new GColor("color.bg.splash"));
mainPanel.add(createTitlePanel(), BorderLayout.NORTH);
mainPanel.add(createContentPanel(), BorderLayout.CENTER);
return mainPanel;
@@ -331,7 +330,7 @@ public class SplashScreen extends JWindow {
statusLabel.setFont(f);
statusLabel.setBorder(BorderFactory.createEmptyBorder(0, 10, 2, 10));
- statusLabel.setBackground(DEFAULT_BACKGROUND_COLOR);
+ statusLabel.setBackground(new GColor("color.bg.splash"));
statusLabel.setOpaque(true);
return statusLabel;
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/help/HelpManager.java b/Ghidra/Framework/Docking/src/main/java/docking/help/HelpManager.java
index dc3678c550..a1f47a5a98 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/help/HelpManager.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/help/HelpManager.java
@@ -32,6 +32,7 @@ import javax.swing.UIManager;
import docking.ComponentProvider;
import docking.action.DockingActionIf;
+import docking.theme.GColor;
import generic.concurrent.GThreadPool;
import generic.util.WindowUtilities;
import ghidra.util.*;
@@ -697,7 +698,7 @@ public class HelpManager implements HelpService {
* you can see the highlights when you do a search in the JavaHelp.
*/
private void setColorResources() {
- UIManager.put("EditorPane.selectionBackground", new Color(204, 204, 255));
+ UIManager.put("EditorPane.selectionBackground", new GColor("color.bg.selection.help"));
UIManager.put("EditorPane.selectionForeground", UIManager.get("EditorPane.foreground"));
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingToolBarUtils.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingToolBarUtils.java
index 9db8af21b1..f5601a1ae2 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingToolBarUtils.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/DockingToolBarUtils.java
@@ -24,7 +24,7 @@ import javax.swing.KeyStroke;
import org.apache.commons.lang3.StringUtils;
import docking.action.DockingActionIf;
-import ghidra.docking.util.DockingWindowsLookAndFeelUtils;
+import ghidra.docking.util.LookAndFeelUtils;
import ghidra.util.StringUtilities;
class DockingToolBarUtils {
@@ -96,7 +96,7 @@ class DockingToolBarUtils {
builder.append(InputEvent.getModifiersExText(modifiers));
// The Aqua LaF does not use the '+' symbol between modifiers
- if (!DockingWindowsLookAndFeelUtils.isUsingAquaUI(button.getUI())) {
+ if (!LookAndFeelUtils.isUsingAquaUI(button.getUI())) {
builder.append('+');
}
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/ColorValue.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/ColorValue.java
new file mode 100644
index 0000000000..079daa8bb8
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/ColorValue.java
@@ -0,0 +1,99 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.awt.Color;
+
+import ghidra.util.Msg;
+import utilities.util.reflection.ReflectionUtilities;
+
+public class ColorValue extends ThemeValue {
+ static final String COLOR_ID_PREFIX = "color.";
+ static final String EXTERNAL_PREFIX = "[color]";
+
+ public static final Color LAST_RESORT_DEFAULT = Color.GRAY;
+
+ public ColorValue(String id, Color value) {
+ super(id, null, value);
+ }
+
+ public ColorValue(String id, String refId) {
+ super(id, refId, null);
+ }
+
+ @Override
+ protected ColorValue getReferredValue(GThemeValueMap values, String refId) {
+ return values.getColor(refId);
+ }
+
+ @Override
+ protected Color getUnresolvedReferenceValue(String id) {
+
+ Throwable t = ReflectionUtilities.createThrowableWithStackOlderThan();
+ StackTraceElement[] trace = t.getStackTrace();
+ StackTraceElement[] filtered =
+ ReflectionUtilities.filterStackTrace(trace, "docking.theme", "classfinder",
+ "Application", "ghidra.GhidraRun", "java.lang.Class", "java.lang.Thread");
+ t.setStackTrace(filtered);
+
+ Msg.error(this,
+ "Could not resolve indirect color for \"" + id + "\", using last resort default!", t);
+ return LAST_RESORT_DEFAULT;
+ }
+
+ @Override
+ protected String getIdPrefix() {
+ return COLOR_ID_PREFIX;
+ }
+
+ @Override
+ public String toExternalId(String internalId) {
+ if (internalId.startsWith(COLOR_ID_PREFIX)) {
+ return internalId;
+ }
+ return EXTERNAL_PREFIX + internalId;
+ }
+
+ @Override
+ public String fromExternalId(String externalId) {
+ if (externalId.startsWith(EXTERNAL_PREFIX)) {
+ return externalId.substring(EXTERNAL_PREFIX.length());
+ }
+ return externalId;
+ }
+
+ public static boolean isColorKey(String key) {
+ return key.startsWith(COLOR_ID_PREFIX) || key.startsWith(EXTERNAL_PREFIX);
+ }
+
+ @Override
+ protected int compareValues(Color v1, Color v2) {
+ int alpha1 = v1.getAlpha();
+ int alpha2 = v2.getAlpha();
+
+ if (alpha1 == alpha2) {
+ return getHsbCompareValue(v1) - getHsbCompareValue(v2);
+ }
+ return alpha1 - alpha2;
+ }
+
+ private int getHsbCompareValue(Color v) {
+ // compute a value the compares colors first by hue, then saturation, then brightness
+ // reduce noise by converting float values from 0-1 to integers 0 - 7
+ float[] hsb = Color.RGBtoHSB(v.getRed(), v.getGreen(), v.getBlue(), null);
+ return 100 * (int) (10 * hsb[0]) + 10 * (int) (10 * hsb[1]) + (int) (10 * hsb[2]);
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/DefaultTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/DefaultTheme.java
new file mode 100644
index 0000000000..2a4328a5a0
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/DefaultTheme.java
@@ -0,0 +1,25 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import ghidra.docking.util.LookAndFeelUtils;
+
+public class DefaultTheme extends DiscoverableGTheme {
+
+ public DefaultTheme() {
+ super("Default", LookAndFeelUtils.getDefaultLookAndFeelName());
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/DiscoverableGTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/DiscoverableGTheme.java
new file mode 100644
index 0000000000..aa4ff910e2
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/DiscoverableGTheme.java
@@ -0,0 +1,35 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import ghidra.util.classfinder.ExtensionPoint;
+
+public abstract class DiscoverableGTheme extends GTheme implements ExtensionPoint {
+ static final String CLASS_PREFIX = "Class:";
+
+ protected DiscoverableGTheme(String name, String lookAndFeelName) {
+ super(name, lookAndFeelName, false);
+ }
+
+ protected DiscoverableGTheme(String name, String lookAndFeelName, boolean isDark) {
+ super(name, lookAndFeelName, isDark);
+ }
+
+ @Override
+ public String getThemeLocater() {
+ return CLASS_PREFIX + getClass().getName();
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/FileGTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/FileGTheme.java
new file mode 100644
index 0000000000..9a6286091e
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/FileGTheme.java
@@ -0,0 +1,40 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.io.File;
+import java.io.IOException;
+
+public class FileGTheme extends GTheme {
+ public static final String FILE_PREFIX = "File:";
+ private final File file;
+
+ public FileGTheme(File file) throws IOException {
+ this(file, new ThemeReader(file));
+ }
+
+ FileGTheme(File file, ThemeReader reader) {
+ super(reader.getThemeName(), reader.getLookAndFeelName(), reader.isDark());
+ this.file = file;
+ reader.loadValues(this);
+ }
+
+ @Override
+ public String getThemeLocater() {
+ return FILE_PREFIX + file.getAbsolutePath();
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/FontValue.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/FontValue.java
new file mode 100644
index 0000000000..56ecfa493f
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/FontValue.java
@@ -0,0 +1,75 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.awt.Font;
+
+import ghidra.util.Msg;
+
+public class FontValue extends ThemeValue {
+ static final String FONT_ID_PREFIX = "font.";
+ public static final Font LAST_RESORT_DEFAULT = new Font("monospaced", Font.PLAIN, 12);
+ private static final String EXTERNAL_PREFIX = "[font]";
+
+ public FontValue(String id, Font value) {
+ super(id, null, value);
+ }
+
+ public FontValue(String id, String refId) {
+ super(id, refId, null);
+ }
+
+ @Override
+ protected FontValue getReferredValue(GThemeValueMap values, String refId) {
+ return values.getFont(refId);
+ }
+
+ @Override
+ protected Font getUnresolvedReferenceValue(String id) {
+ Msg.warn(this, "Could not resolve indirect font for" + id + ", using last resort default");
+ return LAST_RESORT_DEFAULT;
+ }
+
+ @Override
+ protected String getIdPrefix() {
+ return FONT_ID_PREFIX;
+ }
+
+ @Override
+ public String toExternalId(String internalId) {
+ if (internalId.startsWith(FONT_ID_PREFIX)) {
+ return internalId;
+ }
+ return EXTERNAL_PREFIX + internalId;
+ }
+
+ @Override
+ public String fromExternalId(String externalId) {
+ if (externalId.startsWith(EXTERNAL_PREFIX)) {
+ return externalId.substring(EXTERNAL_PREFIX.length());
+ }
+ return externalId;
+ }
+
+ public static boolean isFontKey(String key) {
+ return key.startsWith(FONT_ID_PREFIX) || key.startsWith(EXTERNAL_PREFIX);
+ }
+
+ @Override
+ protected int compareValues(Font v1, Font v2) {
+ return v1.toString().compareTo(v2.toString());
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/GColor.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/GColor.java
new file mode 100644
index 0000000000..dc93a6f3ec
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/GColor.java
@@ -0,0 +1,145 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.awt.*;
+import java.awt.color.ColorSpace;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.ColorModel;
+
+public class GColor extends Color implements Refreshable {
+
+ private String id;
+ private Color delegate;
+
+ public GColor(String id) {
+ super(0x808080);
+ this.id = id;
+ delegate = Gui.getRawColor(id);
+ if (delegate == null) {
+ delegate = Color.gray;
+ }
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public int getRed() {
+ return delegate.getRed();
+ }
+
+ @Override
+ public int getGreen() {
+ return delegate.getGreen();
+ }
+
+ @Override
+ public int getBlue() {
+ return delegate.getBlue();
+ }
+
+ @Override
+ public int getAlpha() {
+ return delegate.getAlpha();
+ }
+
+ @Override
+ public int getRGB() {
+ return delegate.getRGB();
+ }
+
+ @Override
+ public Color brighter() {
+ return delegate.brighter();
+ }
+
+ @Override
+ public Color darker() {
+ return delegate.darker();
+ }
+
+ @Override
+ public int hashCode() {
+ return delegate.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return delegate.equals(obj);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getName() + " [id = " + id + ", " + delegate.toString() + "]";
+ }
+
+ @Override
+ public float[] getRGBComponents(float[] compArray) {
+ return delegate.getRGBComponents(compArray);
+ }
+
+ @Override
+ public float[] getRGBColorComponents(float[] compArray) {
+ return delegate.getRGBColorComponents(compArray);
+ }
+
+ @Override
+ public float[] getComponents(float[] compArray) {
+ return delegate.getColorComponents(compArray);
+ }
+
+ @Override
+ public float[] getColorComponents(float[] compArray) {
+ return delegate.getColorComponents(compArray);
+ }
+
+ @Override
+ public float[] getComponents(ColorSpace cspace, float[] compArray) {
+ return delegate.getComponents(cspace, compArray);
+ }
+
+ @Override
+ public float[] getColorComponents(ColorSpace cspace, float[] compArray) {
+ return delegate.getColorComponents(cspace, compArray);
+ }
+
+ @Override
+ public ColorSpace getColorSpace() {
+ return delegate.getColorSpace();
+ }
+
+ @Override
+ public synchronized PaintContext createContext(ColorModel cm, Rectangle r, Rectangle2D r2d,
+ AffineTransform xform, RenderingHints hints) {
+ return delegate.createContext(cm, r, r2d, xform, hints);
+ }
+
+ @Override
+ public int getTransparency() {
+ return delegate.getTransparency();
+ }
+
+ @Override
+ public void refresh() {
+ Color color = Gui.getRawColor(id);
+ if (color != null) {
+ delegate = color;
+ }
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/GFont.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/GFont.java
new file mode 100644
index 0000000000..edbe39d54c
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/GFont.java
@@ -0,0 +1,310 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.awt.Font;
+import java.awt.font.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.text.AttributedCharacterIterator.Attribute;
+import java.text.CharacterIterator;
+import java.util.Locale;
+import java.util.Map;
+
+public class GFont extends Font implements Refreshable {
+
+ private String id;
+ private Font delegate;
+
+ public GFont(String id) {
+ super("Courier", Font.PLAIN, 12);
+ this.id = id;
+ delegate = Gui.getRawFont(id);
+ if (delegate == null) {
+ delegate = new Font("Courier", Font.PLAIN, 12);
+ }
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public AffineTransform getTransform() {
+ return delegate.getTransform();
+ }
+
+ @Override
+ public void refresh() {
+ Font font = Gui.getRawFont(id);
+ if (font != null) {
+ delegate = font;
+ }
+ }
+
+ @Override
+ public String getFamily() {
+ return delegate.getFamily();
+ }
+
+ @Override
+ public String getFamily(Locale l) {
+ return delegate.getFamily(l);
+ }
+
+ @Override
+ public String getPSName() {
+ return delegate.getPSName();
+ }
+
+ @Override
+ public String getName() {
+ return delegate.getName();
+ }
+
+ @Override
+ public String getFontName() {
+ return delegate.getFontName();
+ }
+
+ @Override
+ public String getFontName(Locale l) {
+ return delegate.getFontName(l);
+ }
+
+ @Override
+ public int getStyle() {
+
+ return delegate.getStyle();
+ }
+
+ @Override
+ public int getSize() {
+ return delegate.getSize();
+ }
+
+ @Override
+ public float getSize2D() {
+ return delegate.getSize2D();
+ }
+
+ @Override
+ public boolean isPlain() {
+ return delegate.isPlain();
+ }
+
+ @Override
+ public boolean isBold() {
+ return delegate.isBold();
+ }
+
+ @Override
+ public boolean isItalic() {
+ return delegate.isItalic();
+ }
+
+ @Override
+ public boolean isTransformed() {
+ return delegate.isTransformed();
+ }
+
+ @Override
+ public boolean hasLayoutAttributes() {
+ return delegate.hasLayoutAttributes();
+ }
+
+ @Override
+ public int hashCode() {
+ return delegate.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return delegate.equals(obj);
+ }
+
+ @Override
+ public String toString() {
+ return delegate.toString();
+ }
+
+ @Override
+ public int getNumGlyphs() {
+ return delegate.getNumGlyphs();
+ }
+
+ @Override
+ public int getMissingGlyphCode() {
+ return delegate.getMissingGlyphCode();
+ }
+
+ @Override
+ public byte getBaselineFor(char c) {
+ return delegate.getBaselineFor(c);
+ }
+
+ @Override
+ public Map getAttributes() {
+ return delegate.getAttributes();
+ }
+
+ @Override
+ public Attribute[] getAvailableAttributes() {
+ return delegate.getAvailableAttributes();
+ }
+
+ @Override
+ public Font deriveFont(int newStyle, float newSize) {
+ return delegate.deriveFont(newStyle, newSize);
+ }
+
+ @Override
+ public Font deriveFont(int newStyle, AffineTransform trans) {
+ return delegate.deriveFont(newStyle, trans);
+ }
+
+ @Override
+ public Font deriveFont(float newSize) {
+ return delegate.deriveFont(newSize);
+ }
+
+ @Override
+ public Font deriveFont(AffineTransform trans) {
+ return delegate.deriveFont(trans);
+ }
+
+ @Override
+ public Font deriveFont(int newStyle) {
+ return delegate.deriveFont(newStyle);
+ }
+
+ @Override
+ public Font deriveFont(Map extends Attribute, ?> attributes) {
+ return delegate.deriveFont(attributes);
+ }
+
+ @Override
+ public boolean canDisplay(char c) {
+ return delegate.canDisplay(c);
+ }
+
+ @Override
+ public boolean canDisplay(int codePoint) {
+ return delegate.canDisplay(codePoint);
+ }
+
+ @Override
+ public int canDisplayUpTo(String str) {
+ return delegate.canDisplayUpTo(str);
+ }
+
+ @Override
+ public int canDisplayUpTo(char[] text, int start, int limit) {
+ return delegate.canDisplayUpTo(text, start, limit);
+ }
+
+ @Override
+ public int canDisplayUpTo(CharacterIterator iter, int start, int limit) {
+ return delegate.canDisplayUpTo(iter, start, limit);
+ }
+
+ @Override
+ public float getItalicAngle() {
+ return delegate.getItalicAngle();
+ }
+
+ @Override
+ public boolean hasUniformLineMetrics() {
+ return delegate.hasUniformLineMetrics();
+ }
+
+ @Override
+ public LineMetrics getLineMetrics(String str, FontRenderContext frc) {
+ return delegate.getLineMetrics(str, frc);
+ }
+
+ @Override
+ public LineMetrics getLineMetrics(String str, int beginIndex, int limit,
+ FontRenderContext frc) {
+ return delegate.getLineMetrics(str, beginIndex, limit, frc);
+ }
+
+ @Override
+ public LineMetrics getLineMetrics(char[] chars, int beginIndex, int limit,
+ FontRenderContext frc) {
+ return delegate.getLineMetrics(chars, beginIndex, limit, frc);
+ }
+
+ @Override
+ public LineMetrics getLineMetrics(CharacterIterator ci, int beginIndex, int limit,
+ FontRenderContext frc) {
+ return delegate.getLineMetrics(ci, beginIndex, limit, frc);
+ }
+
+ @Override
+ public Rectangle2D getStringBounds(String str, FontRenderContext frc) {
+ return delegate.getStringBounds(str, frc);
+ }
+
+ @Override
+ public Rectangle2D getStringBounds(String str, int beginIndex, int limit,
+ FontRenderContext frc) {
+ return delegate.getStringBounds(str, beginIndex, limit, frc);
+ }
+
+ @Override
+ public Rectangle2D getStringBounds(char[] chars, int beginIndex, int limit,
+ FontRenderContext frc) {
+ return delegate.getStringBounds(chars, beginIndex, limit, frc);
+ }
+
+ @Override
+ public Rectangle2D getStringBounds(CharacterIterator ci, int beginIndex, int limit,
+ FontRenderContext frc) {
+ return delegate.getStringBounds(ci, beginIndex, limit, frc);
+ }
+
+ @Override
+ public Rectangle2D getMaxCharBounds(FontRenderContext frc) {
+ return delegate.getMaxCharBounds(frc);
+ }
+
+ @Override
+ public GlyphVector createGlyphVector(FontRenderContext frc, String str) {
+ return delegate.createGlyphVector(frc, str);
+ }
+
+ @Override
+ public GlyphVector createGlyphVector(FontRenderContext frc, char[] chars) {
+ return delegate.createGlyphVector(frc, chars);
+ }
+
+ @Override
+ public GlyphVector createGlyphVector(FontRenderContext frc, CharacterIterator ci) {
+ return delegate.createGlyphVector(frc, ci);
+ }
+
+ @Override
+ public GlyphVector createGlyphVector(FontRenderContext frc, int[] glyphCodes) {
+ return delegate.createGlyphVector(frc, glyphCodes);
+ }
+
+ @Override
+ public GlyphVector layoutGlyphVector(FontRenderContext frc, char[] text, int start, int limit,
+ int flags) {
+ return delegate.layoutGlyphVector(frc, text, start, limit, flags);
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/GIcon.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/GIcon.java
new file mode 100644
index 0000000000..d1ff95720f
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/GIcon.java
@@ -0,0 +1,65 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.awt.Component;
+import java.awt.Graphics;
+
+import javax.swing.Icon;
+
+import resources.ResourceManager;
+
+public class GIcon implements Icon, Refreshable {
+
+ private String id;
+ private Icon delegate;
+
+ public GIcon(String id) {
+ this.id = id;
+ delegate = Gui.getRawIcon(id);
+ if (delegate == null) {
+ delegate = ResourceManager.getDefaultIcon();
+ }
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public void paintIcon(Component c, Graphics g, int x, int y) {
+ delegate.paintIcon(c, g, x, y);
+ }
+
+ @Override
+ public int getIconWidth() {
+ return delegate.getIconWidth();
+ }
+
+ @Override
+ public int getIconHeight() {
+ return delegate.getIconHeight();
+ }
+
+ @Override
+ public void refresh() {
+ Icon icon = Gui.getRawIcon(id);
+ if (icon != null) {
+ delegate = icon;
+ }
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/GTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/GTheme.java
new file mode 100644
index 0000000000..46fb8ea3e4
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/GTheme.java
@@ -0,0 +1,281 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.io.*;
+import java.util.*;
+
+import ghidra.docking.util.LookAndFeelUtils;
+import ghidra.util.WebColors;
+
+/**
+ * Class to store all the configurable appearance properties (Colors, Fonts, Icons, Look and Feel)
+ * in an application.
+ */
+public class GTheme extends GThemeValueMap {
+ static final String THEME_NAME_KEY = "name";
+ static final String THEME_LOOK_AND_FEEL_KEY = "lookAndFeel";
+ static final String THEME_IS_DARK_KEY = "dark";
+
+ private final String name;
+ private final String lookAndFeelName;
+ private final boolean isDark;
+
+ public GTheme(String name) {
+ this(name, LookAndFeelUtils.SYSTEM, false);
+
+ }
+
+ /**
+ * Creates a new empty GTheme with the given name
+ * @param name the name for the new GTheme
+ * @param lookAndFeelName the look and feel used by this theme
+ * @param isDark true if this theme uses dark backgrounds instead of the standard
+ * light backgrounds
+ */
+ protected GTheme(String name, String lookAndFeelName, boolean isDark) {
+ this.name = name;
+ this.lookAndFeelName = lookAndFeelName;
+ this.isDark = isDark;
+ }
+
+ /**
+ * Returns the name of this GTheme
+ * @return the name of this GTheme
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Returns the name of the LookAndFeel associated with this GTheme
+ * @return the name of the LookAndFeel associated with this GTheme
+ */
+ public String getLookAndFeelName() {
+ return lookAndFeelName;
+ }
+
+ /**
+ * Returns true if this theme should use dark defaults
+ * @return true if this theme should use dark defaults
+ */
+ public boolean isDark() {
+ return isDark;
+ }
+
+ /**
+ * Returns a String that can be used to find and restore this theme.
+ * @return a String that can be used to find and restore this theme.
+ */
+ public String getThemeLocater() {
+ return "Default";
+ }
+
+ /**
+ * Sets the Color for the given id
+ * @param id the id to associate with the given Color
+ * @param color the Color to associate with the given id
+ */
+ public void setColor(String id, Color color) {
+ addColor(new ColorValue(id, color));
+ }
+
+ /**
+ * Sets a referred Color for the given id
+ * @param id the id to associate with the refId
+ * @param refId the id of an indirect Color lookup for the given id.
+ */
+ public void setColorRef(String id, String refId) {
+ addColor(new ColorValue(id, refId));
+ }
+
+ /**
+ * Sets the Font for the given id
+ * @param id the id to associate with the given Font
+ * @param font the Font to associate with the given id
+ */
+ public void setFont(String id, Font font) {
+ addFont(new FontValue(id, font));
+ }
+
+ /**
+ * Sets a referred font for the given id
+ * @param id the id to associate with the given Font reference id
+ * @param refId the id of an indirect Font lookup for the given id.
+ */
+ public void setFontRef(String id, String refId) {
+ addFont(new FontValue(id, refId));
+ }
+
+ /**
+ * Sets the icon for the given id
+ * @param id the id to associate with the given IconPath
+ * @param iconPath the path of the icon to assign to the given id
+ */
+ public void setIcon(String id, String iconPath) {
+ addIconPath(new IconValue(id, null, iconPath));
+ }
+
+ /**
+ * Sets a referred icon id for the given id
+ * @param id the id to associate with the given Font
+ * @param refId the id of an indirect Icon lookup for the given id.
+ */
+ public void setIconRef(String id, String refId) {
+ addIconPath(new IconValue(id, refId, null));
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ GTheme other = (GTheme) obj;
+ return Objects.equals(name, other.name) &&
+ Objects.equals(lookAndFeelName, other.lookAndFeelName) &&
+ Objects.equals(isDark, other.isDark);
+ }
+
+ /**
+ * Creates a new file based GTheme with the same values as this GTheme
+ * @param saveToFile file to associate and save this GTheme to
+ * @return the new theme
+ * @throws IOException if a general I/O exception occurs
+ */
+ public GTheme saveToFile(File saveToFile) throws IOException {
+ return doSaveToFile(saveToFile, this);
+ }
+
+ /**
+ * Creates a new file based GTheme with the same values as this GTheme and includes default
+ * values not modified by this theme.
+ * @param saveToFile file to associate and save this GTheme to
+ * @param defaults the collection of default values to include in the output file
+ * @return the new theme
+ * @throws IOException if a general I/O exception occurs
+ */
+ public GTheme saveToFile(File saveToFile, GThemeValueMap defaults) throws IOException {
+ GThemeValueMap combined = new GThemeValueMap();
+ combined.load(defaults);
+ combined.load(this);
+ return doSaveToFile(saveToFile, combined);
+ }
+
+ private GTheme doSaveToFile(File saveToFile, GThemeValueMap values) throws IOException {
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(saveToFile))) {
+ List colors = values.getColors();
+ Collections.sort(colors);
+
+ List fonts = values.getFonts();
+ Collections.sort(fonts);
+
+ List icons = values.getIconPaths();
+ Collections.sort(icons);
+
+ writer.write(THEME_NAME_KEY + " = " + name);
+ writer.newLine();
+
+ writer.write(THEME_LOOK_AND_FEEL_KEY + " = " + lookAndFeelName);
+ writer.newLine();
+
+ if (isDark()) {
+ writer.write(THEME_IS_DARK_KEY + " = true");
+ writer.newLine();
+ }
+
+ for (ColorValue colorValue : colors) {
+ String outputId = colorValue.toExternalId(colorValue.getId());
+ writer.write(outputId + " = " + getValueOutput(colorValue));
+ writer.newLine();
+ }
+
+ for (FontValue fontValue : fonts) {
+ String outputId = fontValue.toExternalId(fontValue.getId());
+ writer.write(outputId + " = " + getValueOutput(fontValue));
+ writer.newLine();
+ }
+
+ for (IconValue iconValue : icons) {
+ String outputId = iconValue.toExternalId(iconValue.getId());
+ writer.write(outputId + " = " + getValueOutput(iconValue));
+ writer.newLine();
+ }
+
+ }
+ return new FileGTheme(saveToFile);
+ }
+
+ private String getValueOutput(IconValue iconValue) {
+ if (iconValue.getReferenceId() != null) {
+ return iconValue.toExternalId(iconValue.getReferenceId());
+ }
+ return iconValue.getRawValue();
+ }
+
+ private String getValueOutput(FontValue fontValue) {
+ if (fontValue.getReferenceId() != null) {
+ return fontValue.toExternalId(fontValue.getReferenceId());
+ }
+ Font font = fontValue.getRawValue();
+ return String.format("%s-%s-%s", font.getName(), getStyleString(font), font.getSize());
+ }
+
+ private String getStyleString(Font font) {
+ boolean bold = font.isBold();
+ boolean italic = font.isItalic();
+ if (bold && italic) {
+ return "BOLDITALIC";
+ }
+ if (bold) {
+ return "BOLD";
+ }
+ if (italic) {
+ return "ITALIC";
+ }
+ return "PLAIN";
+ }
+
+ private String getValueOutput(ColorValue colorValue) {
+ if (colorValue.getReferenceId() != null) {
+ return colorValue.toExternalId(colorValue.getReferenceId());
+ }
+ Color color = colorValue.getRawValue();
+ String outputString = WebColors.toString(color, false);
+ String colorName = WebColors.toWebColorName(color);
+ if (colorName != null) {
+ outputString += " // " + colorName;
+ }
+ return outputString;
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeDefaults.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeDefaults.java
new file mode 100644
index 0000000000..7a067aa46b
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeDefaults.java
@@ -0,0 +1,50 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.awt.Color;
+
+/** TODO doc how clients should use this in their code, with
+ *
+ *
+ * Colors.BACKGROUND
+ * Colors.Java.BORDER
+ */
+public class GThemeDefaults {
+ public static final String STANDARD_DEFAULTS = "Standard Defaults"; // core defaults map name
+ public static final String DARK = "Dark"; // defaults map name for dark based themes
+
+ public static class Ids {
+
+ public static final String COLOR_BG = "color.bg"; // TODO
+
+ public static class Java {
+ public static final String BORDER = "Component.borderColor"; // TODO
+ }
+ }
+
+ public static class Colors {
+ //@formatter:off
+ public static final GColor BACKGROUND = new GColor("color.bg");
+ //@formatter:on
+
+ public static class Java {
+ public static final Color BORDER = new GColor(Ids.Java.BORDER);
+ }
+
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeDialog.java
new file mode 100644
index 0000000000..5b8e3775f3
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeDialog.java
@@ -0,0 +1,86 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.io.File;
+import java.io.IOException;
+
+import javax.swing.*;
+
+import docking.DialogComponentProvider;
+import docking.widgets.filechooser.GhidraFileChooser;
+import docking.widgets.filechooser.GhidraFileChooserMode;
+import docking.widgets.table.GFilterTable;
+import docking.widgets.table.GTable;
+import ghidra.util.filechooser.GhidraFileFilter;
+
+public class GThemeDialog extends DialogComponentProvider {
+
+ public GThemeDialog() {
+ super("Theme Dialog");
+ addWorkPanel(createMainPanel());
+ addOKButton();
+ addCancelButton();
+ setOkButtonText("Save");
+
+ }
+
+ @Override
+ protected void okCallback() {
+ GhidraFileChooser chooser = new GhidraFileChooser(getComponent());
+ chooser.setTitle("Choose Theme File");
+ chooser.setApproveButtonText("Select Output File");
+ chooser.setApproveButtonToolTipText("Select File");
+ chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY);
+ chooser.setSelectedFileFilter(GhidraFileFilter.ALL);
+ File file = chooser.getSelectedFile();
+ try {
+ Gui.getActiveTheme().saveToFile(file, Gui.getAllDefaultValues());
+ }
+ catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+
+ private JComponent createMainPanel() {
+ JPanel panel = new JPanel();
+
+ panel.setLayout(new BorderLayout());
+
+ panel.add(buildTabedTables());
+ return panel;
+ }
+
+ private Component buildTabedTables() {
+ JTabbedPane tabbedPane = new JTabbedPane();
+ tabbedPane.add("Colors", buildColorTable());
+ return tabbedPane;
+ }
+
+ private JComponent buildColorTable() {
+ ThemeColorTableModel colorTableModel = new ThemeColorTableModel(Gui.getActiveTheme());
+
+ GTable colorTable = new GTable(colorTableModel);
+ colorTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ GFilterTable filterTable = new GFilterTable<>(colorTableModel);
+ filterTable.getTable().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ return filterTable;
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeValueMap.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeValueMap.java
new file mode 100644
index 0000000000..0f8cde7992
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeValueMap.java
@@ -0,0 +1,96 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.util.*;
+
+public class GThemeValueMap {
+ Map colorMap = new HashMap<>();
+ Map fontMap = new HashMap<>();
+ Map iconMap = new HashMap<>();
+
+ public GThemeValueMap() {
+ }
+
+ public GThemeValueMap(GThemeValueMap initial) {
+ load(initial);
+ }
+
+ public void addColor(ColorValue value) {
+ if (value != null) {
+ colorMap.put(value.getId(), value);
+ }
+ }
+
+ public void addFont(FontValue value) {
+ if (value != null) {
+ fontMap.put(value.getId(), value);
+ }
+ }
+
+ public void addIconPath(IconValue value) {
+ if (value != null) {
+ iconMap.put(value.getId(), value);
+ }
+ }
+
+ public ColorValue getColor(String id) {
+ return colorMap.get(id);
+ }
+
+ public FontValue getFont(String id) {
+ return fontMap.get(id);
+ }
+
+ public IconValue getIcon(String id) {
+ return iconMap.get(id);
+ }
+
+ public void load(GThemeValueMap valueMap) {
+ valueMap.colorMap.values().forEach(v -> addColor(v));
+ valueMap.fontMap.values().forEach(v -> addFont(v));
+ valueMap.iconMap.values().forEach(v -> addIconPath(v));
+
+ }
+
+ public List getColors() {
+ return new ArrayList<>(colorMap.values());
+ }
+
+ public List getFonts() {
+ return new ArrayList<>(fontMap.values());
+ }
+
+ public List getIconPaths() {
+ return new ArrayList<>(iconMap.values());
+ }
+
+ public boolean containsColor(String id) {
+ return colorMap.containsKey(id);
+ }
+
+ public boolean containsFont(String id) {
+ return colorMap.containsKey(id);
+ }
+
+ public boolean containsIconPath(String id) {
+ return colorMap.containsKey(id);
+ }
+
+ public Object size() {
+ return colorMap.size() + fontMap.size() + iconMap.size();
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/Gui.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/Gui.java
new file mode 100644
index 0000000000..a5029a8193
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/Gui.java
@@ -0,0 +1,310 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.awt.*;
+import java.io.*;
+import java.util.*;
+import java.util.List;
+
+import javax.swing.*;
+
+import docking.framework.ApplicationInformationDisplayFactory;
+import ghidra.docking.util.LookAndFeelUtils;
+import ghidra.framework.Application;
+import ghidra.framework.preferences.Preferences;
+import ghidra.util.Msg;
+import ghidra.util.classfinder.ClassSearcher;
+import resources.ResourceManager;
+import utilities.util.reflection.ReflectionUtilities;
+
+// TODO doc what this concept is
+public class Gui {
+
+ public static final String BACKGROUND_KEY = "color.bg.text";
+
+ private static final String THEME_PREFFERENCE_KEY = "Theme";
+
+ private static GTheme activeTheme = new DefaultTheme();
+ private static Set allThemes;
+
+ private static GThemeValueMap ghidraCoreDefaults = new GThemeValueMap();
+ private static GThemeValueMap javaDefaults;
+ private static GThemeValueMap currentValues = new GThemeValueMap();
+
+ private static GThemeValueMap darkDefaults = new GThemeValueMap();
+
+ private static ThemePropertiesLoader themePropertiesLoader = new ThemePropertiesLoader();
+
+ static void setPropertiesLoader(ThemePropertiesLoader loader) {
+ themePropertiesLoader = loader;
+ }
+
+ private Gui() {
+ // static utils class, can't construct
+ }
+
+ public static void initialize() {
+ themePropertiesLoader.initialize();
+ loadThemeDefaults();
+ setTheme(getThemeFromPreferences());
+ LookAndFeelUtils.installGlobalOverrides();
+ platformSpecificFixups();
+ }
+
+ private static void loadThemeDefaults() {
+ ghidraCoreDefaults = themePropertiesLoader.getDefaults();
+ darkDefaults = themePropertiesLoader.getDarkDefaults();
+ }
+
+ public static void setTheme(GTheme theme) {
+ activeTheme = theme;
+ LookAndFeelUtils.setLookAndFeel(theme.getLookAndFeelName());
+ javaDefaults = mineJavaDefaults();
+ currentValues = buildCurrentValues(theme);
+ installBackIntoJava();
+ }
+
+ private static void installBackIntoJava() {
+ UIDefaults defaults = UIManager.getDefaults();
+ for (ColorValue color : javaDefaults.getColors()) {
+ String id = color.getId();
+ defaults.put(id, new GColor(id));
+ }
+ }
+
+ public static boolean isJavaDefinedColor(String id) {
+ return javaDefaults.containsColor(id);
+ }
+
+ public static GThemeValueMap getAllValues() {
+ return new GThemeValueMap(currentValues);
+ }
+
+ public static GThemeValueMap getAllDefaultValues() {
+ GThemeValueMap currentDefaults = new GThemeValueMap();
+ currentDefaults.load(javaDefaults);
+ currentDefaults.load(ghidraCoreDefaults);
+ if (activeTheme.isDark()) {
+ currentDefaults.load(darkDefaults);
+ }
+ return currentDefaults;
+ }
+
+ public static Set getAllThemes() {
+ if (allThemes == null) {
+ allThemes = findThemes();
+ }
+ return Collections.unmodifiableSet(allThemes);
+ }
+
+ public static Color darker(Color color) {
+ if (activeTheme.isDark()) {
+ return color.brighter();
+ }
+ return color.darker();
+ }
+
+ public static Color brighter(Color color) {
+ if (activeTheme.isDark()) {
+ return color.darker();
+ }
+ return color.brighter();
+ }
+
+ public static GFont getFont(String id) {
+ return new GFont(id);
+ }
+
+ public static GIcon getIcon(String id) {
+ return new GIcon(id);
+ }
+
+ public static void saveThemeToPreferneces(GTheme theme) {
+ Preferences.setProperty(THEME_PREFFERENCE_KEY, theme.getThemeLocater());
+ Preferences.store();
+ }
+
+ public static GTheme getActiveTheme() {
+ return activeTheme;
+ }
+
+ public static String getLookAndFeelName() {
+ return activeTheme.getLookAndFeelName();
+ }
+
+ private static void platformSpecificFixups() {
+
+ // Set the dock icon for macOS
+ if (Taskbar.isTaskbarSupported()) {
+ Taskbar taskbar = Taskbar.getTaskbar();
+ if (taskbar.isSupported(Taskbar.Feature.ICON_IMAGE)) {
+ taskbar.setIconImage(ApplicationInformationDisplayFactory.getLargestWindowIcon());
+ }
+ }
+ }
+
+ static Color getRawColor(String id) {
+ ColorValue color = currentValues.getColor(id);
+ if (color == null) {
+ Throwable t = getFilteredTrace();
+
+ Msg.error(Gui.class, "No color value registered for: " + id, t);
+ return null;
+ }
+ return color.get(currentValues);
+ }
+
+ static Font getRawFont(String id) {
+ FontValue font = currentValues.getFont(id);
+ if (font == null) {
+ Throwable t = getFilteredTrace();
+
+ Msg.error(Gui.class, "No font value registered for: " + id, t);
+ return null;
+ }
+ return font.get(currentValues);
+ }
+
+ public static Icon getRawIcon(String id) {
+ IconValue icon = currentValues.getIcon(id);
+ if (icon == null) {
+ Throwable t = getFilteredTrace();
+
+ Msg.error(Gui.class, "No color value registered for: " + id, t);
+ return null;
+ }
+ String iconPath = icon.get(currentValues);
+ return ResourceManager.loadImage(iconPath);
+ }
+
+ private static Throwable getFilteredTrace() {
+ Throwable t = ReflectionUtilities.createThrowableWithStackOlderThan();
+ StackTraceElement[] trace = t.getStackTrace();
+ StackTraceElement[] filtered =
+ ReflectionUtilities.filterStackTrace(trace, "java.", "theme.Gui", "theme.GColor");
+ t.setStackTrace(filtered);
+ return t;
+ }
+
+ private static GThemeValueMap buildCurrentValues(GTheme theme) {
+ GThemeValueMap map = new GThemeValueMap();
+
+ map.load(javaDefaults);
+ map.load(ghidraCoreDefaults);
+ if (theme.isDark()) {
+ map.load(darkDefaults);
+ }
+ map.load(theme);
+ return map;
+ }
+
+ private static GThemeValueMap mineJavaDefaults() {
+ GThemeValueMap values = new GThemeValueMap();
+ // for now, just doing color properties.
+ List ids = LookAndFeelUtils.getLookAndFeelIdsForType(Color.class);
+ for (String id : ids) {
+ // Create a new color to ensure we are not storing a UIResource; otherwise java
+ // java ignore the color because the UI widgets take liberties when UIResources
+ // are being used.
+ Color lafColor = new Color(UIManager.getColor(id).getRGB(), true);
+ values.addColor(new ColorValue(id, lafColor));
+ }
+ return values;
+ }
+
+ private static Set findThemes() {
+ Set set = new HashSet<>();
+ set.addAll(findDiscoverableThemes());
+ set.addAll(loadThemesFromFiles());
+
+ // The set should contains a duplicate of the active theme. Make sure the active theme
+ // instance is the one in the set
+ set.remove(activeTheme);
+ set.add(activeTheme);
+ return set;
+ }
+
+ private static Collection loadThemesFromFiles() {
+ List fileList = new ArrayList<>();
+
+ File dir = Application.getUserSettingsDirectory();
+ FileFilter themeFileFilter = file -> file.getName().endsWith(".theme");
+ fileList.addAll(Arrays.asList(dir.listFiles(themeFileFilter)));
+
+ List list = new ArrayList<>();
+ for (File file : fileList) {
+ GTheme theme = loadTheme(file);
+ if (theme != null) {
+ list.add(theme);
+ }
+ }
+ return list;
+ }
+
+ private static GTheme loadTheme(File file) {
+ try {
+ return new FileGTheme(file);
+ }
+ catch (IOException e) {
+ Msg.error(Gui.class, "Could not load theme from file: " + file.getAbsolutePath());
+ }
+ return null;
+ }
+
+ private static Collection findDiscoverableThemes() {
+ return ClassSearcher.getInstances(DiscoverableGTheme.class);
+ }
+
+ private static GTheme getThemeFromPreferences() {
+ String themeId = Preferences.getProperty(THEME_PREFFERENCE_KEY, "Default", true);
+ if (themeId.startsWith(FileGTheme.FILE_PREFIX)) {
+ String filename = themeId.substring(FileGTheme.FILE_PREFIX.length());
+ try {
+ return new FileGTheme(new File(filename));
+ }
+ catch (IOException e) {
+ Msg.showError(GTheme.class, null, "Can't Load Previous Theme",
+ "Error loading theme file: " + filename);
+ }
+ }
+ else if (themeId.startsWith(DiscoverableGTheme.CLASS_PREFIX)) {
+ String className = themeId.substring(DiscoverableGTheme.CLASS_PREFIX.length());
+ try {
+ Class> forName = Class.forName(className);
+ return (GTheme) forName.getDeclaredConstructor().newInstance();
+ }
+ catch (Exception e) {
+ Msg.showError(GTheme.class, null, "Can't Load Previous Theme",
+ "Can't find or instantiate class: " + className);
+ }
+ }
+ return new DefaultTheme();
+ }
+
+ public static GThemeValueMap getCoreDefaults() {
+ GThemeValueMap map = new GThemeValueMap(ghidraCoreDefaults);
+ map.load(javaDefaults);
+ return map;
+ }
+
+ public static GThemeValueMap getDarkDefaults() {
+ GThemeValueMap map = new GThemeValueMap(ghidraCoreDefaults);
+ map.load(darkDefaults);
+ return map;
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/IconValue.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/IconValue.java
new file mode 100644
index 0000000000..b51f9ca38c
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/IconValue.java
@@ -0,0 +1,72 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import ghidra.util.Msg;
+
+public class IconValue extends ThemeValue {
+ static final String ICON_ID_PREFIX = "icon.";
+
+ public static final String LAST_RESORT_DEFAULT = "images/bomb.gif";
+
+ private static final String EXTERNAL_PREFIX = "[icon]";
+
+ public IconValue(String id, String refId, String iconPath) {
+ super(id, refId, iconPath);
+ }
+
+ @Override
+ protected IconValue getReferredValue(GThemeValueMap values, String refId) {
+ return values.getIcon(refId);
+ }
+
+ @Override
+ protected String getUnresolvedReferenceValue(String id) {
+ Msg.warn(this,
+ "Could not resolve indirect icon path for" + id + ", using last resort default");
+ return LAST_RESORT_DEFAULT;
+ }
+
+ @Override
+ protected String getIdPrefix() {
+ return ICON_ID_PREFIX;
+ }
+
+ @Override
+ public String toExternalId(String internalId) {
+ if (internalId.startsWith(ICON_ID_PREFIX)) {
+ return internalId;
+ }
+ return EXTERNAL_PREFIX + internalId;
+ }
+
+ @Override
+ public String fromExternalId(String externalId) {
+ if (externalId.startsWith(EXTERNAL_PREFIX)) {
+ return externalId.substring(EXTERNAL_PREFIX.length());
+ }
+ return externalId;
+ }
+
+ public static boolean isIconKey(String key) {
+ return key.startsWith(ICON_ID_PREFIX) || key.startsWith(EXTERNAL_PREFIX);
+ }
+
+ @Override
+ protected int compareValues(String v1, String v2) {
+ return v1.compareTo(v2);
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/Refreshable.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/Refreshable.java
new file mode 100644
index 0000000000..ffc8f5c79e
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/Refreshable.java
@@ -0,0 +1,20 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+public interface Refreshable {
+ public void refresh();
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeColorTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeColorTableModel.java
new file mode 100644
index 0000000000..80b426cbbe
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeColorTableModel.java
@@ -0,0 +1,176 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.awt.*;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.swing.JLabel;
+
+import docking.widgets.table.*;
+import ghidra.docking.settings.Settings;
+import ghidra.framework.plugintool.ServiceProvider;
+import ghidra.framework.plugintool.ServiceProviderStub;
+import ghidra.util.ColorUtils;
+import ghidra.util.WebColors;
+import ghidra.util.table.column.AbstractGColumnRenderer;
+import ghidra.util.table.column.GColumnRenderer;
+
+public class ThemeColorTableModel extends GDynamicColumnTableModel {
+ private List colors;
+
+ public ThemeColorTableModel(GTheme theme) {
+ super(new ServiceProviderStub());
+ colors = Gui.getAllValues().getColors();
+ }
+
+ @Override
+ public String getName() {
+ return "Users";
+ }
+
+ @Override
+ public List getModelData() {
+ return colors;
+ }
+
+ @Override
+ protected TableColumnDescriptor createTableColumnDescriptor() {
+ TableColumnDescriptor descriptor = new TableColumnDescriptor<>();
+ descriptor.addVisibleColumn(new IdColumn());
+ descriptor.addVisibleColumn(new IsLafPropertyColumn());
+ descriptor.addVisibleColumn(new ValueColumn("Current Color", Gui.getAllValues()));
+ descriptor.addVisibleColumn(new ValueColumn("Core Defaults", Gui.getAllValues()));
+ descriptor.addVisibleColumn(new ValueColumn("Dark Defaults", Gui.getDarkDefaults()));
+ return descriptor;
+ }
+
+ @Override
+ public Object getDataSource() {
+ return null;
+ }
+
+ class IdColumn extends AbstractDynamicTableColumn {
+
+ @Override
+ public String getColumnName() {
+ return "Id";
+ }
+
+ @Override
+ public String getValue(ColorValue themeColor, Settings settings, Object data,
+ ServiceProvider provider) throws IllegalArgumentException {
+ return themeColor.getId();
+ }
+ }
+
+ class ValueColumn extends AbstractDynamicTableColumn {
+ private ThemeColorRenderer renderer = new ThemeColorRenderer(Gui.getAllValues());
+ private GThemeValueMap valueMap;
+ private String name;
+
+ ValueColumn(String name, GThemeValueMap valueMap) {
+ this.name = name;
+ this.valueMap = valueMap;
+ renderer = new ThemeColorRenderer(valueMap);
+ }
+
+ @Override
+ public String getColumnName() {
+ return name;
+ }
+
+ @Override
+ public String getValue(ColorValue themeColor, Settings settings, Object data,
+ ServiceProvider provider) throws IllegalArgumentException {
+ return themeColor.getId();
+ }
+
+ @Override
+ public GColumnRenderer getColumnRenderer() {
+ return renderer;
+ }
+
+ public Comparator getComparator() {
+ return (s1, s2) -> valueMap.getColor(s1).compareValue(valueMap.getColor(s2));
+ }
+
+ }
+
+ class IsLafPropertyColumn extends AbstractDynamicTableColumn {
+
+ @Override
+ public String getColumnName() {
+ return "Is Laf";
+ }
+
+ @Override
+ public Boolean getValue(ColorValue themeColor, Settings settings, Object data,
+ ServiceProvider provider) throws IllegalArgumentException {
+ return Gui.isJavaDefinedColor(themeColor.getId());
+ }
+ }
+
+ private class ThemeColorRenderer extends AbstractGColumnRenderer {
+
+ private GThemeValueMap valueMap;
+
+ public ThemeColorRenderer(GThemeValueMap valueMap) {
+ this.valueMap = valueMap;
+ setFont(new Font("Monospaced", Font.PLAIN, 12));
+ }
+
+ @Override
+ public Component getTableCellRendererComponent(GTableCellRenderingData data) {
+
+ JLabel label = (JLabel) super.getTableCellRendererComponent(data);
+ String id = (String) data.getValue();
+
+ ColorValue colorValue = valueMap.getColor(id);
+ Color color;
+ String text;
+ if (colorValue != null) {
+ color = colorValue.get(valueMap);
+ if (colorValue.getReferenceId() != null) {
+ text = colorValue.getReferenceId();
+ }
+ else {
+ text = WebColors.toString(color, false);
+ String name = WebColors.toWebColorName(color);
+ if (name != null) {
+ text += " [" + name + "]";
+ }
+ }
+
+ }
+ else {
+ color = GThemeDefaults.Colors.BACKGROUND;
+ text = "";
+ }
+ label.setText(text);
+ label.setBackground(color);
+ label.setForeground(ColorUtils.contrastForegroundColor(color));
+ label.setOpaque(true);
+ return label;
+ }
+
+ @Override
+ public String getFilterString(String id, Settings settings) {
+ return id;
+ }
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemePropertiesLoader.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemePropertiesLoader.java
new file mode 100644
index 0000000000..9ee7d65728
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemePropertiesLoader.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 docking.theme;
+
+import java.io.IOException;
+import java.util.List;
+
+import generic.jar.ResourceFile;
+import ghidra.framework.Application;
+import ghidra.util.Msg;
+
+public class ThemePropertiesLoader {
+ GThemeValueMap defaults = new GThemeValueMap();
+ GThemeValueMap darkDefaults = new GThemeValueMap();
+
+ ThemePropertiesLoader() {
+ }
+
+ public void initialize() {
+ List themeDefaultFiles =
+ Application.findFilesByExtensionInApplication(".theme.properties");
+
+ for (ResourceFile resourceFile : themeDefaultFiles) {
+ Msg.debug(this, "found theme file: " + resourceFile.getAbsolutePath());
+ try {
+ ThemePropertyFileReader reader = new ThemePropertyFileReader(resourceFile);
+ defaults.load(reader.getDefaultValues());
+ darkDefaults.load(reader.getDarkDefaultValues());
+ }
+ catch (IOException e) {
+ Msg.error(this,
+ "Error reading theme properties file: " + resourceFile.getAbsolutePath());
+ }
+ }
+ }
+
+ public GThemeValueMap getDefaults() {
+ return defaults;
+ }
+
+ public GThemeValueMap getDarkDefaults() {
+ return darkDefaults;
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemePropertyFileReader.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemePropertyFileReader.java
new file mode 100644
index 0000000000..3e28dc29a1
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemePropertyFileReader.java
@@ -0,0 +1,267 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.io.*;
+import java.util.*;
+
+import org.apache.commons.collections4.map.HashedMap;
+
+import generic.jar.ResourceFile;
+import ghidra.util.Msg;
+import ghidra.util.WebColors;
+
+public class ThemePropertyFileReader {
+
+ private static final String NO_SECTION = "[No Section]";
+ private static final String DEFAULTS = "[Defaults]";
+ private static final String DARK_DEFAULTS = "[Dark Defaults]";
+
+ private GThemeValueMap defaults = new GThemeValueMap();
+ private GThemeValueMap darkDefaults = new GThemeValueMap();
+ private Map> aliasMap = new HashedMap<>();
+ private List errors = new ArrayList<>();
+ private String filePath;
+
+ public ThemePropertyFileReader(File file) throws IOException {
+ filePath = file.getAbsolutePath();
+ try (Reader reader = new FileReader(file)) {
+ read(reader);
+ }
+ }
+
+ public ThemePropertyFileReader(ResourceFile file) throws IOException {
+ filePath = file.getAbsolutePath();
+ try (Reader reader = new InputStreamReader(file.getInputStream())) {
+ read(reader);
+ }
+ }
+
+ ThemePropertyFileReader(String source, Reader reader) throws IOException {
+ filePath = source;
+ read(reader);
+ }
+
+ public GThemeValueMap getDefaultValues() {
+ return defaults;
+ }
+
+ public GThemeValueMap getDarkDefaultValues() {
+ return darkDefaults;
+ }
+
+ public Map> getAliases() {
+ return aliasMap;
+ }
+
+ public List getErrors() {
+ return errors;
+ }
+
+ private void read(Reader reader) throws IOException {
+ List sections = readSections(new LineNumberReader(reader));
+ for (Section section : sections) {
+ switch (section.getName()) {
+ case NO_SECTION:
+ processNoSection(section);
+ break;
+ case DEFAULTS:
+ processValues(defaults, section);
+ break;
+ case DARK_DEFAULTS:
+ processValues(darkDefaults, section);
+ break;
+ default:
+ error(section.getLineNumber(),
+ "Encounded unknown theme file section: " + section.getName());
+ }
+ }
+
+ }
+
+ protected void processNoSection(Section section) throws IOException {
+ if (!section.isEmpty()) {
+ error(0, "Theme properties file has values defined outside of a defined section");
+ }
+
+ }
+
+ public void processValues(GThemeValueMap valueMap, Section section) {
+ for (String key : section.getKeys()) {
+ String value = section.getValue(key);
+ int lineNumber = section.getLineNumber(key);
+ if (ColorValue.isColorKey(key)) {
+ valueMap.addColor(parseColorProperty(key, value, lineNumber));
+ }
+ else if (FontValue.isFontKey(key)) {
+ valueMap.addFont(parseFontProperty(key, value, lineNumber));
+ }
+ else if (IconValue.isIconKey(key)) {
+ valueMap.addIconPath(parseIconProperty(key, value));
+ }
+ else {
+ error(lineNumber, "Can't process property: " + key + " = " + value);
+ }
+ }
+ }
+
+ private IconValue parseIconProperty(String key, String value) {
+ if (IconValue.isIconKey(value)) {
+ return new IconValue(key, value, null);
+ }
+ return new IconValue(key, null, value);
+ }
+
+ private FontValue parseFontProperty(String key, String value, int lineNumber) {
+ if (FontValue.isFontKey(value)) {
+ return new FontValue(key, value);
+ }
+ Font font = Font.decode(value);
+ if (font == null) {
+ error(lineNumber, "Could not parse Color: " + value);
+ }
+ return font == null ? null : new FontValue(key, font);
+ }
+
+ private ColorValue parseColorProperty(String key, String value, int lineNumber) {
+ if (ColorValue.isColorKey(value)) {
+ return new ColorValue(key, value);
+ }
+ Color color = WebColors.getColor(value);
+ if (color == null) {
+ error(lineNumber, "Could not parse Color: " + value);
+ }
+ return color == null ? null : new ColorValue(key, color);
+ }
+
+ private List readSections(LineNumberReader reader) throws IOException {
+
+ List sections = new ArrayList<>();
+ Section currentSection = new Section(NO_SECTION, 0);
+ sections.add(currentSection);
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ line = removeComments(line);
+
+ if (line.isBlank()) {
+ continue;
+ }
+
+ if (isSectionHeader(line)) {
+ currentSection = new Section(line, reader.getLineNumber());
+ sections.add(currentSection);
+ }
+ else {
+ currentSection.add(line, reader.getLineNumber());
+ }
+ }
+
+ return sections;
+ }
+
+ private String removeComments(String line) {
+ // remove any trailing comment on line
+ int commentIndex = line.indexOf("//");
+ if (commentIndex >= 0) {
+ line = line.substring(0, commentIndex);
+ }
+ line = line.trim();
+
+ // clear line if entire line is comment
+ if (line.startsWith("#")) {
+ return "";
+ }
+ return line;
+ }
+
+ private boolean isSectionHeader(String line) {
+ return line.startsWith("[") && line.endsWith("]");
+ }
+
+ protected void error(int lineNumber, String message) {
+ String msg =
+ "Error parsing file \"" + filePath + "\" at line: " + lineNumber + ", " + message;
+ errors.add(msg);
+ Msg.out(msg);
+ }
+
+ protected class Section {
+
+ private String name;
+ Map properties = new HashMap<>();
+ Map lineNumbers = new HashMap<>();
+ private int startLineNumber;
+
+ public Section(String sectionName, int lineNumber) {
+ this.name = sectionName;
+ this.startLineNumber = lineNumber;
+ }
+
+ public void remove(String key) {
+ properties.remove(key);
+ }
+
+ public String getValue(String key) {
+ return properties.get(key);
+ }
+
+ public Set getKeys() {
+ return properties.keySet();
+ }
+
+ public int getLineNumber(String key) {
+ return lineNumbers.get(key);
+ }
+
+ public boolean isEmpty() {
+ return properties.isEmpty();
+ }
+
+ public int getLineNumber() {
+ return startLineNumber;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void add(String line, int lineNumber) {
+ int splitIndex = line.indexOf('=');
+ if (splitIndex < 0) {
+ error(lineNumber, "Missing required \"=\" for propery line: \"" + line + "\"");
+ return;
+ }
+ String key = line.substring(0, splitIndex).trim();
+ String value = line.substring(splitIndex + 1, line.length()).trim();
+ if (key.isBlank()) {
+ error(lineNumber, "Missing key for propery line: \"" + line + "\"");
+ return;
+ }
+ if (key.isBlank()) {
+ error(lineNumber, "Missing value for propery line: \"" + line + "\"");
+ return;
+ }
+ properties.put(key, value);
+ lineNumbers.put(key, lineNumber);
+
+ }
+
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeReader.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeReader.java
new file mode 100644
index 0000000000..4554ea655d
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeReader.java
@@ -0,0 +1,69 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.io.File;
+import java.io.IOException;
+
+public class ThemeReader extends ThemePropertyFileReader {
+
+ private Section themeSection;
+ private String themeName;
+ private String lookAndFeelName;
+ private boolean isDark;
+
+ public ThemeReader(File file) throws IOException {
+ super(file);
+ }
+
+ @Override
+ protected void processNoSection(Section section) throws IOException {
+ themeSection = section;
+ themeName = section.getValue(GTheme.THEME_NAME_KEY);
+ lookAndFeelName = section.getValue(GTheme.THEME_LOOK_AND_FEEL_KEY);
+ if (themeName == null) {
+ throw new IOException("Missing theme name and/or lookAndFeel name!");
+ }
+ if (lookAndFeelName == null) {
+ error(section.getLineNumber(), "Invalid theme - missing theme name!");
+ return;
+ }
+ isDark = Boolean.parseBoolean(section.getValue(GTheme.THEME_IS_DARK_KEY));
+ }
+
+ void loadValues(GTheme theme) {
+
+ // processValues expects only colors, fonts, and icons
+ themeSection.remove(GTheme.THEME_NAME_KEY);
+ themeSection.remove(GTheme.THEME_LOOK_AND_FEEL_KEY);
+ themeSection.remove(GTheme.THEME_IS_DARK_KEY);
+
+ processValues(theme, themeSection);
+ }
+
+ public String getThemeName() {
+ return themeName;
+ }
+
+ public String getLookAndFeelName() {
+ return lookAndFeelName;
+ }
+
+ public boolean isDark() {
+ return isDark;
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeValue.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeValue.java
new file mode 100644
index 0000000000..7e514ae4cd
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeValue.java
@@ -0,0 +1,132 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.util.*;
+
+import ghidra.util.Msg;
+
+// TODO doc why 'cachedValue' is lazy loaded
+public abstract class ThemeValue implements Comparable> {
+ private final String id;
+ private final T value;
+ private final String refId;
+ private T cachedValue;
+
+ protected ThemeValue(String id, String refId, T value) {
+ this.id = fromExternalId(id);
+ this.refId = (refId == null) ? null : fromExternalId(refId);
+ this.value = value;
+ }
+
+ protected abstract String getIdPrefix();
+
+ public String getId() {
+ return id;
+ }
+
+ public String getReferenceId() {
+ return refId;
+ }
+
+ public T getRawValue() {
+ return value;
+ }
+
+ T get(GThemeValueMap preferredValues) {
+ if (cachedValue == null) {
+ cachedValue = doGetValue(preferredValues);
+ }
+ return cachedValue;
+ }
+
+ private T doGetValue(GThemeValueMap values) {
+ ThemeValue result = this;
+ Set visitedKeys = new HashSet<>();
+ visitedKeys.add(id); // seed with my id, we don't want to see that key again
+
+ // loop resolving indirect references
+ while (result != null) {
+ if (result.value != null) {
+ return result.value;
+ }
+ if (visitedKeys.contains(result.refId)) {
+ Msg.warn(this, "Theme value reference loop detected for key: " + id);
+ return getUnresolvedReferenceValue(id);
+ }
+ result = getReferredValue(values, result.refId);
+ }
+ return getUnresolvedReferenceValue(id);
+ }
+
+ abstract protected T getUnresolvedReferenceValue(String theId);
+
+ abstract public String toExternalId(String internalId);
+
+ abstract public String fromExternalId(String externalId);
+
+ abstract protected ThemeValue getReferredValue(GThemeValueMap preferredValues,
+ String theRefId);
+
+ @Override
+ public int compareTo(ThemeValue o) {
+ return id.compareTo(o.id);
+ }
+
+ public int compareValue(ThemeValue o) {
+ if (refId != null) {
+ return o.refId != null ? refId.compareTo(o.refId) : -1;
+ }
+ if (o.refId != null) {
+ return 1;
+ }
+ return compareValues(value, o.value);
+ }
+
+ protected abstract int compareValues(T v1, T v2);
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, refId, value);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ThemeValue> other = (ThemeValue>) obj;
+
+ return Objects.equals(id, other.id) && Objects.equals(refId, other.refId) &&
+ Objects.equals(value, other.value);
+ }
+
+ @Override
+ public String toString() {
+ String name = getClass().getSimpleName();
+ if (refId == null) {
+ return name + " (" + id + ", " + value + ")";
+ }
+ return name + " (" + id + ", " + refId + ")";
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/CDEMotifTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/CDEMotifTheme.java
new file mode 100644
index 0000000000..725a620f28
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/CDEMotifTheme.java
@@ -0,0 +1,27 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme.builtin;
+
+import docking.theme.DiscoverableGTheme;
+import ghidra.docking.util.LookAndFeelUtils;
+
+public class CDEMotifTheme extends DiscoverableGTheme {
+
+ public CDEMotifTheme() {
+ super("CDE/Motif", LookAndFeelUtils.MOTIF_LOOK_AND_FEEL);
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatDarkTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatDarkTheme.java
new file mode 100644
index 0000000000..dc1e7fe522
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatDarkTheme.java
@@ -0,0 +1,25 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme.builtin;
+
+import docking.theme.DiscoverableGTheme;
+import ghidra.docking.util.LookAndFeelUtils;
+
+public class FlatDarkTheme extends DiscoverableGTheme {
+ public FlatDarkTheme() {
+ super("Dark Flat Theme", LookAndFeelUtils.FLAT_DARK_LOOK_AND_FEEL, true);
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatLightTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatLightTheme.java
new file mode 100644
index 0000000000..935233cfec
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatLightTheme.java
@@ -0,0 +1,27 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme.builtin;
+
+import docking.theme.DiscoverableGTheme;
+import ghidra.docking.util.LookAndFeelUtils;
+
+public class FlatLightTheme extends DiscoverableGTheme {
+
+ public FlatLightTheme() {
+ super("Flat", LookAndFeelUtils.FLAT_LIGHT_LOOK_AND_FEEL);
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/GdkTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/GdkTheme.java
new file mode 100644
index 0000000000..30b122c7c1
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/GdkTheme.java
@@ -0,0 +1,27 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme.builtin;
+
+import docking.theme.DiscoverableGTheme;
+import ghidra.docking.util.LookAndFeelUtils;
+
+public class GdkTheme extends DiscoverableGTheme {
+
+ public GdkTheme() {
+ super("GDK+", LookAndFeelUtils.GDK_LOOK_AND_FEEL);
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/MetalTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/MetalTheme.java
new file mode 100644
index 0000000000..dc6f7938f6
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/MetalTheme.java
@@ -0,0 +1,27 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme.builtin;
+
+import docking.theme.DiscoverableGTheme;
+import ghidra.docking.util.LookAndFeelUtils;
+
+public class MetalTheme extends DiscoverableGTheme {
+
+ public MetalTheme() {
+ super("Metal", LookAndFeelUtils.METAL_LOOK_AND_FEEL);
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/NimbusTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/NimbusTheme.java
new file mode 100644
index 0000000000..d64daf4fff
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/NimbusTheme.java
@@ -0,0 +1,26 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme.builtin;
+
+import docking.theme.DiscoverableGTheme;
+
+public class NimbusTheme extends DiscoverableGTheme {
+
+ public NimbusTheme() {
+ super("Nimbus", "Nimbus");
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/WindowsClassicTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/WindowsClassicTheme.java
new file mode 100644
index 0000000000..eee25d50f9
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/WindowsClassicTheme.java
@@ -0,0 +1,26 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme.builtin;
+
+import docking.theme.DiscoverableGTheme;
+import ghidra.docking.util.LookAndFeelUtils;
+
+public class WindowsClassicTheme extends DiscoverableGTheme {
+
+ public WindowsClassicTheme() {
+ super("Windows Classic", LookAndFeelUtils.WINDOWS_CLASSIC);
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/WindowsTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/WindowsTheme.java
new file mode 100644
index 0000000000..4b1b8e2a23
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/WindowsTheme.java
@@ -0,0 +1,26 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme.builtin;
+
+import docking.theme.DiscoverableGTheme;
+import ghidra.docking.util.LookAndFeelUtils;
+
+public class WindowsTheme extends DiscoverableGTheme {
+
+ public WindowsTheme() {
+ super("Windows", LookAndFeelUtils.WINDOWS);
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/AbstractGCellRenderer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/AbstractGCellRenderer.java
index 48c057437f..a1d6288c97 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/AbstractGCellRenderer.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/AbstractGCellRenderer.java
@@ -21,6 +21,7 @@ import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.border.Border;
+import docking.theme.GColor;
import docking.widgets.label.GDHtmlLabel;
/**
@@ -31,8 +32,8 @@ import docking.widgets.label.GDHtmlLabel;
*
*/
public abstract class AbstractGCellRenderer extends GDHtmlLabel {
-
- private static final Color ALTERNATE_BACKGROUND_COLOR = new Color(237, 243, 254);
+ private static final Color BACKGROUND_COLOR = new GColor("color.bg.table.row");
+ private static final Color ALT_BACKGROUND_COLOR = new GColor("color.bg.table.row.alt");
/** Allows the user to disable alternating row colors on JLists and JTables */
private static final String DISABLE_ALTERNATING_ROW_COLORS_PROPERTY =
@@ -156,7 +157,7 @@ public abstract class AbstractGCellRenderer extends GDHtmlLabel {
}
protected Color getDefaultBackgroundColor() {
- return Color.WHITE;
+ return BACKGROUND_COLOR;
}
protected Color getBackgroundColorForRow(int row) {
@@ -164,7 +165,7 @@ public abstract class AbstractGCellRenderer extends GDHtmlLabel {
if ((row & 1) == 1) {
return getDefaultBackgroundColor();
}
- return ALTERNATE_BACKGROUND_COLOR;
+ return ALT_BACKGROUND_COLOR;
}
// ==================================================================================================
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/EmptyBorderButton.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/EmptyBorderButton.java
index 8eb0260bba..cb26445e1e 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/EmptyBorderButton.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/EmptyBorderButton.java
@@ -24,7 +24,7 @@ import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
-import ghidra.docking.util.DockingWindowsLookAndFeelUtils;
+import ghidra.docking.util.LookAndFeelUtils;
import resources.ResourceManager;
/**
@@ -123,7 +123,7 @@ public class EmptyBorderButton extends JButton {
// Mac OSX LNF doesn't give us rollover callbacks, so we have to add a mouse listener to
// do the work
- if (DockingWindowsLookAndFeelUtils.isUsingAquaUI(getUI())) {
+ if (LookAndFeelUtils.isUsingAquaUI(getUI())) {
addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java
index 4d17046b8e..13ebbb8ce2 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/FieldPanel.java
@@ -15,7 +15,7 @@
*/
package docking.widgets.fieldpanel;
-import static docking.widgets.EventTrigger.INTERNAL_ONLY;
+import static docking.widgets.EventTrigger.*;
import java.awt.*;
import java.awt.event.*;
@@ -29,6 +29,7 @@ import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import docking.DockingUtils;
+import docking.theme.GColor;
import docking.util.GraphicsUtils;
import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.field.Field;
@@ -51,7 +52,7 @@ public class FieldPanel extends JPanel
private boolean inFocus;
protected BackgroundColorModel backgroundColorModel =
- new DefaultBackgroundColorModel(Color.WHITE);
+ new DefaultBackgroundColorModel(new GColor("color.bg.fieldpanel"));
protected PaintContext paintContext = new PaintContext();
private AnchoredLayoutHandler layoutHandler;
@@ -415,7 +416,6 @@ public class FieldPanel extends JPanel
*/
public void setBackgroundColor(Color c) {
backgroundColorModel.setDefaultBackgroundColor(c);
- paintContext.setDefaultBackgroundColor(c);
}
public Color getBackgroundColor(BigInteger index) {
@@ -1238,8 +1238,7 @@ public class FieldPanel extends JPanel
if (layout == null) {
return null;
}
- Rectangle r =
- layout.getCursorRect(location.fieldNum, location.row, location.col);
+ Rectangle r = layout.getCursorRect(location.fieldNum, location.row, location.col);
return r.getLocation();
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/PaintContext.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/PaintContext.java
index 04dd4c04b0..68ffce4b18 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/PaintContext.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/PaintContext.java
@@ -1,6 +1,5 @@
/* ###
* 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.
@@ -18,12 +17,13 @@ package docking.widgets.fieldpanel.internal;
import java.awt.Color;
+import docking.theme.GColor;
+
/**
* Miscellaneous information needed by fields to paint.
*/
public class PaintContext {
- private Color defaultBackground;
private Color background;
private Color foreground;
private Color selectionColor;
@@ -43,20 +43,18 @@ public class PaintContext {
* Create a new PaintContext with default color values.
*/
public PaintContext() {
- defaultBackground = Color.white;
- background = Color.white;
- foreground = Color.black;
- selectionColor = new Color(180, 255, 180);
- highlightColor = new Color(255, 255, 150);
- selectedHighlightColor = Color.green;
- focusedCursorColor = Color.RED;
+ background = new GColor("color.bg.fieldpanel");
+ foreground = new GColor("color.fg.fieldpanel");
+ selectionColor = new GColor("color.bg.fieldpanel.selection");
+ highlightColor = new GColor("color.bg.fieldpanel.highlight");
+ selectedHighlightColor = new GColor("color.bg.fieldpanel.selection-highlight");
+ focusedCursorColor = new GColor("color.cursor.focused");
+ notFocusedCursorColor = new GColor("color.cursor.unfocused");
cursorColor = focusedCursorColor;
invisibleCursorColor = new Color(255, 0, 0, 1);
- notFocusedCursorColor = Color.PINK;
}
public PaintContext(PaintContext other) {
- defaultBackground = other.defaultBackground;
background = other.background;
foreground = other.foreground;
selectionColor = other.selectionColor;
@@ -69,16 +67,9 @@ public class PaintContext {
printColor = other.printColor;
}
- /**
- * Returns the current default background color setting that is used when
- * there is no special background color or highlight or selection.
- */
- public final Color getDefaultBackground() {
- return defaultBackground;
- }
-
/**
* Returns the current background color setting.
+ * @return the current background color setting.
*/
public final Color getBackground() {
return background;
@@ -86,6 +77,7 @@ public class PaintContext {
/**
* Returns the current foreground color setting.
+ * @return the current foreground color setting.
*/
public final Color getForeground() {
return foreground;
@@ -93,27 +85,31 @@ public class PaintContext {
/**
* Returns the current selection color setting.
+ * @return the current selection color setting.
*/
public final Color getSelectionColor() {
return selectionColor;
}
/**
- * Returns the current selection color setting.
+ * Returns the current highlight color setting.
+ * @return the current highlight color setting.
*/
public final Color getHighlightColor() {
return highlightColor;
}
/**
- * Returns the current selection color setting.
+ * Returns the current selected highlight color setting.
+ * @return the current selected highlight color setting.
*/
public final Color getSelectedHighlightColor() {
return selectedHighlightColor;
}
/**
- * Returns the current cursor color setting.
+ * Returns the current cursor color.
+ * @return the current cursor color.
*/
public final Color getCursorColor() {
return cursorColor;
@@ -133,17 +129,6 @@ public class PaintContext {
adjustSelectedHighlightColor();
}
- public void setDefaultBackgroundColor(Color c) {
- defaultBackground = c;
- }
-
- /**
- * Returns true if the current background color matches the default background color.
- */
- public final boolean isDefaultBackground() {
- return defaultBackground.equals(background);
- }
-
private void adjustSelectedHighlightColor() {
int red = (selectionColor.getRed() + highlightColor.getRed()) / 2;
int green = (selectionColor.getGreen() + highlightColor.getGreen()) / 2;
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java
index f38f9d0c3c..b0dffb2e37 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/filechooser/GhidraFileChooser.java
@@ -15,23 +15,23 @@
*/
package docking.widgets.filechooser;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.File;
+import java.io.FileFilter;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-import java.awt.*;
-import java.awt.event.*;
-import java.io.File;
-import java.io.FileFilter;
-
import javax.swing.*;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.filechooser.FileSystemView;
import docking.*;
+import docking.theme.GColor;
import docking.widgets.*;
import docking.widgets.combobox.GComboBox;
import docking.widgets.label.GDLabel;
@@ -73,8 +73,8 @@ import util.HistoryList;
public class GhidraFileChooser extends DialogComponentProvider implements FileFilter {
static final String UP_BUTTON_NAME = "UP_BUTTON";
- private static final Color FOREROUND_COLOR = Color.BLACK;
- private static final Color BACKGROUND_COLOR = Color.WHITE;
+ private static final Color FOREROUND_COLOR = new GColor("color.fg.filechooser");
+ private static final Color BACKGROUND_COLOR = new GColor("color.bg.filechooser");
static final String PREFERENCES_PREFIX = "G_FILE_CHOOSER";
private static final String WIDTH_PREFERENCE_PREFIX = PREFERENCES_PREFIX + ".WIDTH.";
private static final String HEIGHT_PREFERENCE_PREFIX = PREFERENCES_PREFIX + ".HEIGHT.";
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableHeaderRenderer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableHeaderRenderer.java
index 778efa2426..be1ffca267 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableHeaderRenderer.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableHeaderRenderer.java
@@ -24,6 +24,7 @@ import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
+import docking.theme.GColor;
import docking.widgets.label.GDLabel;
import resources.*;
import resources.icons.EmptyIcon;
@@ -37,11 +38,17 @@ import resources.icons.TranslateIcon;
*/
public class GTableHeaderRenderer extends JPanel implements TableCellRenderer {
private static final int PADDING_FOR_COLUMN_NUMBER = 10;
+ //@formatter:off
+ public static final Color GRADIENT_START_COLOR = new GColor("color.bg.tableheader.gradient.start");
+ public static final Color GRADIENT_END_COLOR = new GColor("color.bg.tableheader.gradient.end");
+ public static final Color GRADIENT_START_PRIMARY_COLOR = new GColor("color.bg.tableheader.gradient.start.primary");
+ public static final Color GRADIENT_END_PRIMARY_COLOR= new GColor("color.bg.tableheader.gradient.end.primary");
+ //@formatter:on
- private static final Color PRIMARY_SORT_GRADIENT_START = new Color(205, 227, 244);
- private static final Color PRIMARY_SORT_GRADIENT_END = new Color(126, 186, 233);
- private static final Color DEFAULT_GRADIENT_START = Color.WHITE;
- private static final Color DEFAULT_GRADIENT_END = new Color(215, 215, 215);
+// static {
+// Gui.registerAltColor(GThemeDefaults.DARK, GRADIENT_START_ID, new Color(20, 20, 20));
+// Gui.registerAltColor(GThemeDefaults.DARK, GRADIENT_END_ID, new Color(40, 40, 40));
+// }
private static final Icon UP_ICON =
ResourceManager.getScaledIcon(Icons.SORT_ASCENDING_ICON, 14, 14);
@@ -219,12 +226,12 @@ public class GTableHeaderRenderer extends JPanel implements TableCellRenderer {
}
protected Paint getBackgroundPaint() {
- if (isPaintingPrimarySortColumn) {
- return new GradientPaint(0, 0, PRIMARY_SORT_GRADIENT_START, 0, getHeight() - 11,
- PRIMARY_SORT_GRADIENT_END, true);
- }
- return new GradientPaint(0, 0, DEFAULT_GRADIENT_START, 0, getHeight() - 11,
- DEFAULT_GRADIENT_END, true);
+ Color startColor =
+ isPaintingPrimarySortColumn ? GRADIENT_START_PRIMARY_COLOR : GRADIENT_START_COLOR;
+ Color endColor =
+ isPaintingPrimarySortColumn ? GRADIENT_END_PRIMARY_COLOR : GRADIENT_END_COLOR;
+
+ return new GradientPaint(0, 0, startColor, 0, getHeight() - 11, endColor, true);
}
private void updateHelpIcon(JTable table, int currentColumnIndex, Icon icon) {
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HintTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HintTextField.java
index 122abd986c..15a618eb23 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HintTextField.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HintTextField.java
@@ -22,6 +22,8 @@ import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
+import docking.theme.GColor;
+
/**
* Simple text field that shows a text hint when the field is empty.
*
@@ -39,8 +41,8 @@ public class HintTextField extends JTextField {
// some indication of what the field should contain.
private String hint;
- private Color INVALID_COLOR = new Color(255, 225, 225);
- private Color VALID_COLOR = Color.WHITE;
+ private Color VALID_COLOR = new GColor("color.bg.textfield.hint.valid");
+ private Color INVALID_COLOR = new GColor("color.bg.textfield.hint.invalid");
private Color defaultBackgroundColor;
/**
@@ -126,8 +128,7 @@ public class HintTextField extends JTextField {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.LIGHT_GRAY);
- g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
- RenderingHints.VALUE_ANTIALIAS_ON);
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Dimension size = getSize();
Insets insets = getInsets();
@@ -179,7 +180,7 @@ public class HintTextField extends JTextField {
*/
private void setAttributes() {
setFont(getFont().deriveFont(Font.PLAIN));
- setForeground(Color.BLACK);
+ setForeground(new GColor("color.fg.textfield.hint"));
}
/**
diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/util/DockingWindowsLookAndFeelUtils.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/util/LookAndFeelUtils.java
similarity index 81%
rename from Ghidra/Framework/Docking/src/main/java/ghidra/docking/util/DockingWindowsLookAndFeelUtils.java
rename to Ghidra/Framework/Docking/src/main/java/ghidra/docking/util/LookAndFeelUtils.java
index 9b1e6199e5..140c7babd9 100644
--- a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/util/DockingWindowsLookAndFeelUtils.java
+++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/util/LookAndFeelUtils.java
@@ -15,16 +15,15 @@
*/
package ghidra.docking.util;
-import java.awt.Dimension;
-import java.awt.Font;
+import java.awt.*;
import java.util.*;
+import java.util.List;
import java.util.Map.Entry;
import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.plaf.ComponentUI;
-import ghidra.docking.util.painting.GRepaintManager;
import ghidra.framework.OperatingSystem;
import ghidra.framework.Platform;
import ghidra.framework.preferences.Preferences;
@@ -33,56 +32,50 @@ import ghidra.util.*;
/**
* A utility class to manage LookAndFeel (LaF) settings.
*/
-public class DockingWindowsLookAndFeelUtils {
+public class LookAndFeelUtils {
/**
- * Preference name for look and feel for the application.
+ * Default Look and feel for the current platform.
*/
- public final static String LAST_LOOK_AND_FEEL_KEY = "LastLookAndFeel";
-
- /**
- * Preference name for whether to use inverted colors.
- */
- public final static String USE_INVERTED_COLORS_KEY = "LookAndFeel.UseInvertedColors";
+ public final static String SYSTEM_LOOK_AND_FEEL = "System";
/**
* Metal is the non-system, generic Java Look and Feel.
*/
public final static String METAL_LOOK_AND_FEEL = "Metal";
- /**
- * Default Look and feel for the current platform.
- */
- private final static String SYSTEM_LOOK_AND_FEEL = "System";
-
/**
* The most stable Linux LaF, based on anecdotal observation.
*/
- private static final String NIMBUS_LOOK_AND_FEEL = "Nimbus";
+ public static final String NIMBUS_LOOK_AND_FEEL = "Nimbus";
+
+ public static final String GDK_LOOK_AND_FEEL = "GTK+";
/**
* The Motif LaF name.
*/
- private static final String MOTIF_LOOK_AND_FEEL = "CDE/Motif";
+ public static final String MOTIF_LOOK_AND_FEEL = "CDE/Motif";
+ /**
+ * The flatlaf implementation of light mode.
+ */
+ public static final String FLAT_LIGHT_LOOK_AND_FEEL = "Flat Light";
+ /**
+ * The Flatlaf implementation of dark mode.
+ */
+ public static final String FLAT_DARK_LOOK_AND_FEEL = "Flat Dark";
+ public static final String WINDOWS = "Windows";
+ public static final String WINDOWS_CLASSIC = "Windows Classic";
- private static RepaintManager defaultSwingRepaintManager = null;
+ public static final String SYSTEM = "System";
- private DockingWindowsLookAndFeelUtils() {
+ private LookAndFeelUtils() {
// utils class, cannot create
}
/**
* Loads settings from {@link Preferences}.
*/
- public static void loadFromPreferences() {
-
- boolean useHistoricalValue = true;
- String laf = Preferences.getProperty(LAST_LOOK_AND_FEEL_KEY, getDefaultLookAndFeelName(),
- useHistoricalValue);
- setLookAndFeel(laf);
-
- boolean useInvertedColors = getUseInvertedColorsPreference();
- setUseInvertedColors(useInvertedColors);
+ public static void installGlobalOverrides() {
//
// Users can change this via the SystemUtilities.FONT_SIZE_OVERRIDE_PROPERTY_NAME
@@ -94,18 +87,6 @@ public class DockingWindowsLookAndFeelUtils {
}
}
- /**
- * Returns the {@link Preferences} value for whether to use inverted colors when painting.
- * @return the {@link Preferences} value for whether to use inverted colors when painting.
- */
- public static boolean getUseInvertedColorsPreference() {
- boolean useHistoricalValue = true;
- String useInvertedColorsString = Preferences.getProperty(USE_INVERTED_COLORS_KEY,
- Boolean.FALSE.toString(), useHistoricalValue);
- boolean useInvertedColors = Boolean.parseBoolean(useInvertedColorsString);
- return useInvertedColors;
- }
-
/**
* Returns the currently installed LaF.
* @return the currently installed LaF.
@@ -137,19 +118,45 @@ public class DockingWindowsLookAndFeelUtils {
installPopupMenuSettingsOverride();
}
catch (Exception exc) {
- Msg.error(DockingWindowsLookAndFeelUtils.class,
+ Msg.error(LookAndFeelUtils.class,
"Error loading Look and Feel: " + exc, exc);
}
});
}
+ public static void dumpUIProperties() {
+ List colorKeys = getLookAndFeelIdsForType(Color.class);
+ Collections.sort(colorKeys);
+ for (String string : colorKeys) {
+ Msg.debug(LookAndFeelUtils.class, string + "\t\t" + UIManager.get(string));
+ }
+
+ }
+
+ public static List getLookAndFeelIdsForType(Class> clazz) {
+ UIDefaults defaults = UIManager.getDefaults();
+ List colorKeys = new ArrayList<>();
+ for (Entry, ?> entry : defaults.entrySet()) {
+ Object key = entry.getKey();
+ if (key instanceof String) {
+ Object value = entry.getValue();
+ if (clazz.isInstance(value)) {
+ colorKeys.add((String) key);
+ }
+ }
+ }
+ return colorKeys;
+ }
+
/**
* Returns all installed LaFs. This will vary by OS.
* @return all installed LaFs.
*/
public static List getLookAndFeelNames() {
List list = new ArrayList<>();
- list.add(DockingWindowsLookAndFeelUtils.SYSTEM_LOOK_AND_FEEL);
+ list.add(LookAndFeelUtils.SYSTEM_LOOK_AND_FEEL);
+ list.add(LookAndFeelUtils.FLAT_LIGHT_LOOK_AND_FEEL);
+ list.add(LookAndFeelUtils.FLAT_DARK_LOOK_AND_FEEL);
LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels();
for (LookAndFeelInfo info : installedLookAndFeels) {
@@ -171,6 +178,12 @@ public class DockingWindowsLookAndFeelUtils {
if (lookAndFeelName.equalsIgnoreCase(SYSTEM_LOOK_AND_FEEL)) {
return UIManager.getSystemLookAndFeelClassName();
}
+ else if (lookAndFeelName.equalsIgnoreCase(FLAT_LIGHT_LOOK_AND_FEEL)) {
+ return "com.formdev.flatlaf.FlatLightLaf";
+ }
+ else if (lookAndFeelName.equalsIgnoreCase(FLAT_DARK_LOOK_AND_FEEL)) {
+ return "com.formdev.flatlaf.FlatDarkLaf";
+ }
LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels();
for (LookAndFeelInfo info : installedLookAndFeels) {
@@ -180,26 +193,11 @@ public class DockingWindowsLookAndFeelUtils {
}
}
- Msg.debug(DockingWindowsLookAndFeelUtils.class,
+ Msg.debug(LookAndFeelUtils.class,
"Unable to find requested Look and Feel: " + lookAndFeelName);
return UIManager.getSystemLookAndFeelClassName();
}
- public static void setUseInvertedColors(boolean useInvertedColors) {
- SystemUtilities.runIfSwingOrPostSwingLater(() -> {
-
- if (defaultSwingRepaintManager == null) {
- defaultSwingRepaintManager = RepaintManager.currentManager(null /*unused*/);
- }
-
- RepaintManager rm = defaultSwingRepaintManager;
- if (useInvertedColors) {
- rm = new GRepaintManager();
- }
- RepaintManager.setCurrentManager(rm);
- });
- }
-
/**
* Fixes issues in the currently running look and feel.
*/
@@ -340,8 +338,9 @@ public class DockingWindowsLookAndFeelUtils {
/**
* Returns the name of the default LookAndFeel for the current OS.
+ * @return the name
*/
- private static String getDefaultLookAndFeelName() {
+ public static String getDefaultLookAndFeelName() {
OperatingSystem OS = Platform.CURRENT_PLATFORM.getOperatingSystem();
if (OS == OperatingSystem.LINUX) {
return NIMBUS_LOOK_AND_FEEL;
@@ -369,8 +368,4 @@ public class DockingWindowsLookAndFeelUtils {
return NIMBUS_LOOK_AND_FEEL.equals(lookAndFeel.getName());
}
- public static boolean isUsingMotifUI() {
- LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
- return MOTIF_LOOK_AND_FEEL.equals(lookAndFeel.getName());
- }
}
diff --git a/Ghidra/Framework/Docking/src/test/java/docking/theme/GThemeTest.java b/Ghidra/Framework/Docking/src/test/java/docking/theme/GThemeTest.java
new file mode 100644
index 0000000000..1bb02e2dc1
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/test/java/docking/theme/GThemeTest.java
@@ -0,0 +1,119 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import static org.junit.Assert.*;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.io.File;
+import java.io.IOException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import generic.test.AbstractGenericTest;
+
+public class GThemeTest extends AbstractGenericTest {
+
+ private static final Font COURIER = new Font("Courier", Font.BOLD, 14);
+ private static final Font DIALOG = new Font("Dialog", Font.PLAIN, 16);
+ private static final Color COLOR_WITH_ALPHA = new Color(10, 20, 30, 40);
+ private static final String ICON_PATH_1 = "images/arrow.png";
+ private static final String ICON_PATH_2 = "images/disk.png";
+
+ private GTheme theme;
+
+ @Before
+ public void setUp() {
+ theme = new DefaultTheme();
+ new Font("Courier", Font.BOLD, 12);
+ }
+
+ @Test
+ public void testGetName() {
+ assertEquals("Default", theme.getName());
+ }
+
+ @Test
+ public void testSetColor() {
+ theme.setColor("color.a.1", Color.BLUE);
+ assertEquals(Color.BLUE, theme.getColor("color.a.1").get(null));
+ theme.setColor("color.a.1", Color.RED);
+ assertEquals(Color.RED, theme.getColor("color.a.1").get(null));
+ }
+
+ @Test
+ public void testSetFont() {
+ theme.setFont("font.a.1", DIALOG);
+ assertEquals(DIALOG, theme.getFont("font.a.1").get(null));
+ }
+
+ @Test
+ public void testSetIconPath() {
+ theme.setIcon("icon.a.1", ICON_PATH_1);
+ assertEquals(ICON_PATH_1, theme.getIcon("icon.a.1").get(null));
+ }
+
+ @Test
+ public void testSavingLoadingTheme() throws IOException {
+ theme = new GTheme("abc");
+ theme.setColor("color.a.1", Color.RED);
+ theme.setColor("color.a.2", Color.BLUE);
+ theme.setColor("color.a.3", COLOR_WITH_ALPHA);
+ theme.setColorRef("color.a.4", "color.a.1");
+ theme.setColor("foo.bar", Color.GREEN);
+ theme.setColorRef("foo.bar.xyz", "foo.bar");
+
+ theme.setFont("font.a.1", COURIER);
+ theme.setFont("font.a.2", DIALOG);
+ theme.setFontRef("font.a.3", "font.a.1");
+ theme.setFont("x.y.z", COURIER);
+ theme.setFontRef("x.y.z.1", "x.y.z");
+
+ theme.setIcon("icon.a.1", ICON_PATH_1);
+ theme.setIcon("icon.a.2", ICON_PATH_2);
+ theme.setIconRef("icon.a.3", "icon.a.1");
+ theme.setIcon("t.u.v", ICON_PATH_1);
+ theme.setIconRef("t.u.v.1", "t.u.v");
+
+ File file = createTempFile("themeTest.theme");
+
+ theme = theme.saveToFile(file); // saveToFile returns new theme instance
+
+ assertEquals("abc", theme.getName());
+ assertEquals("System", theme.getLookAndFeelName());
+
+ assertEquals(Color.RED, theme.getColor("color.a.1").get(theme));
+ assertEquals(Color.BLUE, theme.getColor("color.a.2").get(theme));
+ assertEquals(COLOR_WITH_ALPHA, theme.getColor("color.a.3").get(theme));
+ assertEquals("color.a.1", theme.getColor("color.a.4").getReferenceId());
+ assertEquals(Color.RED, theme.getColor("color.a.4").get(theme));
+ assertEquals(Color.GREEN, theme.getColor("foo.bar").get(theme));
+ assertEquals(Color.GREEN, theme.getColor("foo.bar.xyz").get(theme));
+
+ assertEquals(COURIER, theme.getFont("font.a.1").get(theme));
+ assertEquals(DIALOG, theme.getFont("font.a.2").get(theme));
+ assertEquals(COURIER, theme.getFont("x.y.z").get(theme));
+ assertEquals(COURIER, theme.getFont("x.y.z.1").get(theme));
+
+ assertEquals(ICON_PATH_1, theme.getIcon("icon.a.1").get(theme));
+ assertEquals(ICON_PATH_2, theme.getIcon("icon.a.2").get(theme));
+ assertEquals(ICON_PATH_1, theme.getIcon("t.u.v").get(theme));
+ assertEquals(ICON_PATH_1, theme.getIcon("t.u.v.1").get(theme));
+ }
+
+}
diff --git a/Ghidra/Framework/Docking/src/test/java/docking/theme/GuiTest.java b/Ghidra/Framework/Docking/src/test/java/docking/theme/GuiTest.java
new file mode 100644
index 0000000000..d46dd4da13
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/test/java/docking/theme/GuiTest.java
@@ -0,0 +1,111 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.collections4.map.HashedMap;
+import org.junit.Before;
+
+import docking.test.AbstractDockingTest;
+
+public class GuiTest extends AbstractDockingTest {
+
+ private Map> aliasMap = new HashedMap<>();
+ private GThemeValueMap darkValues = new GThemeValueMap();
+
+ @Before
+ public void setUp() {
+ Gui.setPropertiesLoader(new ThemePropertiesLoader() {
+ @Override
+ public void initialize() {
+ // do nothing
+ }
+
+ @Override
+ public GThemeValueMap getDarkDefaults() {
+ return darkValues;
+ }
+ });
+ }
+
+// @Test
+// public void testRegisteredColorBeforeAndAfterGuiInit() {
+// Gui.registerColor("test.before", Color.RED);
+// Gui.initialize();
+// Gui.registerColor("test.after", Color.BLUE);
+//
+// assertEquals(Color.RED, Gui.getColor("test.before"));
+// assertEquals(Color.BLUE, Gui.getColor("test.after"));
+// }
+
+// @Test
+// public void testThemeColorOverride() {
+// Gui.initialize();
+// String id = "color.test.bg";
+// Gui.registerColor(id, Color.RED);
+// assertEquals(Color.RED, Gui.getColor(id));
+//
+// GTheme theme = new GTheme("Test");
+// theme.setColor(id, Color.BLUE);
+// Gui.setTheme(theme);
+//
+// assertEquals(Color.BLUE, Gui.getColor(id));
+//
+// Gui.setTheme(new GTheme("Test2"));
+// assertEquals(Color.RED, Gui.getColor(id));
+//
+// }
+
+// @Test
+// public void testDarkOverride() {
+// String id = "color.test.bg";
+// // simulate registered dark color from theme property file
+// darkValues.addColor(new ColorValue(id, Color.BLACK));
+//
+// Gui.registerColor(id, Color.RED);
+// Gui.initialize();
+//
+// assertEquals(Color.RED, Gui.getColor(id));
+//
+// GTheme theme = new GTheme("Dark Test", "System", true);
+// Gui.setTheme(theme);
+//
+// assertEquals(Color.BLACK, Gui.getColor(id));
+// }
+
+// @Test
+// public void testAliasOverride() {
+// String id = "color.test.bg";
+// //simulate alias defined
+// List aliases = Arrays.asList("Menu.background");
+// aliasMap.put(id, aliases);
+//
+// Gui.registerColor(id, Color.RED);
+// Gui.initialize();
+// Color menuColor = UIManager.getColor("Menu.background");
+// assertNotEquals(menuColor, Color.RED);
+// assertEquals(menuColor, Gui.getColor(id));
+// }
+
+// private void assertEqual(Color a, GColor b) {
+// if (a.getRGB() != b.getRGB()) {
+// fail("Expected: " + a.toString() + " but was: " + b.toString());
+// }
+// }
+
+}
diff --git a/Ghidra/Framework/Docking/src/test/java/docking/theme/ThemePropertyFileReaderTest.java b/Ghidra/Framework/Docking/src/test/java/docking/theme/ThemePropertyFileReaderTest.java
new file mode 100644
index 0000000000..e35bc8b7a2
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/test/java/docking/theme/ThemePropertyFileReaderTest.java
@@ -0,0 +1,167 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package docking.theme;
+
+import static ghidra.util.WebColors.*;
+import static org.junit.Assert.*;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class ThemePropertyFileReaderTest {
+
+ @Before
+ public void setUp() {
+ }
+
+ @Test
+ public void testDefaults() throws IOException {
+ //@formatter:off
+ ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
+ "[Defaults]",
+ " color.b.1 = white", // WHITE
+ " color.b.2 = #ff0000", // RED
+ " color.b.3 = 0x008000", // GREEN
+ " color.b.4 = 0xff000080", // half alpha red
+ " color.b.5 = rgb(0,0,255)", // BLUE
+ " color.b.6 = rgba(255,0,0,0.5)", // half alpha red
+ " color.b.7 = color.b.1", // ref
+ " font.a.8 = dialog-PLAIN-14",
+ " font.a.9 = font.a.8",
+ " icon.a.10 = foo.png",
+ " icon.a.11 = icon.a.10",
+ "")));
+ //@formatter:on
+
+ Color halfAlphaRed = new Color(0x80ff0000, true);
+ GThemeValueMap values = reader.getDefaultValues();
+ assertEquals(11, values.size());
+
+ assertEquals(WHITE, getColorOrRef(values, "color.b.1"));
+ assertEquals(RED, getColorOrRef(values, "color.b.2"));
+ assertEquals(GREEN, getColorOrRef(values, "color.b.3"));
+ assertEquals(halfAlphaRed, getColorOrRef(values, "color.b.4"));
+ assertEquals(BLUE, getColorOrRef(values, "color.b.5"));
+ assertEquals(halfAlphaRed, getColorOrRef(values, "color.b.6"));
+ assertEquals("color.b.1", getColorOrRef(values, "color.b.7"));
+
+ assertEquals(new Font("dialog", Font.PLAIN, 14), getFontOrRef(values, "font.a.8"));
+ assertEquals("font.a.8", getFontOrRef(values, "font.a.9"));
+
+ assertEquals("foo.png", getIconOrRef(values, "icon.a.10"));
+ assertEquals("icon.a.10", getIconOrRef(values, "icon.a.11"));
+
+ }
+
+ @Test
+ public void testDarkDefaults() throws IOException {
+ //@formatter:off
+ ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
+ "[Dark Defaults]",
+ " color.b.1 = white", // WHITE
+ " color.b.2 = #ff0000", // RED
+ " color.b.3 = 0x008000", // GREEN
+ " color.b.4 = 0xff000080", // half alpha red
+ " color.b.5 = rgb(0,0,255)", // BLUE
+ " color.b.6 = rgba(255,0,0,0.5)", // half alpha red
+ " color.b.7 = color.b.1", // ref
+ "")));
+ //@formatter:on
+
+ Color halfAlphaRed = new Color(0x80ff0000, true);
+ GThemeValueMap values = reader.getDarkDefaultValues();
+ assertEquals(7, values.size());
+
+ assertEquals(WHITE, getColorOrRef(values, "color.b.1"));
+ assertEquals(RED, getColorOrRef(values, "color.b.2"));
+ assertEquals(GREEN, getColorOrRef(values, "color.b.3"));
+ assertEquals(halfAlphaRed, getColorOrRef(values, "color.b.4"));
+ assertEquals(BLUE, getColorOrRef(values, "color.b.5"));
+ assertEquals(halfAlphaRed, getColorOrRef(values, "color.b.6"));
+ assertEquals("color.b.1", getColorOrRef(values, "color.b.7"));
+ }
+
+ @Test
+ public void testBothDefaultsAndDarkDefaultsInSameFile() throws IOException {
+ //@formatter:off
+ ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
+ "[Defaults]",
+ " color.b.1 = white", // WHITE
+ " color.b.2 = #ff0000", // RED
+ "[Dark Defaults]",
+ " color.b.1 = black", // BLACK
+ " color.b.2 = #0000ff", // BLUE
+ "")));
+ //@formatter:on
+
+ GThemeValueMap values = reader.getDefaultValues();
+ assertEquals(2, values.size());
+
+ GThemeValueMap darkValues = reader.getDarkDefaultValues();
+ assertEquals(2, values.size());
+
+ assertEquals(WHITE, getColorOrRef(values, "color.b.1"));
+ assertEquals(RED, getColorOrRef(values, "color.b.2"));
+ assertEquals(BLACK, getColorOrRef(darkValues, "color.b.1"));
+ assertEquals(BLUE, getColorOrRef(darkValues, "color.b.2"));
+ }
+
+ @Test
+ public void testParseColorError() throws IOException {
+ //@formatter:off
+ ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
+ "[Defaults]",
+ " color.b.1 = white", // WHITE
+ " color.b.2 = sdfsdf", // RED
+ "")));
+ //@formatter:on
+ List errors = reader.getErrors();
+ assertEquals(1, errors.size());
+ assertEquals("Error parsing file \"test\" at line: 3, Could not parse Color: sdfsdf",
+ errors.get(0));
+
+ }
+
+ private Object getColorOrRef(GThemeValueMap values, String id) {
+ ColorValue color = values.getColor(id);
+ if (color.getReferenceId() != null) {
+ return color.getReferenceId();
+ }
+ return color.getRawValue();
+ }
+
+ private Object getFontOrRef(GThemeValueMap values, String id) {
+ FontValue font = values.getFont(id);
+ if (font.getReferenceId() != null) {
+ return font.getReferenceId();
+ }
+ return font.getRawValue();
+ }
+
+ private Object getIconOrRef(GThemeValueMap values, String id) {
+ IconValue icon = values.getIcon(id);
+ if (icon.getReferenceId() != null) {
+ return icon.getReferenceId();
+ }
+ return icon.getRawValue();
+ }
+}
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 45d9ef8d7e..b199b3f091 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
@@ -135,6 +135,13 @@ public abstract class AbstractOptions implements Options {
throw new IllegalArgumentException(
"Can't register an option of type: " + OptionType.NO_TYPE);
}
+
+ if (!type.isCompatible(defaultValue)) {
+ throw new IllegalStateException(
+ "Given default value does not match the given OptionType! OptionType = " + type +
+ ", defaultValue = " + defaultValue);
+ }
+
if (type == OptionType.CUSTOM_TYPE && editor == null) {
throw new IllegalStateException(
"Can't register a custom option without a property editor");
@@ -173,8 +180,7 @@ public abstract class AbstractOptions implements Options {
if (option == null) {
return null;
}
-
- if (!isCompatibleOption(option, type, defaultValue)) {
+ if (option.getOptionType() != type) {
Msg.error(this, "Registered option incompatible with existing option: " + optionName,
new AssertException());
return null;
@@ -182,15 +188,6 @@ public abstract class AbstractOptions implements Options {
return option;
}
- private boolean isCompatibleOption(Option option, OptionType type, Object defaultValue) {
- if (option.getOptionType() != type) {
- return false;
- }
- Object optionValue = option.getValue(null);
- return optionValue == null || defaultValue == null ||
- optionValue.getClass().equals(defaultValue.getClass());
- }
-
@Override
public synchronized void removeOption(String optionName) {
aliasMap.remove(optionName);
@@ -700,7 +697,16 @@ public abstract class AbstractOptions implements Options {
return true;
}
- return SUPPORTED_CLASSES.contains(obj.getClass());
+ if (SUPPORTED_CLASSES.contains(obj.getClass())) {
+ return true;
+ }
+ // check for extended classes
+ for (Class> class1 : SUPPORTED_CLASSES) {
+ if (class1.isAssignableFrom(obj.getClass())) {
+ return true;
+ }
+ }
+ return false;
}
/**
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/OptionType.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/OptionType.java
index 060c11f852..ec75c49e26 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/OptionType.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/OptionType.java
@@ -64,6 +64,10 @@ public enum OptionType {
return stringAdapter.objectToString(object);
}
+ public boolean isCompatible(Object object) {
+ return object == null || clazz.isAssignableFrom(object.getClass());
+ }
+
public Class> getValueClass() {
return clazz;
}
@@ -230,8 +234,8 @@ public enum OptionType {
}
catch (Exception e) {
Msg.error(this,
- "Can't create customOption instance for: " + customOptionClassName +
- e);
+ "Can't create customOption instance for: " + customOptionClassName +
+ e);
}
return null;
}
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/WebColors.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/WebColors.java
index e28748e217..eee9c7ceb5 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/WebColors.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/WebColors.java
@@ -18,8 +18,6 @@ package ghidra.util;
import java.awt.Color;
import java.util.HashMap;
import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* Class for web color support. This class defines many of the colors used by html. This class
@@ -27,7 +25,7 @@ import java.util.regex.Pattern;
* those strings back to a color.
*/
public abstract class WebColors {
- private static final Pattern HEX_PATTERN = Pattern.compile("(0x|#)[0-9A-Fa-f]{6}");
+// private static final Pattern HEX_PATTERN = Pattern.compile("(0x|#)[0-9A-Fa-f]{6}");
private static final Map nameToColorMap = new HashMap<>();
private static final Map colorToNameMap = new HashMap<>();
@@ -172,12 +170,12 @@ public abstract class WebColors {
public static final Color MEDUM_AQUA_MARINE = registerColor("MediumAquaMarine", Color.decode("0x66CDAA"));
public static final Color MEDIUM_TURQOISE = registerColor("MediumTurquoise", Color.decode("0x48D1CC"));
public static final Color DARK_OLIVE_GREEN = registerColor("DarkOliveGreen", Color.decode("0x556B2F"));
- public static final Color CORN_FLOWER_BLUE = registerColor("CornflowerBlue", Color.decode("0x6495ED"));
+ public static final Color COMFLOWER_BLUE = registerColor("ComflowerBlue", Color.decode("0x6495ED"));
//@formatter:on
// cannot instantiate nor extend
- private WebColors() {
- }
+ private WebColors() {
+ }
/**
* Tries to find a color for the given String value. The String value can either be
@@ -193,57 +191,202 @@ public abstract class WebColors {
return color != null ? color : defaultColor;
}
- /**
- * Tries to find a color for the given String value. The String value can either be
- * a hex string (see {@link Color#decode(String)}) or a web color name as defined
- * in {@link WebColors}
- *
- * @param value the string value to interpret as a color
- * @return a color for the given string value or null if the string can't be translated
- */
- public static Color getColor(String value) {
- Color color = nameToColorMap.get(value);
- if (color != null) {
- return color;
- }
- // if the value matches an RGB hex string, turn that into a color
- color = getHexColor(value);
- if (color != null) {
- return color;
- }
- return null;
- }
-
/**
* Converts a color to a string value. If there is a defined color for the given color value,
* the color name will be returned. Otherwise, it will return a hex string for the color as
- * defined by {@link Color#toString()}. The result of this call can be passed to
- * {@link #getColor(String)} and be guaranteed that a color will be returned
+ * follows. If the color has an non-opaque alpha value, it will be of the form #rrggbb. If
+ * it has an alpha value,then the format will be #rrggbbaa.
*
* @param color the color to convert to a string.
* @return the string representation for the given color.
*/
public static String toString(Color color) {
- String name = colorToNameMap.get(color);
- if (name != null) {
- return name;
- }
- // this will format a color value as a 6 digit hex (e.g. #rrggbb)
- return String.format("#%06X", color.getRGB() & 0xffffff);
+ return toString(color, true);
}
- private static Color getHexColor(String hexString) {
- Matcher matcher = HEX_PATTERN.matcher(hexString);
- if (matcher.matches()) {
- return Color.decode(hexString);
+ /**
+ * Converts a color to a string value. If the color is a WebColor and the useNameIfPossible
+ * is true, the name of the color will be returned. OOtherwise, it will return a hex string for the color as
+ * follows. If the color has an non-opaque alpha value, it will be of the form #rrggbb. If
+ * it has an alpha value ,then the format will be #rrggbbaa.
+ *
+ * @param color the color to convert to a string.
+ * @param useNameIfPossible if true, the name of the color will be returned if the color is
+ * a WebColor
+ * @return the string representation for the given color.
+ */
+ public static String toString(Color color, boolean useNameIfPossible) {
+ if (useNameIfPossible) {
+ String name = colorToNameMap.get(color);
+ if (name != null) {
+ return name;
+ }
}
- return null;
+ int rgb = color.getRGB() & 0xffffff; //mask off any alpha value
+ int alpha = color.getAlpha();
+ if (alpha != 0xff) {
+ return String.format("#%06x%02x", rgb, alpha);
+ }
+ return String.format("#%06x", rgb);
+ }
+
+ /**
+ * Returns the WebColor name for the given color. Returns null if the color is not a WebColor
+ * @param color the color to lookup a WebColor name.
+ * @return the WebColor name for the given color. Returns null if the color is not a WebColor
+ */
+ public static String toWebColorName(Color color) {
+ return colorToNameMap.get(color);
}
private static Color registerColor(String name, Color color) {
- nameToColorMap.put(name, color);
+ nameToColorMap.put(name.toLowerCase(), color);
colorToNameMap.put(color, name);
return color;
}
+ /**
+ * Attempts to convert the given string into a color in a most flexible manner. It first checks
+ * if the given string matches the name of a known web color as defined above. If so it
+ * returns that color. Otherwise it tries to parse the string in any one of the following
+ * formats:
+ *
+ * #rrggbb
+ * #rrggbbaa
+ * 0xrrggbb
+ * 0xrrggbbaa
+ * rgb(red, green, blue)
+ * rgba(red, green, alpha)
+ *
+ * In the hex digit formats, the hex digits "rr", "gg", "bb", "aa" represent the values for red,
+ * green, blue, and alpha, respectively. In the "rgb" and "rgba" formats the red, green, and
+ * blue values are all integers between 0-255, while the alpha value is a float value from 0.0 to
+ * 1.0.
+ *
+ * @param colorString
+ * @return a color for the given string or null
+ */
+ public static Color getColor(String colorString) {
+ String value = colorString.trim().toLowerCase();
+ Color color = nameToColorMap.get(value.toLowerCase());
+ if (color != null) {
+ return color;
+ }
+
+ return parseColor(value);
+ }
+
+ private static Color parseColor(String colorString) {
+ if (colorString.startsWith("#") || colorString.startsWith("0x")) {
+ return parseHexColor(colorString);
+ }
+ if (colorString.startsWith("rgb(")) {
+ return parseRGBColor(colorString);
+ }
+ return parseRGBAColor(colorString);
+ }
+
+ /**
+ * Parses the given string into a color. The string must be in one of the following formats:
+ *
+ * #rrggbb
+ * #rrggbbaa
+ * 0xrrggbb
+ * 0xrrggbbaa
+ *
+ *
+ * Each of the hex digits "rr", "gg", "bb", and "aa" specify the red, green, blue, and alpha
+ * values respectively.
+ *
+ *
+ * @param hexString the string to parse into a color.
+ * @return the parsed Color or null if the input string was invalid.
+ */
+ private static Color parseHexColor(String hexString) {
+ String value = hexString.trim();
+ if (value.startsWith("#")) {
+ value = value.substring(1);
+ }
+ else if (value.startsWith("0x")) {
+ value = value.substring(2);
+ }
+ else {
+ return null;
+ }
+
+ if (value.length() != 8 && value.length() != 6) {
+ return null;
+ }
+
+ boolean hasAlpha = value.length() == 8;
+ if (hasAlpha) {
+ // alpha value is the last 2 digits, Color wants alpha to be in upper bits so re-arrange
+ value = value.substring(6) + value.substring(0, 6);
+ }
+
+ try {
+ long colorValue = Long.parseLong(value, 16);
+ return new Color((int) colorValue, hasAlpha);
+ }
+ catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Parses the given string into a color. The string must be in one of the following formats:
+ *
+ * rgb(red, green, blue)
+ * rgb(red, green, blue, alpha)
+ *
+ * Each of the values "red", "green", "blue", and "alpha" must be integer values between 0-255
+ *
+ * @param rgbString the string to parse into a color.
+ * @return the parsed Color or null if the input string was invalid.
+ */
+ private static Color parseRGBColor(String rgbString) {
+ String value = rgbString.trim().replaceAll(" ", "");
+ if (!(value.startsWith("rgb(") && value.endsWith(")"))) {
+ return null;
+ }
+ // strip off to comma separated values
+ value = value.substring(4, value.length() - 1);
+ String[] split = value.split(",");
+ if (split.length != 3) {
+ return null;
+ }
+ try {
+ int red = Integer.parseInt(split[0]);
+ int green = Integer.parseInt(split[1]);
+ int blue = Integer.parseInt(split[2]);
+ return new Color(red, green, blue);
+ }
+ catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
+ private static Color parseRGBAColor(String rgbaString) {
+ String value = rgbaString.replaceAll(" ", "");
+ if (!(value.startsWith("rgba(") && value.endsWith(")"))) {
+ return null;
+ }
+ // strip off to comma separated values
+ value = value.substring(5, value.length() - 1);
+ value = value.replaceAll(" ", "");
+ String[] split = value.split(",");
+ if (split.length != 4) {
+ return null;
+ }
+ try {
+ int red = Integer.parseInt(split[0]);
+ int green = Integer.parseInt(split[1]);
+ int blue = Integer.parseInt(split[2]);
+ float alpha = Float.parseFloat(split[3]);
+ return new Color(red, green, blue, (int) (alpha * 255 + 0.5));
+ }
+ catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
}
diff --git a/Ghidra/Framework/Generic/src/test/java/ghidra/util/WebColorsTest.java b/Ghidra/Framework/Generic/src/test/java/ghidra/util/WebColorsTest.java
index 6968ef9a29..0e61a39b74 100644
--- a/Ghidra/Framework/Generic/src/test/java/ghidra/util/WebColorsTest.java
+++ b/Ghidra/Framework/Generic/src/test/java/ghidra/util/WebColorsTest.java
@@ -39,27 +39,16 @@ public class WebColorsTest {
}
@Test
- public void testGetColorFromName() {
+ public void testGetColor() {
assertEquals(WebColors.NAVY, WebColors.getColor("Navy"));
- }
-
- @Test
- public void testGetColorFromHexString() {
assertEquals(WebColors.NAVY, WebColors.getColor("0x000080"));
- }
-
- @Test
- public void testGetColorFromHexString2() {
assertEquals(WebColors.NAVY, WebColors.getColor("#000080"));
- }
+ assertEquals(WebColors.NAVY, WebColors.getColor("rgb(0,0,128)"));
+ assertEquals(WebColors.NAVY, WebColors.getColor("rgba(0,0,128,1.0)"));
- @Test
- public void testGetColorWithNoDefinedValue() {
- assertEquals(new Color(0x12, 0x34, 0x56), WebColors.getColor("0x123456"));
- }
+ assertEquals(new Color(0x123456), WebColors.getColor("0x123456"));
+ assertEquals(new Color(0x80102030, true), WebColors.getColor("rgba(16, 32, 48, 0.5)"));
- @Test
- public void testGetColorByBadName() {
- assertNull(WebColors.getColor("ABCDEFG"));
+ assertNull(WebColors.getColor("asdfasdfas"));
}
}
diff --git a/Ghidra/Framework/Graph/certification.manifest b/Ghidra/Framework/Graph/certification.manifest
index b693379ab1..dcf8979d70 100644
--- a/Ghidra/Framework/Graph/certification.manifest
+++ b/Ghidra/Framework/Graph/certification.manifest
@@ -5,6 +5,7 @@
##MODULE IP: Oxygen Icons - LGPL 3.0
Module.manifest||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END|
+data/graph.theme.properties||GHIDRA||||END|
src/main/docs/README.txt||GHIDRA||||END|
src/main/docs/VerticesAndEdges.png||GHIDRA||||END|
src/main/docs/VerticesAndEdges.xml||GHIDRA||||END|
diff --git a/Ghidra/Framework/Graph/data/graph.theme.properties b/Ghidra/Framework/Graph/data/graph.theme.properties
new file mode 100644
index 0000000000..de7206c154
--- /dev/null
+++ b/Ghidra/Framework/Graph/data/graph.theme.properties
@@ -0,0 +1,13 @@
+[Defaults]
+
+
+color.bg.visualgraph = color.bg
+color.bg.highlight.visualgraph = rgba(255,255,0,0.6) // somewhat transparent yellow
+
+color.graph.display.vertex = green
+color.graph.display.edge = green
+color.graph.display.vertex.selected = blue
+color.graph.display.edge.selected = blue
+
+[Dark Defaults]
+
diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/options/VisualGraphOptions.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/options/VisualGraphOptions.java
index 5ad0e2c952..7538078f1c 100644
--- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/options/VisualGraphOptions.java
+++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/options/VisualGraphOptions.java
@@ -18,6 +18,7 @@ package ghidra.graph.viewer.options;
import java.awt.Color;
import docking.DockingUtils;
+import docking.theme.GColor;
import ghidra.framework.options.Options;
import ghidra.util.HelpLocation;
@@ -56,7 +57,7 @@ public class VisualGraphOptions {
"new graphs and already rendered graphs are zoomed and positioned. See the help for " +
"more details.";
- public static final Color DEFAULT_GRAPH_BACKGROUND_COLOR = Color.WHITE;
+ public static final Color DEFAULT_GRAPH_BACKGROUND_COLOR = new GColor("color.bg.visualgraph");
protected Color graphBackgroundColor = DEFAULT_GRAPH_BACKGROUND_COLOR;
protected boolean useAnimation = true;
diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/vertex/AbstractVisualVertexRenderer.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/vertex/AbstractVisualVertexRenderer.java
index 2a85a2a0bd..cbd92f9ec1 100644
--- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/vertex/AbstractVisualVertexRenderer.java
+++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/vertex/AbstractVisualVertexRenderer.java
@@ -15,7 +15,7 @@
*/
package ghidra.graph.viewer.vertex;
-import static ghidra.graph.viewer.GraphViewerUtils.INTERACTION_ZOOM_THRESHOLD;
+import static ghidra.graph.viewer.GraphViewerUtils.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
@@ -23,6 +23,7 @@ import java.awt.geom.Point2D;
import com.google.common.base.Function;
+import docking.theme.GColor;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.visualization.*;
import edu.uci.ics.jung.visualization.renderers.BasicVertexRenderer;
@@ -41,6 +42,8 @@ import ghidra.graph.viewer.VisualVertex;
public class AbstractVisualVertexRenderer>
extends BasicVertexRenderer {
+ private static final Color HIGHLIGHT_COLOR = new GColor("color.bg.highlight.visualgraph");
+
/**
* Creates a copy of the given {@link GraphicsDecorator} that may have scaling tweaked to
* handle {@link VisualVertex#getEmphasis()} emphasized vertices.
@@ -96,8 +99,7 @@ public class AbstractVisualVertexRenderer edgePriorityMap = new HashMap<>();
private List changeListeners = new CopyOnWriteArrayList<>();
- private Color vertexSelectionColor = Color.green;
- private Color edgeSelectionColor = Color.green;
- private Color defaultVertexColor = Color.blue;
- private Color defaultEdgeColor = Color.blue;
+ private Color vertexSelectionColor = new GColor("color.graph.display.vertex.selected");
+ private Color edgeSelectionColor = new GColor("color.graph.display.edge.selected");
+ private Color defaultVertexColor = new GColor("color.graph.display.vertex");
+ private Color defaultEdgeColor = new GColor("color.graph.display.edge");
private String favoredEdgeType;
private VertexShape defaultVertexShape = VertexShape.RECTANGLE;
@@ -727,9 +729,8 @@ public class GraphDisplayOptions implements OptionsChangeListener {
Options options = rootOptions.getOptions(VERTEX_COLORS);
for (String vertexType : graphType.getVertexTypes()) {
- options.registerOption(vertexType, OptionType.COLOR_TYPE,
- getVertexColor(vertexType), help,
- "Choose the color for this vertex type");
+ options.registerOption(vertexType, OptionType.COLOR_TYPE, getVertexColor(vertexType),
+ help, "Choose the color for this vertex type");
}
List list = new ArrayList<>(graphType.getVertexTypes());
OptionsEditor editor = new ScrollableOptionsEditor(VERTEX_COLORS, list);
@@ -744,8 +745,8 @@ public class GraphDisplayOptions implements OptionsChangeListener {
for (String vertexType : graphType.getVertexTypes()) {
StringWithChoicesEditor editor = new StringWithChoicesEditor(shapeNames);
options.registerOption(vertexType, OptionType.STRING_TYPE,
- getVertexShapeName(vertexType), help,
- "Choose the shape for this vertex type", editor);
+ getVertexShapeName(vertexType), help, "Choose the shape for this vertex type",
+ editor);
}
List list = new ArrayList<>(graphType.getVertexTypes());
OptionsEditor editor = new ScrollableOptionsEditor(VERTEX_SHAPES, list);
@@ -756,8 +757,8 @@ public class GraphDisplayOptions implements OptionsChangeListener {
Options options = rootOptions.getOptions(EDGE_COLORS);
for (String edgeType : graphType.getEdgeTypes()) {
- options.registerOption(edgeType, OptionType.COLOR_TYPE,
- getEdgeColor(edgeType), help, "Choose the color for this edge type");
+ options.registerOption(edgeType, OptionType.COLOR_TYPE, getEdgeColor(edgeType), help,
+ "Choose the color for this edge type");
}
List list = new ArrayList<>(graphType.getEdgeTypes());
OptionsEditor editor = new ScrollableOptionsEditor(EDGE_COLORS, list);
@@ -779,14 +780,14 @@ public class GraphDisplayOptions implements OptionsChangeListener {
help, "Color for highlighting selected edge");
options.registerOption(DEFAULT_VERTEX_SHAPE, OptionType.STRING_TYPE,
- defaultVertexShape.getName(),
- help, "Shape for vertices that have no vertex type defined", editor);
+ defaultVertexShape.getName(), help,
+ "Shape for vertices that have no vertex type defined", editor);
options.registerOption(DEFAULT_VERTEX_COLOR, OptionType.COLOR_TYPE, defaultVertexColor,
help, "Color for vertices that have no vertex type defined");
- options.registerOption(DEFAULT_EDGE_COLOR, OptionType.COLOR_TYPE, defaultEdgeColor,
- help, "Color for edge that have no edge type defined");
+ options.registerOption(DEFAULT_EDGE_COLOR, OptionType.COLOR_TYPE, defaultEdgeColor, help,
+ "Color for edge that have no edge type defined");
List edgeTypes = graphType.getEdgeTypes();
if (!edgeTypes.isEmpty()) {
diff --git a/Ghidra/Framework/Project/certification.manifest b/Ghidra/Framework/Project/certification.manifest
index ab5289e0bc..642d3e7b64 100644
--- a/Ghidra/Framework/Project/certification.manifest
+++ b/Ghidra/Framework/Project/certification.manifest
@@ -8,6 +8,7 @@
##MODULE IP: Tango Icons - Public Domain
Module.manifest||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END|
+data/project.theme.properties||GHIDRA||||END|
src/main/java/ghidra/framework/cmd/package.html||GHIDRA||reviewed||END|
src/main/java/ghidra/framework/data/package.html||GHIDRA||reviewed||END|
src/main/java/ghidra/framework/model/package.html||GHIDRA||||END|
diff --git a/Ghidra/Framework/Project/data/project.theme.properties b/Ghidra/Framework/Project/data/project.theme.properties
new file mode 100644
index 0000000000..5f8dcc4907
--- /dev/null
+++ b/Ghidra/Framework/Project/data/project.theme.properties
@@ -0,0 +1,34 @@
+[Defaults]
+
+color.fg.pluginpanel.name = color.fg
+color.fg.pluginpanel.description = gray
+
+color.bg.panel.details = color.bg
+
+color.fg.pluginpanel.details.title = maroon
+color.fg.pluginpanel.details.name.no-dependents = limeGreen
+color.fg.pluginpanel.details.name.has-dependents = red
+color.fg.pluginpanel.details.description = blue
+color.fg.pluginpanel.details.category = magenta
+color.fg.pluginpanel.details.class = black
+color.fg.pluginpanel.details.loc = darkGray
+color.fg.pluginpanel.details.developer =mediumVioletRed
+color.fg.pluginpanel.details.dependency = green
+color.fg.pluginpanel.details.novalue = lightGray
+
+color.border.pluginpanel = darkGray
+
+[Dark Defaults]
+color.fg.pluginpanel.details.title = indianRed
+color.fg.pluginpanel.details.name.no-dependents = forestGreen
+color.fg.pluginpanel.details.name.has-dependents = indianRed
+color.fg.pluginpanel.details.description = blue
+color.fg.pluginpanel.details.category = darkMagenta
+color.fg.pluginpanel.details.class = gray
+color.fg.pluginpanel.details.loc = darkGray
+color.fg.pluginpanel.details.developer =mediumVioletRed
+color.fg.pluginpanel.details.dependency = forestGreen
+color.fg.pluginpanel.details.novalue = dimGray
+
+color.fg.pluginpanel.name = lightGray
+color.fg.pluginpanel.description = Gray
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/AbstractDetailsPanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/AbstractDetailsPanel.java
index ba979a4a24..65d698525c 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/AbstractDetailsPanel.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/AbstractDetailsPanel.java
@@ -23,6 +23,7 @@ import javax.swing.*;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
+import docking.theme.GColor;
import docking.widgets.label.GDHtmlLabel;
import ghidra.util.HTMLUtilities;
@@ -118,7 +119,7 @@ public abstract class AbstractDetailsPanel extends JPanel {
textLabel.setVerticalAlignment(SwingConstants.TOP);
textLabel.setOpaque(true);
- textLabel.setBackground(Color.WHITE);
+ textLabel.setBackground(new GColor("color.bg.panel.details"));
sp = new JScrollPane(textLabel);
sp.getVerticalScrollBar().setUnitIncrement(10);
sp.setPreferredSize(new Dimension(MIN_WIDTH, 200));
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginDetailsPanel.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginDetailsPanel.java
index 973306fb3d..30fc1c5e8d 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginDetailsPanel.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginDetailsPanel.java
@@ -15,7 +15,6 @@
*/
package ghidra.framework.plugintool.dialog;
-import java.awt.Color;
import java.awt.Point;
import java.util.*;
@@ -26,6 +25,7 @@ import javax.swing.text.StyleConstants;
import docking.action.DockingActionIf;
import docking.action.MenuData;
import docking.actions.KeyBindingUtils;
+import docking.theme.GColor;
import ghidra.framework.plugintool.PluginConfigurationModel;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginDescription;
@@ -241,64 +241,75 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
@Override
protected void createFieldAttributes() {
+
titleAttrSet = new SimpleAttributeSet();
titleAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
titleAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
titleAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
- titleAttrSet.addAttribute(StyleConstants.Foreground, new Color(140, 0, 0));
+ titleAttrSet.addAttribute(StyleConstants.Foreground,
+ new GColor("color.fg.pluginpanel.details.title"));
nameAttrSet = new SimpleAttributeSet();
nameAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
nameAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
nameAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
- nameAttrSet.addAttribute(StyleConstants.Foreground, new Color(0, 204, 51));
+ nameAttrSet.addAttribute(StyleConstants.Foreground,
+ new GColor("color.fg.pluginpanel.details.name.no-dependents"));
depNameAttrSet = new SimpleAttributeSet();
depNameAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
depNameAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
depNameAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
- depNameAttrSet.addAttribute(StyleConstants.Foreground, Color.RED);
+ depNameAttrSet.addAttribute(StyleConstants.Foreground,
+ new GColor("color.fg.pluginpanel.details.name.has-dependents"));
descrAttrSet = new SimpleAttributeSet();
descrAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
descrAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
descrAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
- descrAttrSet.addAttribute(StyleConstants.Foreground, Color.BLUE);
+ descrAttrSet.addAttribute(StyleConstants.Foreground,
+ new GColor("color.fg.pluginpanel.details.description"));
categoriesAttrSet = new SimpleAttributeSet();
categoriesAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
categoriesAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
categoriesAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
- categoriesAttrSet.addAttribute(StyleConstants.Foreground, new Color(204, 0, 204));
+ categoriesAttrSet.addAttribute(StyleConstants.Foreground,
+ new GColor("color.fg.pluginpanel.details.category"));
classAttrSet = new SimpleAttributeSet();
classAttrSet.addAttribute(StyleConstants.FontFamily, "monospaced");
classAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
classAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
- classAttrSet.addAttribute(StyleConstants.Foreground, Color.BLACK);
+ classAttrSet.addAttribute(StyleConstants.Foreground,
+ new GColor("color.fg.pluginpanel.details.class"));
locAttrSet = new SimpleAttributeSet();
locAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
locAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
locAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
- locAttrSet.addAttribute(StyleConstants.Foreground, Color.DARK_GRAY);
+ locAttrSet.addAttribute(StyleConstants.Foreground,
+ new GColor("color.fg.pluginpanel.details.loc"));
developerAttrSet = new SimpleAttributeSet();
developerAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
developerAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
developerAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
- developerAttrSet.addAttribute(StyleConstants.Foreground, new Color(230, 15, 85));
+ developerAttrSet.addAttribute(StyleConstants.Foreground,
+ new GColor("color.fg.pluginpanel.details.developer"));
dependencyAttrSet = new SimpleAttributeSet();
dependencyAttrSet.addAttribute(StyleConstants.FontFamily, "monospaced");
dependencyAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
dependencyAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
- dependencyAttrSet.addAttribute(StyleConstants.Foreground, new Color(23, 100, 30));
+ dependencyAttrSet.addAttribute(StyleConstants.Foreground,
+ new GColor("color.fg.pluginpanel.details.dependency"));
noValueAttrSet = new SimpleAttributeSet();
noValueAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
noValueAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
noValueAttrSet.addAttribute(StyleConstants.Italic, Boolean.TRUE);
- noValueAttrSet.addAttribute(StyleConstants.Foreground, new Color(192, 192, 192));
+ noValueAttrSet.addAttribute(StyleConstants.Foreground,
+ new GColor("color.fg.pluginpanel.details.novalue"));
}
}
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginManagerComponent.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginManagerComponent.java
index af96ae3298..7f2fd236b0 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginManagerComponent.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginManagerComponent.java
@@ -23,6 +23,8 @@ import javax.swing.*;
import javax.swing.event.HyperlinkEvent.EventType;
import docking.EmptyBorderToggleButton;
+import docking.theme.GColor;
+import docking.theme.GThemeDefaults;
import docking.widgets.HyperlinkComponent;
import docking.widgets.checkbox.GCheckBox;
import docking.widgets.label.*;
@@ -45,7 +47,7 @@ public class PluginManagerComponent extends JPanel implements Scrollable {
PluginManagerComponent(PluginTool tool, PluginConfigurationModel model) {
super(new VerticalLayout(2));
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
- setBackground(Color.WHITE);
+ setBackground(new GColor("color.bg"));
this.tool = tool;
this.model = model;
model.setChangeCallback(this::updateCheckboxes);
@@ -114,7 +116,7 @@ public class PluginManagerComponent extends JPanel implements Scrollable {
//=================================================================================================
private class PluginPackageComponent extends JPanel {
- private final Color BG = Color.white;
+ private Color BG = new GColor("color.bg");
private final PluginPackage pluginPackage;
private final GCheckBox checkBox;
@@ -129,7 +131,7 @@ public class PluginManagerComponent extends JPanel implements Scrollable {
initializeLabelSection();
initializeDescriptionSection();
- setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY));
+ setBorder(BorderFactory.createLineBorder(GThemeDefaults.Colors.Java.BORDER));
updateCheckBoxState();
}
@@ -152,8 +154,7 @@ public class PluginManagerComponent extends JPanel implements Scrollable {
if (icon == null) {
icon = DEFAULT_ICON;
}
- JLabel iconLabel =
- new GIconLabel(ResourceManager.getScaledIcon(icon, 32, 32, 32));
+ JLabel iconLabel = new GIconLabel(ResourceManager.getScaledIcon(icon, 32, 32, 32));
iconLabel.setBackground(BG);
checkboxPanel.add(iconLabel);
@@ -176,7 +177,7 @@ public class PluginManagerComponent extends JPanel implements Scrollable {
GLabel nameLabel = new GLabel(pluginPackage.getName());
nameLabel.setFont(nameLabel.getFont().deriveFont(18f));
- nameLabel.setForeground(Color.BLACK);
+ nameLabel.setForeground(new GColor("color.fg.pluginpanel.name"));
labelPanel.add(nameLabel);
HyperlinkComponent configureHyperlink = createConfigureHyperlink();
@@ -207,7 +208,7 @@ public class PluginManagerComponent extends JPanel implements Scrollable {
String htmlDescription = enchanceDescription(pluginPackage.getDescription());
JLabel descriptionlabel = new GHtmlLabel(htmlDescription);
- descriptionlabel.setForeground(Color.GRAY);
+ descriptionlabel.setForeground(new GColor("color.fg.pluginpanel.description"));
descriptionlabel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0));
descriptionlabel.setVerticalAlignment(SwingConstants.TOP);
descriptionlabel.setToolTipText(