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 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(