GP-1981 - Checkpoint - Initial Theming

This commit is contained in:
ghidragon 2022-04-09 20:24:28 +08:00
parent 38292de02c
commit f808431251
108 changed files with 4355 additions and 738 deletions

View file

@ -36,7 +36,7 @@ class CursorBackgroundColorModel implements ListingBackgroundColorModel {
private AddressIndexMap addressIndexMap; private AddressIndexMap addressIndexMap;
@AutoOptionConsumed(category = {}, name = GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR) @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) @AutoOptionConsumed(category = {}, name = GhidraOptions.HIGHLIGHT_CURSOR_LINE)
private boolean doHighlight = true; private boolean doHighlight = true;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -61,7 +61,7 @@ class CursorBackgroundColorModel implements ListingBackgroundColorModel {
if (!Objects.equals(cursorAddress, address)) { if (!Objects.equals(cursorAddress, address)) {
return defaultBackgroundColor; return defaultBackgroundColor;
} }
return cursorColor; return cursorLineColor;
} }
@Override @Override

View file

@ -16,6 +16,10 @@ data/ElfFunctionsThatDoNotReturn||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END| data/ExtensionPoint.manifest||GHIDRA||||END|
data/MachOFunctionsThatDoNotReturn||GHIDRA||||END| data/MachOFunctionsThatDoNotReturn||GHIDRA||||END|
data/PEFunctionsThatDoNotReturn||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/file_extension_icons.xml||GHIDRA||||END|
data/functionTags.xml||GHIDRA||||END| data/functionTags.xml||GHIDRA||||END|
data/ms_pe_rich_products.xml||GHIDRA||||END| data/ms_pe_rich_products.xml||GHIDRA||||END|

View file

@ -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)

View file

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

View file

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

View file

@ -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)

View file

@ -18,6 +18,7 @@ package ghidra;
import java.awt.Color; import java.awt.Color;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import docking.theme.GColor;
import ghidra.framework.options.Options; 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 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"; final String HIGHLIGHT_CURSOR_LINE_OPTION_NAME = "Highlight Cursor Line";
@ -176,6 +177,7 @@ public interface GhidraOptions {
public static enum CURSOR_MOUSE_BUTTON_NAMES { public static enum CURSOR_MOUSE_BUTTON_NAMES {
LEFT(MouseEvent.BUTTON1), MIDDLE(MouseEvent.BUTTON2), RIGHT(MouseEvent.BUTTON3); LEFT(MouseEvent.BUTTON1), MIDDLE(MouseEvent.BUTTON2), RIGHT(MouseEvent.BUTTON3);
private int mouseEventID; private int mouseEventID;
CURSOR_MOUSE_BUTTON_NAMES(int mouseEventID) { CURSOR_MOUSE_BUTTON_NAMES(int mouseEventID) {
@ -190,10 +192,9 @@ public interface GhidraOptions {
// end cursor highlight // end cursor highlight
final String OPTION_SELECTION_COLOR = "Selection Colors.Selection Color"; 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 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"; final String APPLY_ENABLED = "apply.enabled";
} }

View file

@ -26,6 +26,7 @@ import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import docking.action.DockingAction; import docking.action.DockingAction;
import docking.theme.GColor;
import docking.widgets.fieldpanel.*; import docking.widgets.fieldpanel.*;
import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.field.Field;
import docking.widgets.fieldpanel.support.FieldLocation; import docking.widgets.fieldpanel.support.FieldLocation;
@ -61,12 +62,17 @@ public abstract class AbstractCodeBrowserPlugin<P extends CodeViewerProvider> ex
implements CodeViewerService, CodeFormatService, OptionsChangeListener, FormatModelListener, implements CodeViewerService, CodeFormatService, OptionsChangeListener, FormatModelListener,
DomainObjectListener, CodeBrowserPluginInterface { 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 CURSOR_COLOR = "Cursor.Cursor Color - Focused";
private static final String UNFOCUSED_CURSOR_COLOR = "Cursor.Cursor Color - Unfocused"; private static final String UNFOCUSED_CURSOR_COLOR = "Cursor.Cursor Color - Unfocused";
private static final String BLINK_CURSOR = "Cursor.Blink Cursor"; private static final String BLINK_CURSOR = "Cursor.Blink Cursor";
private static final String MOUSE_WHEEL_HORIZONTAL_SCROLLING = "Mouse.Horizontal Scrolling"; 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 - // - Icon -
private ImageIcon CURSOR_LOC_ICON = private ImageIcon CURSOR_LOC_ICON =
ResourceManager.loadImage("images/cursor_arrow_flipped.gif"); ResourceManager.loadImage("images/cursor_arrow_flipped.gif");
@ -554,14 +560,15 @@ public abstract class AbstractCodeBrowserPlugin<P extends CodeViewerProvider> ex
GhidraOptions.DEFAULT_HIGHLIGHT_COLOR, helpLocation, GhidraOptions.DEFAULT_HIGHLIGHT_COLOR, helpLocation,
"The highlight color in the browser."); "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."); "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."); "The color of the cursor in the browser when the browser does not have focus.");
fieldOptions.registerOption(BLINK_CURSOR, true, helpLocation, fieldOptions.registerOption(BLINK_CURSOR, true, helpLocation,
"When selected, the cursor will blink when the containing window is focused."); "When selected, the cursor will blink when the containing window is focused.");
fieldOptions.registerOption(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR, CURSOR_LINE_COLOR, fieldOptions.registerOption(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR,
helpLocation, "The background color of the line where the cursor is located"); ICURRENT_LINE_HIGHLIGHT_COLOR, helpLocation,
"The background color of the line where the cursor is located");
fieldOptions.registerOption(GhidraOptions.HIGHLIGHT_CURSOR_LINE, true, helpLocation, fieldOptions.registerOption(GhidraOptions.HIGHLIGHT_CURSOR_LINE, true, helpLocation,
"Toggles highlighting background color of line containing the cursor"); "Toggles highlighting background color of line containing the cursor");
@ -588,10 +595,10 @@ public abstract class AbstractCodeBrowserPlugin<P extends CodeViewerProvider> ex
highlightMarkers.setMarkerColor(color); highlightMarkers.setMarkerColor(color);
} }
color = fieldOptions.getColor(CURSOR_COLOR, Color.RED); color = fieldOptions.getColor(CURSOR_COLOR, IFOCUSED_CURSOR_COLOR);
fieldPanel.setFocusedCursorColor(color); fieldPanel.setFocusedCursorColor(color);
color = fieldOptions.getColor(UNFOCUSED_CURSOR_COLOR, Color.PINK); color = fieldOptions.getColor(UNFOCUSED_CURSOR_COLOR, IUNFOCUSED_CURSOR_COLOR);
fieldPanel.setNonFocusCursorColor(color); fieldPanel.setNonFocusCursorColor(color);
Boolean isBlinkCursor = fieldOptions.getBoolean(BLINK_CURSOR, true); Boolean isBlinkCursor = fieldOptions.getBoolean(BLINK_CURSOR, true);
@ -601,8 +608,8 @@ public abstract class AbstractCodeBrowserPlugin<P extends CodeViewerProvider> ex
fieldOptions.getBoolean(MOUSE_WHEEL_HORIZONTAL_SCROLLING, true); fieldOptions.getBoolean(MOUSE_WHEEL_HORIZONTAL_SCROLLING, true);
fieldPanel.setHorizontalScrollingEnabled(horizontalScrollingEnabled); fieldPanel.setHorizontalScrollingEnabled(horizontalScrollingEnabled);
cursorHighlightColor = cursorHighlightColor = fieldOptions.getColor(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR,
fieldOptions.getColor(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR, CURSOR_LINE_COLOR); ICURRENT_LINE_HIGHLIGHT_COLOR);
isHighlightCursorLine = fieldOptions.getBoolean(GhidraOptions.HIGHLIGHT_CURSOR_LINE, true); isHighlightCursorLine = fieldOptions.getBoolean(GhidraOptions.HIGHLIGHT_CURSOR_LINE, true);
} }

View file

@ -26,6 +26,7 @@ import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import docking.theme.GColor;
import docking.widgets.fieldpanel.field.FieldElement; import docking.widgets.fieldpanel.field.FieldElement;
import docking.widgets.fieldpanel.support.FieldLocation; import docking.widgets.fieldpanel.support.FieldLocation;
import docking.widgets.fieldpanel.support.Highlight; import docking.widgets.fieldpanel.support.Highlight;
@ -51,18 +52,15 @@ import ghidra.util.datastruct.Stack;
public class ListingHighlightProvider public class ListingHighlightProvider
implements ButtonPressedListener, OptionsChangeListener, HighlightProvider { implements ButtonPressedListener, OptionsChangeListener, HighlightProvider {
//@formatter:off
private static final String DISPLAY_HIGHLIGHT_NAME = private static final Color DEFAULT_HIGHLIGHT_COLOR = new GColor("color.fg.listing.highlighter.default");
CURSOR_HIGHLIGHT_GROUP + DELIMITER + "Enabled"; 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 SCOPED_WRITE_HIGHLIGHT_COLOR = private static final String DISPLAY_HIGHLIGHT_NAME = CURSOR_HIGHLIGHT_GROUP + DELIMITER + "Enabled";
CURSOR_HIGHLIGHT_GROUP + DELIMITER + "Scoped Write Highlight Color"; 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 SCOPED_READ_HIGHLIGHT_COLOR = private static final String SCOPE_REGISTER_OPERAND = CURSOR_HIGHLIGHT_GROUP + DELIMITER + "Scope Register Operand";
CURSOR_HIGHLIGHT_GROUP + DELIMITER + "Scoped Read Highlight Color"; //@formatter:on
private static final String SCOPE_REGISTER_OPERAND =
CURSOR_HIGHLIGHT_GROUP + DELIMITER + "Scope Register Operand";
private static char[] UNDERSCORE_AND_PERIOD_OK = new char[] { '.', '_' }; private static char[] UNDERSCORE_AND_PERIOD_OK = new char[] { '.', '_' };
private static char[] UNDERSCORE_OK = new char[] { '_' }; private static char[] UNDERSCORE_OK = new char[] { '_' };
@ -871,11 +869,11 @@ public class ListingHighlightProvider
ToolOptions opt = tool.getOptions(CATEGORY_BROWSER_FIELDS); ToolOptions opt = tool.getOptions(CATEGORY_BROWSER_FIELDS);
HelpLocation hl = new HelpLocation("CodeBrowserPlugin", "Cursor_Text_Highlight"); 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."); "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."); "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."); "The color to use for showing a register being read.");
opt.registerOption(SCOPE_REGISTER_OPERAND, true, hl, opt.registerOption(SCOPE_REGISTER_OPERAND, true, hl,
@ -895,11 +893,11 @@ public class ListingHighlightProvider
setHighlightString(null, null); setHighlightString(null, null);
} }
textMatchingHighlightColor = opt.getColor(HIGHLIGHT_COLOR_NAME, Color.YELLOW); textMatchingHighlightColor = opt.getColor(HIGHLIGHT_COLOR_NAME, DEFAULT_HIGHLIGHT_COLOR);
scopeWriteHighlightColor = scopeWriteHighlightColor =
opt.getColor(SCOPED_WRITE_HIGHLIGHT_COLOR, new Color(204, 204, 0)); opt.getColor(SCOPED_WRITE_HIGHLIGHT_COLOR, DEFAULT_SCOPED_WRITE_COLOR);
scopeReadHighlightColor = opt.getColor(SCOPED_READ_HIGHLIGHT_COLOR, new Color(0, 255, 0)); scopeReadHighlightColor =
opt.getColor(SCOPED_READ_HIGHLIGHT_COLOR, DEFAULT_SCOPED_READ_COLOR);
///////////////////////////////////////////////////// /////////////////////////////////////////////////////

View file

@ -18,6 +18,7 @@ package ghidra.app.plugin.core.codebrowser;
import java.awt.Color; import java.awt.Color;
import java.math.BigInteger; import java.math.BigInteger;
import docking.theme.GColor;
import docking.widgets.fieldpanel.support.BackgroundColorModel; import docking.widgets.fieldpanel.support.BackgroundColorModel;
import ghidra.app.services.MarkerService; import ghidra.app.services.MarkerService;
import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel; import ghidra.app.util.viewer.listingpanel.ListingBackgroundColorModel;
@ -33,7 +34,7 @@ public class MarkerServiceBackgroundColorModel implements ListingBackgroundColor
private MarkerService markerService; private MarkerService markerService;
private Program program; private Program program;
private AddressIndexMap indexMap; private AddressIndexMap indexMap;
private Color defaultBackgroundColor = Color.WHITE; private Color defaultBackgroundColor = new GColor("color.bg.markerservice");
public MarkerServiceBackgroundColorModel(MarkerService markerService, Program program, public MarkerServiceBackgroundColorModel(MarkerService markerService, Program program,
AddressIndexMap indexMap) { AddressIndexMap indexMap) {

View file

@ -28,6 +28,7 @@ import javax.swing.text.*;
import docking.DockingUtils; import docking.DockingUtils;
import docking.actions.KeyBindingUtils; import docking.actions.KeyBindingUtils;
import docking.theme.GColor;
import generic.util.WindowUtilities; import generic.util.WindowUtilities;
import ghidra.app.plugin.core.console.CodeCompletion; import ghidra.app.plugin.core.console.CodeCompletion;
import ghidra.framework.options.OptionsChangeListener; import ghidra.framework.options.OptionsChangeListener;
@ -37,6 +38,7 @@ import ghidra.util.*;
public class InterpreterPanel extends JPanel implements OptionsChangeListener { 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_LABEL = "Completion Window Trigger";
private static final String COMPLETION_WINDOW_TRIGGER_DESCRIPTION = private static final String COMPLETION_WINDOW_TRIGGER_DESCRIPTION =
"The key binding used to show the auto-complete window " + "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. " + "This is the font that will be used in the Console. " +
"Double-click the font example to change it."; "Double-click the font example to change it.";
private static final Color NORMAL_COLOR = Color.black; private static final Color NORMAL_COLOR = new GColor("color.fg.interpreterpanel");
private static final Color ERROR_COLOR = Color.red; private static final Color ERROR_COLOR = new GColor("color.fg.interpreterpanel.error");
public enum TextType { public enum TextType {
STDOUT, STDERR, STDIN; STDOUT, STDERR, STDIN;

View file

@ -15,17 +15,18 @@
*/ */
package ghidra.app.plugin.core.osgi; package ghidra.app.plugin.core.osgi;
import java.awt.*; import java.awt.BorderLayout;
import java.awt.Dimension;
import java.io.File; import java.io.File;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.*; import java.util.*;
import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelEvent;
import docking.action.builder.ActionBuilder; import docking.action.builder.ActionBuilder;
import docking.theme.GColor;
import docking.widgets.filechooser.GhidraFileChooser; import docking.widgets.filechooser.GhidraFileChooser;
import docking.widgets.filechooser.GhidraFileChooserMode; import docking.widgets.filechooser.GhidraFileChooserMode;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
@ -105,8 +106,8 @@ public class BundleStatusComponentProvider extends ComponentProviderAdapter {
bundleStatusTable = new GTable(bundleStatusTableModel); bundleStatusTable = new GTable(bundleStatusTableModel);
bundleStatusTable.setName("BUNDLESTATUS_TABLE"); bundleStatusTable.setName("BUNDLESTATUS_TABLE");
bundleStatusTable.setSelectionBackground(new Color(204, 204, 255)); bundleStatusTable.setSelectionBackground(new GColor("color.bg.table.selection.bundle"));
bundleStatusTable.setSelectionForeground(Color.BLACK); bundleStatusTable.setSelectionForeground(new GColor("color.fg.table.selection.bundle"));
bundleStatusTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); bundleStatusTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
// give actions a chance to update status when selection changed // give actions a chance to update status when selection changed

View file

@ -27,6 +27,7 @@ import javax.swing.event.TableModelEvent;
import org.osgi.framework.Bundle; import org.osgi.framework.Bundle;
import docking.theme.GColor;
import docking.widgets.table.*; import docking.widgets.table.*;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import generic.util.Path; import generic.util.Path;
@ -42,11 +43,14 @@ import ghidra.util.table.column.GColumnRenderer;
*/ */
public class BundleStatusTableModel public class BundleStatusTableModel
extends GDynamicColumnTableModel<BundleStatus, List<BundleStatus>> { extends GDynamicColumnTableModel<BundleStatus, List<BundleStatus>> {
private static final Color COLOR_BUNDLE_ERROR = Color.RED;
private static final Color COLOR_BUNDLE_DISABLED = Color.DARK_GRAY; //@formatter:off
private static final Color COLOR_BUNDLE_BUSY = Color.GRAY; private static final Color COLOR_BUNDLE_ERROR = new GColor("color.fg.table.bundle.error");
private static final Color COLOR_BUNDLE_INACTIVE = Color.BLACK; private static final Color COLOR_BUNDLE_DISABLED = new GColor("color.fg.table.bundle.disabled");
private static final Color COLOR_BUNDLE_ACTIVE = new Color(0.0f, .6f, 0.0f); // a dark green 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 BundleHost bundleHost;
private BundleStatusComponentProvider provider; private BundleStatusComponentProvider provider;

View file

@ -49,8 +49,7 @@ public class CodeUnitPrintable implements Printable {
private static final PaintContext PAINT_CONTEXT = new PaintContext(); private static final PaintContext PAINT_CONTEXT = new PaintContext();
static { static {
PAINT_CONTEXT.setForegroundColor(Color.BLACK); 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.setCursorColor(Color.RED);
PAINT_CONTEXT.setSelectionColor(new Color(180, 255, 180)); PAINT_CONTEXT.setSelectionColor(new Color(180, 255, 180));
PAINT_CONTEXT.setHighlightColor(new Color(255, 255, 150)); PAINT_CONTEXT.setHighlightColor(new Color(255, 255, 150));

View file

@ -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<String> 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" +
"<FONT SIZE='7'><B>PROTOTYPE</B></FONT> - This feature is an example prototype " +
"and contains many known rendering issues that cause some widgets to be " +
"<U>unreadable</U>.");
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();
}
}

View file

@ -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<String> 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<String> getAllThemeNames() {
Set<GTheme> allThemes = Gui.getAllThemes();
List<String> 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<GTheme> 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();
}
}

View file

@ -17,6 +17,8 @@ package ghidra.app.util;
import java.awt.Color; import java.awt.Color;
import docking.theme.GColor;
/** /**
* Miscellaneous defined constants * Miscellaneous defined constants
* *
@ -94,11 +96,13 @@ public interface PluginConstants {
/** /**
* Color for highlighting for searches. * 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 * Default highlight color used when something to highlight is at the current
* address. * 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");
} }

View file

@ -148,17 +148,17 @@ public class SetEquateDialog extends DialogComponentProvider {
int refCount = eqRowObject.getRefCount(); int refCount = eqRowObject.getRefCount();
if (refCount > 0) { if (refCount > 0) {
if (eqRowObject.getEntryName().contains(EquateManager.ERROR_TAG)) { 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 { else {
Equate e = eqRowObject.getEquate(); Equate e = eqRowObject.getEquate();
if (e != null && !e.isEnumBased()) { if (e != null && !e.isEnumBased()) {
c.setForeground(isSelected ? Color.WHITE : Color.BLUE.brighter()); c.setForeground(isSelected ? this.SELECTED_CELL_COLOR : this.EQUATE_COLOR);
} }
} }
} }
else { else {
c.setForeground(isSelected ? Color.WHITE : Color.GRAY.darker()); c.setForeground(isSelected ? this.SELECTED_CELL_COLOR : this.SUGGESTION_COLOR);
} }
return c; return c;
} }

View file

@ -20,6 +20,7 @@ import java.math.BigInteger;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import docking.theme.GColor;
import docking.widgets.fieldpanel.support.BackgroundColorModel; import docking.widgets.fieldpanel.support.BackgroundColorModel;
import ghidra.app.util.viewer.util.AddressIndexMap; import ghidra.app.util.viewer.util.AddressIndexMap;
import ghidra.framework.model.DomainObjectChangedEvent; import ghidra.framework.model.DomainObjectChangedEvent;
@ -40,7 +41,7 @@ public class PropertyBasedBackgroundColorModel
public static final String COLOR_PROPERTY_NAME = "LISTING_COLOR"; public static final String COLOR_PROPERTY_NAME = "LISTING_COLOR";
private IntRangeMap colorMap; private IntRangeMap colorMap;
private AddressIndexMap indexMap; private AddressIndexMap indexMap;
private Color defaultBackgroundColor = Color.WHITE; private Color defaultBackgroundColor = new GColor("color.bg.listing");
private Map<Integer, Color> colorCache = new HashMap<>(); private Map<Integer, Color> colorCache = new HashMap<>();
private Program program; private Program program;
private boolean enabled = false; private boolean enabled = false;

View file

@ -27,6 +27,7 @@ import java.util.stream.IntStream;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.Border; import javax.swing.border.Border;
import docking.theme.GColor;
import docking.widgets.checkbox.GCheckBox; import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GComboBox; import docking.widgets.combobox.GComboBox;
import docking.widgets.fieldpanel.*; import docking.widgets.fieldpanel.*;
@ -43,95 +44,150 @@ import ghidra.util.SystemUtilities;
*/ */
public class OptionsGui extends JPanel { public class OptionsGui extends JPanel {
private static final long serialVersionUID = 1L; 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 Highlight[] NO_HIGHLIGHTS = new Highlight[0];
private static final HighlightFactory hlFactory = private static final HighlightFactory hlFactory =
(field, text, cursorTextOffset) -> NO_HIGHLIGHTS; (field, text, cursorTextOffset) -> NO_HIGHLIGHTS;
public static final ScreenElement BACKGROUND =
new ScreenElement("Background", new GColor("color.bg.listing"));
public static final ScreenElement COMMENT_AUTO = public static final ScreenElement COMMENT_AUTO =
new ScreenElement("Comment, Automatic", Color.LIGHT_GRAY); new ScreenElement("Comment, Automatic", new GColor("color.fg.listing.comment.auto"));
public static final ScreenElement ADDRESS = new ScreenElement("Address", Color.BLACK); public static final ScreenElement ADDRESS =
public static final ScreenElement BACKGROUND = new ScreenElement("Background", Color.WHITE); new ScreenElement("Address", new GColor("color.fg.listing.address"));
public static final ScreenElement BAD_REF_ADDR = public static final ScreenElement BAD_REF_ADDR =
new ScreenElement("Bad Reference Address", Color.RED); new ScreenElement("Bad Reference Address", new GColor("color.fg.listing.ref.bad"));
public static final ScreenElement BYTES = new ScreenElement("Bytes", Color.BLUE);
public static final ScreenElement CONSTANT = new ScreenElement("Constant", BLUE_GREEN); public static final ScreenElement BYTES =
public static final ScreenElement LABELS_UNREFD = new ScreenElement("Bytes", new GColor("color.fg.listing.bytes"));
new ScreenElement("Labels, Unreferenced", Color.BLACK);
public static final ScreenElement ENTRY_POINT = new ScreenElement("Entry Point", Color.MAGENTA); public static final ScreenElement CONSTANT =
public static final ScreenElement COMMENT_EOL = new ScreenElement("Constant", new GColor("color.fg.listing.constant"));
new ScreenElement("Comment, EOL", "EOL Comment", Color.BLUE);
public static final ScreenElement EXT_REF_RESOLVED = public static final ScreenElement LABELS_UNREFD = new ScreenElement("Labels, Unreferenced",
new ScreenElement("External Reference, Resolved", Color.CYAN.darker().darker()); new GColor("color.fg.listing.label.unreferenced"));
public static final ScreenElement FIELD_NAME = new ScreenElement("Field Name", Color.BLACK);
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 = public static final ScreenElement FUN_CALL_FIXUP =
new ScreenElement("Function Call-Fixup", new Color(255, 0, 204)); new ScreenElement("Function Call-Fixup", new GColor("color.fg.listing.function.callfixup"));
public static final ScreenElement FUN_NAME = new ScreenElement("Function Name", Color.BLUE);
public static final ScreenElement FUN_NAME =
new ScreenElement("Function Name", new GColor("color.fg.listing.function.name"));
public static final ScreenElement FUN_PARAMS = public static final ScreenElement FUN_PARAMS =
new ScreenElement("Function Parameters", Color.BLACK); new ScreenElement("Function Parameters", new GColor("color.fg.listing.function.param"));
public static final ScreenElement FUN_TAG = new ScreenElement("Function Tag", DARK_RED);
public static final ScreenElement FUN_AUTO_PARAMS = public static final ScreenElement FUN_TAG =
new ScreenElement("Function Auto-Parameters", Color.GRAY); new ScreenElement("Function Tag", new GColor("color.fg.listing.function.tag"));
public static final ScreenElement FUN_RET_TYPE =
new ScreenElement("Function Return Type", Color.BLACK); 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 = public static final ScreenElement COMMENT_REPEATABLE =
new ScreenElement("Comment, Repeatable", DARK_ORANGE); new ScreenElement("Comment, Repeatable", new GColor("color.fg.listing.comment.repeatable"));
public static final ScreenElement COMMENT_REF_REPEAT =
new ScreenElement("Comment, Referenced Repeatable", new Color(190, 190, 255)); public static final ScreenElement COMMENT_REF_REPEAT = new ScreenElement(
public static final ScreenElement LABELS_LOCAL = new ScreenElement("Labels, Local", BLUE_GREEN); "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 = public static final ScreenElement MNEMONIC_OVERRIDE =
new ScreenElement("Mnemonic, Override", new Color(255, 0, 204)); new ScreenElement("Mnemonic, Override", new GColor("color.fg.listing.mnemonic.override"));
public static final ScreenElement MNEMONIC = new ScreenElement("Mnemonic", DARK_BLUE);
public static final ScreenElement UNIMPL = public static final ScreenElement MNEMONIC =
new ScreenElement("Unimplemented Mnemonic", Color.RED); new ScreenElement("Mnemonic", new GColor("color.fg.listing.mnemonic"));
public static final ScreenElement FLOW_ARROW_NON_ACTIVE =
new ScreenElement("Flow Arrow, Not Active", new Color(160, 160, 160)); 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 = public static final ScreenElement FLOW_ARROW_ACTIVE =
new ScreenElement("Flow Arrow, Active", Color.BLACK); 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 Color(0, 200, 0)); 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 = public static final ScreenElement LABELS_NON_PRIMARY =
new ScreenElement("Labels, Non-primary", YELLOW_ORANGE); 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", Color.GRAY); public static final ScreenElement COMMENT_PLATE = new ScreenElement("Comment, Plate",
public static final ScreenElement COMMENT_POST = "Plate Comment", new GColor("color.fg.listing.comment.plate"));
new ScreenElement("Comment, Post", "Post-Comment", Color.BLUE);
public static final ScreenElement COMMENT_PRE = public static final ScreenElement COMMENT_POST = new ScreenElement("Comment, Post",
new ScreenElement("Comment, Pre", "Pre-Comment", DEEP_PURPLE); "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 = public static final ScreenElement LABELS_PRIMARY =
new ScreenElement("Labels, Primary", DARK_BLUE); new ScreenElement("Labels, Primary", new GColor("color.fg.listing.label.primary"));
public static final ScreenElement SEPARATOR = new ScreenElement("Separator", Color.BLACK);
public static final ScreenElement VARIABLE = new ScreenElement("Variable", PURPLE); public static final ScreenElement SEPARATOR =
public static final ScreenElement PARAMETER_CUSTOM = new ScreenElement("Separator", new GColor("color.fg.listing.separator"));
new ScreenElement("Parameter, Custom Storage", DARK_PURPLE);
public static final ScreenElement PARAMETER_DYNAMIC = public static final ScreenElement VARIABLE =
new ScreenElement("Parameter, Dynamic Storage", DARK_CYAN); new ScreenElement("Variable", new GColor("color.fg.listing.variable"));
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 PARAMETER_CUSTOM = new ScreenElement(
public static final ScreenElement XREF_OFFCUT = new ScreenElement("XRef, Offcut", Color.GRAY); "Parameter, Custom Storage", new GColor("color.fg.listing.function.param.custom"));
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 PARAMETER_DYNAMIC = new ScreenElement(
public static final ScreenElement XREF_OTHER = new ScreenElement("XRef Other", Color.BLACK); "Parameter, Dynamic Storage", new GColor("color.fg.listing.function.param.dynamic"));
public static final ScreenElement REGISTERS = new ScreenElement("Registers", YELLOW_ORANGE);
public static final ScreenElement UNDERLINE = new ScreenElement("Underline", PALE_BLUE); 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 = 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 = 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 = 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 = 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, static ScreenElement[] elements = { ADDRESS, BACKGROUND, BAD_REF_ADDR, BYTES, COMMENT_AUTO,
COMMENT_EOL, COMMENT_PLATE, COMMENT_POST, COMMENT_PRE, COMMENT_REPEATABLE, COMMENT_EOL, COMMENT_PLATE, COMMENT_POST, COMMENT_PRE, COMMENT_REPEATABLE,

View file

@ -15,16 +15,12 @@
*/ */
package ghidra.framework; package ghidra.framework;
import java.awt.Taskbar;
import java.awt.Toolkit;
import java.lang.reflect.Field;
import docking.DockingErrorDisplay; import docking.DockingErrorDisplay;
import docking.DockingWindowManager; import docking.DockingWindowManager;
import docking.framework.ApplicationInformationDisplayFactory; import docking.framework.ApplicationInformationDisplayFactory;
import docking.framework.SplashScreen; import docking.framework.SplashScreen;
import docking.theme.Gui;
import docking.widgets.PopupKeyStorePasswordProvider; import docking.widgets.PopupKeyStorePasswordProvider;
import ghidra.docking.util.DockingWindowsLookAndFeelUtils;
import ghidra.formats.gfilesystem.crypto.CryptoProviders; import ghidra.formats.gfilesystem.crypto.CryptoProviders;
import ghidra.formats.gfilesystem.crypto.PopupGUIPasswordProvider; import ghidra.formats.gfilesystem.crypto.PopupGUIPasswordProvider;
import ghidra.framework.main.GhidraApplicationInformationDisplayFactory; import ghidra.framework.main.GhidraApplicationInformationDisplayFactory;
@ -46,10 +42,7 @@ public class GhidraApplicationConfiguration extends HeadlessGhidraApplicationCon
@Override @Override
protected void initializeApplication() { protected void initializeApplication() {
Gui.initialize();
DockingWindowsLookAndFeelUtils.loadFromPreferences();
platformSpecificFixups();
if (showSplashScreen) { if (showSplashScreen) {
showUserAgreement(); showUserAgreement();
@ -63,17 +56,6 @@ public class GhidraApplicationConfiguration extends HeadlessGhidraApplicationCon
CryptoProviders.getInstance().registerCryptoProvider(new PopupGUIPasswordProvider()); 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() { private static void showUserAgreement() {
String value = Preferences.getProperty(USER_AGREEMENT_PROPERTY_NAME); String value = Preferences.getProperty(USER_AGREEMENT_PROPERTY_NAME);
if ("ACCEPT".equals(value)) { if ("ACCEPT".equals(value)) {

View file

@ -15,13 +15,13 @@
*/ */
package ghidra.framework.main; package ghidra.framework.main;
import java.awt.Color;
import java.awt.Font; import java.awt.Font;
import java.util.LinkedList; import java.util.LinkedList;
import javax.swing.JTextPane; import javax.swing.JTextPane;
import javax.swing.text.*; import javax.swing.text.*;
import docking.theme.GColor;
import ghidra.framework.options.*; import ghidra.framework.options.*;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -113,7 +113,8 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener
private void updateFromOptions(Options options) { private void updateFromOptions(Options options) {
int newLimit = options.getInt(MAXIMUM_CHARACTERS_OPTION_NAME, DEFAULT_MAXIMUM_CHARS); 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); setMaximumCharacterLimit(newLimit);
} }
@ -141,7 +142,7 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener
private void doAddMessage(MessageWrapper newMessage) { private void doAddMessage(MessageWrapper newMessage) {
synchronized (messageList) { synchronized (messageList) {
if ( !messageList.isEmpty() ) { if (!messageList.isEmpty()) {
MessageWrapper lastMessage = messageList.getLast(); MessageWrapper lastMessage = messageList.getLast();
if (lastMessage.merge(newMessage)) { if (lastMessage.merge(newMessage)) {
return; return;
@ -211,7 +212,8 @@ public class ConsoleTextPane extends JTextPane implements OptionsChangeListener
outputAttributeSet.addAttribute(StyleConstants.FontSize, font.getSize()); outputAttributeSet.addAttribute(StyleConstants.FontSize, font.getSize());
outputAttributeSet.addAttribute(StyleConstants.Italic, font.isItalic()); outputAttributeSet.addAttribute(StyleConstants.Italic, font.isItalic());
outputAttributeSet.addAttribute(StyleConstants.Bold, font.isBold()); 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 = new SimpleAttributeSet();
errorAttributeSet.addAttribute(CUSTOM_ATTRIBUTE_KEY, ERROR_ATTRIBUTE_VALUE); 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.FontSize, font.getSize());
errorAttributeSet.addAttribute(StyleConstants.Italic, font.isItalic()); errorAttributeSet.addAttribute(StyleConstants.Italic, font.isItalic());
errorAttributeSet.addAttribute(StyleConstants.Bold, font.isBold()); 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() { private void doUpdate() {

View file

@ -25,6 +25,7 @@ import javax.swing.event.HyperlinkEvent;
import javax.swing.text.View; import javax.swing.text.View;
import docking.DockingUtils; import docking.DockingUtils;
import docking.theme.GColor;
import docking.widgets.*; import docking.widgets.*;
import docking.widgets.label.*; import docking.widgets.label.*;
import generic.util.WindowUtilities; import generic.util.WindowUtilities;
@ -58,7 +59,7 @@ class InfoPanel extends JPanel {
InfoPanel() { InfoPanel() {
getAboutInfo(); getAboutInfo();
bgColor = new Color(243, 250, 255); bgColor = new GColor("color.bg.splash.infopanel");
create(); create();
} }
@ -174,7 +175,7 @@ class InfoPanel extends JPanel {
Font font = versionLabel.getFont(); Font font = versionLabel.getFont();
font = font.deriveFont(14f).deriveFont(Font.BOLD); font = font.deriveFont(14f).deriveFont(Font.BOLD);
versionLabel.setFont(font); versionLabel.setFont(font);
versionLabel.setForeground(Color.BLACK); versionLabel.setForeground(new GColor("color.fg.infopanel.version"));
return versionLabel; return versionLabel;
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,25 +15,30 @@
*/ */
package ghidra.util.table; 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.Color;
import java.awt.Font; import java.awt.Font;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.table.TableModel; import javax.swing.table.TableModel;
import docking.theme.GColor;
import docking.widgets.table.GTableCellRenderer; 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 { public class GhidraTableCellRenderer extends GTableCellRenderer {
// Defaults as defined by OptionsGui class - would be nice to use the tool options // 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 BAD_REF_ADDR_COLOR = new GColor("color.fg.listing.ref.bad");
private static final Color EXT_REF_RESOLVED_COLOR = Color.CYAN.darker().darker(); 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() { public GhidraTableCellRenderer() {
// default constructor // default constructor

View file

@ -3,6 +3,7 @@
##MODULE IP: Oxygen Icons - LGPL 3.0 ##MODULE IP: Oxygen Icons - LGPL 3.0
Module.manifest||GHIDRA||||END| Module.manifest||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||reviewed||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/TOC_Source.xml||GHIDRA||reviewed||END|
src/main/help/help/shared/arrow.gif||GHIDRA||reviewed||END| src/main/help/help/shared/arrow.gif||GHIDRA||reviewed||END|
src/main/help/help/shared/close16.gif||GHIDRA||reviewed||END| src/main/help/help/shared/close16.gif||GHIDRA||reviewed||END|

View file

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

View file

@ -25,6 +25,7 @@ import java.util.List;
import javax.swing.JComponent; import javax.swing.JComponent;
import docking.action.ToggleDockingAction; import docking.action.ToggleDockingAction;
import docking.theme.GColor;
import ghidra.GhidraOptions; import ghidra.GhidraOptions;
import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES; import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES;
import ghidra.app.plugin.core.format.*; 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 Font DEFAULT_FONT = new Font("Monospaced", Font.PLAIN, 12);
static final int DEFAULT_BYTES_PER_LINE = 16; 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; //@formatter:off
static final Color DEFAULT_CURRENT_CURSOR_COLOR = Color.magenta.brighter(); static final String FG = "byteviewer.color.fg";
static final Color DEFAULT_CURSOR_COLOR = Color.black; static final String CURSOR = "byteviewer.color.cursor";
static final Color DEFAULT_NONFOCUS_CURSOR_COLOR = Color.darkGray; 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; private static final Color DEFAULT_CURSOR_LINE_COLOR = GhidraOptions.DEFAULT_CURSOR_LINE_COLOR;
static final String DEFAULT_INDEX_NAME = "Addresses"; static final String DEFAULT_INDEX_NAME = "Addresses";
@ -101,8 +108,7 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
private Map<String, Class<? extends DataFormatModel>> dataFormatModelClassMap; private Map<String, Class<? extends DataFormatModel>> dataFormatModelClassMap;
protected ByteViewerComponentProvider(PluginTool tool, AbstractByteViewerPlugin<?> plugin, protected ByteViewerComponentProvider(PluginTool tool, AbstractByteViewerPlugin<?> plugin,
String name, String name, Class<?> contextType) {
Class<?> contextType) {
super(tool, name, plugin.getName(), contextType); super(tool, name, plugin.getName(), contextType);
this.plugin = plugin; this.plugin = plugin;
@ -265,7 +271,8 @@ public abstract class ByteViewerComponentProvider extends ComponentProviderAdapt
CURSOR_HIGHLIGHT_BUTTON_NAME, GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES.MIDDLE); CURSOR_HIGHLIGHT_BUTTON_NAME, GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES.MIDDLE);
panel.setHighlightButton(mouseButton.getMouseEventID()); 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); opt.addOptionsChangeListener(this);
} }

View file

@ -23,6 +23,7 @@ import java.util.List;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.*; import javax.swing.event.*;
import docking.theme.GColor;
import docking.widgets.fieldpanel.*; import docking.widgets.fieldpanel.*;
import docking.widgets.fieldpanel.field.EmptyTextField; import docking.widgets.fieldpanel.field.EmptyTextField;
import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.field.Field;
@ -259,8 +260,8 @@ public class ByteViewerPanel extends JPanel
String start = blocks[0].getLocationRepresentation(BigInteger.ZERO); String start = blocks[0].getLocationRepresentation(BigInteger.ZERO);
startField.setText(start); startField.setText(start);
ByteBlock lastBlock = blocks[blocks.length - 1]; ByteBlock lastBlock = blocks[blocks.length - 1];
endField.setText(lastBlock.getLocationRepresentation( endField.setText(lastBlock
lastBlock.getLength().subtract(BigInteger.ONE))); .getLocationRepresentation(lastBlock.getLength().subtract(BigInteger.ONE)));
indexPanelWidth = getIndexPanelWidth(blocks); indexPanelWidth = getIndexPanelWidth(blocks);
int center = indexPanelWidth / 2; int center = indexPanelWidth / 2;
@ -833,7 +834,8 @@ public class ByteViewerPanel extends JPanel
columnHeader.addColumn(ByteViewerComponentProvider.DEFAULT_INDEX_NAME, indexPanel); columnHeader.addColumn(ByteViewerComponentProvider.DEFAULT_INDEX_NAME, indexPanel);
scrollp.setColumnHeaderComp(columnHeader); scrollp.setColumnHeaderComp(columnHeader);
compPanel.setBackground(Color.WHITE);
compPanel.setBackground(new GColor("color.bg.byteviewer"));
statusPanel = createStatusPanel(); statusPanel = createStatusPanel();
add(scrollp, BorderLayout.CENTER); add(scrollp, BorderLayout.CENTER);

View file

@ -24,6 +24,7 @@ import java.awt.Font;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.io.IOException; import java.io.IOException;
import docking.theme.GColor;
import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES; import ghidra.GhidraOptions.CURSOR_MOUSE_BUTTON_NAMES;
import ghidra.app.util.HelpTopics; import ghidra.app.util.HelpTopics;
import ghidra.framework.options.Options; 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 final static IntegerFormatEnum INTEGERFORMAT_OPTIONDEFAULT = IntegerFormatEnum.BestFit; // Must match PrintLanguage::resetDefaultsInternal
private IntegerFormatEnum integerFormat; 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 Color middleMouseHighlightColor;
private int middleMouseHighlightButton = MouseEvent.BUTTON2; private int middleMouseHighlightButton = MouseEvent.BUTTON2;
private final static String HIGHLIGHT_CURRENT_VARIABLE_MSG = private final static String HIGHLIGHT_CURRENT_VARIABLE_MSG ="Display.Color for Current Variable Highlight";
"Display.Color for Current Variable Highlight"; private final static Color HIGHLIGHT_CURRENT_VARIABLE_DEF = new GColor("color.bg.decompiler.current-variable");
private final static Color HIGHLIGHT_CURRENT_VARIABLE_DEF = new Color(255, 255, 0, 128);
private Color currentVariableHighlightColor; private Color currentVariableHighlightColor;
private final static String HIGHLIGHT_KEYWORD_MSG = "Display.Color for Keywords"; 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 Color keywordColor;
private final static String HIGHLIGHT_FUNCTION_MSG = "Display.Color for Function names"; 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 Color functionColor;
private final static String HIGHLIGHT_COMMENT_MSG = "Display.Color for Comments"; 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 Color commentColor;
private final static String HIGHLIGHT_VARIABLE_MSG = "Display.Color for Variables"; 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 Color variableColor;
private final static String HIGHLIGHT_CONST_MSG = "Display.Color for Constants"; 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 Color constantColor;
private final static String HIGHLIGHT_TYPE_MSG = "Display.Color for Types"; 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 Color typeColor;
private final static String HIGHLIGHT_PARAMETER_MSG = "Display.Color for Parameters"; 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 Color parameterColor;
private final static String HIGHLIGHT_GLOBAL_MSG = "Display.Color for Globals"; 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 Color globalColor;
private final static String HIGHLIGHT_SPECIAL_MSG = "Display.Color for Special"; private final static String HIGHLIGHT_SPECIAL_MSG = "Display.Color for Special";
private final static Color HIGHLIGHT_SPECIAL_DEF = Color.decode("0xCC0033"); private final static Color HIGHLIGHT_SPECIAL_DEF = Color.decode("0xCC0033");
private Color specialColor; private Color specialColor;
private final static String HIGHLIGHT_DEFAULT_MSG = "Display.Color Default"; 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; private Color defaultColor;
//@formatter:on
private static final String CODE_VIEWER_BACKGROUND_COLOR_MSG = "Display.Background Color"; 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 Color codeViewerBackgroundColor;
private static final String SEARCH_HIGHLIGHT_MSG = private static final String SEARCH_HIGHLIGHT_MSG =

View file

@ -21,6 +21,7 @@ import java.util.Map.Entry;
import org.jdom.Element; import org.jdom.Element;
import docking.theme.GColor;
import edu.uci.ics.jung.algorithms.layout.Layout; import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.picking.PickedState; import edu.uci.ics.jung.visualization.picking.PickedState;
@ -45,9 +46,12 @@ import ghidra.util.UndefinedFunction;
public class FGComponent extends GraphComponent<FGVertex, FGEdge, FunctionGraph> { public class FGComponent extends GraphComponent<FGVertex, FGEdge, FunctionGraph> {
private static final Color END_COLOR = new Color(255, 127, 127); //@formatter:off
private static final Color START_COLOR = new Color(127, 255, 127); private static final Color PICKED_COLOR = new GColor("color.bg.functiongraph.vertex.picked");
private static final Color UNDEFINED_FUNCTION_COLOR = new Color(220, 220, 220); 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 * 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<FGVertex, FGEdge, FunctionGraph>
// for background colors when we are zoomed to far to render the listing // for background colors when we are zoomed to far to render the listing
PickedState<FGVertex> pickedVertexState = viewer.getPickedVertexState(); PickedState<FGVertex> pickedVertexState = viewer.getPickedVertexState();
renderContext.setVertexFillPaintTransformer(new FGVertexPickableBackgroundPaintTransformer( renderContext.setVertexFillPaintTransformer(new FGVertexPickableBackgroundPaintTransformer(
pickedVertexState, Color.YELLOW, START_COLOR, END_COLOR)); pickedVertexState, PICKED_COLOR, START_COLOR, END_COLOR));
// edge label rendering // edge label rendering
com.google.common.base.Function<FGEdge, String> edgeLabelTransformer = e -> e.getLabel(); com.google.common.base.Function<FGEdge, String> edgeLabelTransformer = e -> e.getLabel();
@ -233,7 +237,7 @@ public class FGComponent extends GraphComponent<FGVertex, FGEdge, FunctionGraph>
viewer.setBackground(UNDEFINED_FUNCTION_COLOR); viewer.setBackground(UNDEFINED_FUNCTION_COLOR);
} }
else { else {
viewer.setBackground(Color.WHITE); viewer.setBackground(new GColor("color.bg.functiongraph"));
} }
} }
@ -257,7 +261,7 @@ public class FGComponent extends GraphComponent<FGVertex, FGEdge, FunctionGraph>
PickedState<FGVertex> pickedVertexState = viewer.getPickedVertexState(); PickedState<FGVertex> pickedVertexState = viewer.getPickedVertexState();
renderContext.setVertexFillPaintTransformer(new FGVertexPickableBackgroundPaintTransformer( renderContext.setVertexFillPaintTransformer(new FGVertexPickableBackgroundPaintTransformer(
pickedVertexState, Color.YELLOW, START_COLOR, END_COLOR)); pickedVertexState, PICKED_COLOR, START_COLOR, END_COLOR));
viewer.setGraphOptions(vgOptions); viewer.setGraphOptions(vgOptions);

View file

@ -17,9 +17,11 @@ package ghidra.app.plugin.core.functiongraph.graph.jung.transformer;
import java.awt.Color; import java.awt.Color;
import java.awt.Paint; import java.awt.Paint;
import java.util.Objects;
import com.google.common.base.Function; import com.google.common.base.Function;
import docking.theme.Gui;
import edu.uci.ics.jung.visualization.picking.PickedInfo; import edu.uci.ics.jung.visualization.picking.PickedInfo;
import ghidra.app.plugin.core.functiongraph.graph.FGVertexType; import ghidra.app.plugin.core.functiongraph.graph.FGVertexType;
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex; import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
@ -31,8 +33,8 @@ public class FGVertexPickableBackgroundPaintTransformer implements Function<FGVe
private final Color pickedColor; private final Color pickedColor;
private final Color entryColor; private final Color entryColor;
private final Color exitColor; private final Color exitColor;
private final Color pickedStartColor; private final Color pickedEntryColor;
private final Color pickedEndColor; private final Color pickedExitColor;
private static Color mix(Color c1, Color c2) { private static Color mix(Color c1, Color c2) {
return new Color((c1.getRed() + c2.getRed()) / 2, (c1.getGreen() + c2.getGreen()) / 2, return new Color((c1.getRed() + c2.getRed()) / 2, (c1.getGreen() + c2.getGreen()) / 2,
@ -42,15 +44,12 @@ public class FGVertexPickableBackgroundPaintTransformer implements Function<FGVe
public FGVertexPickableBackgroundPaintTransformer(PickedInfo<FGVertex> info, Color pickedColor, public FGVertexPickableBackgroundPaintTransformer(PickedInfo<FGVertex> info, Color pickedColor,
Color startColor, Color endColor) { Color startColor, Color endColor) {
if (info == null) { this.info = Objects.requireNonNull(info);
throw new IllegalArgumentException("PickedInfo instance must be non-null");
}
this.info = info;
this.pickedColor = pickedColor; this.pickedColor = pickedColor;
this.entryColor = startColor; this.entryColor = startColor;
this.exitColor = endColor; this.exitColor = endColor;
this.pickedStartColor = mix(pickedColor, startColor); this.pickedEntryColor = mix(pickedColor, startColor);
this.pickedEndColor = mix(pickedColor, endColor); this.pickedExitColor = mix(pickedColor, endColor);
} }
@Override @Override
@ -69,20 +68,31 @@ public class FGVertexPickableBackgroundPaintTransformer implements Function<FGVe
if (info.isPicked(v)) { if (info.isPicked(v)) {
if (v.isDefaultBackgroundColor()) { if (v.isDefaultBackgroundColor()) {
if (vertexType.isEntry()) { if (vertexType.isEntry()) {
return pickedStartColor; return pickedEntryColor;
} }
if (vertexType.isExit()) { if (vertexType.isExit()) {
return pickedEndColor; return pickedExitColor;
} }
return pickedColor; return pickedColor;
} }
if (vertexType.isEntry()) { if (vertexType.isEntry()) {
return pickedStartColor.darker(); // this is a vertex that has a non-default, user-defined color; making the value
// darker() is meant to signal that the 'picked entry color' is on top of a vertex
// that has another color underneath
return Gui.darker(pickedEntryColor);
} }
if (vertexType.isExit()) { if (vertexType.isExit()) {
return pickedEndColor.darker(); // this is a vertex that has a non-default, user-defined color; making the value
// darker() is meant to signal that the 'picked exit color' is on top of a vertex
// that has another color underneath
return Gui.darker(pickedExitColor);
} }
return pickedColor.darker();
// this is a vertex that has a non-default, user-defined color; making the value
// darker() is meant to signal that the 'picked color' is on top of a vertex that has
// another color underneath
Color mixed = mix(pickedColor, backgroundColor);
return Gui.darker(mixed);
} }
if (vertexType.isEntry()) { if (vertexType.isEntry()) {

View file

@ -21,6 +21,7 @@ import java.awt.geom.Point2D;
import javax.swing.*; import javax.swing.*;
import docking.theme.GThemeDefaults.Colors;
import edu.uci.ics.jung.visualization.VisualizationViewer; import edu.uci.ics.jung.visualization.VisualizationViewer;
import ghidra.app.plugin.core.functiongraph.graph.FGEdge; import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
import ghidra.app.plugin.core.functiongraph.mvc.FGController; import ghidra.app.plugin.core.functiongraph.mvc.FGController;
@ -133,7 +134,7 @@ public abstract class AbstractGraphComponentPanel extends JPanel {
abstract ProgramLocation getProgramLocation(); abstract ProgramLocation getProgramLocation();
boolean isDefaultBackgroundColor() { boolean isDefaultBackgroundColor() {
return getBackgroundColor().equals(Color.WHITE); return getBackgroundColor().equals(Colors.BACKGROUND);
} }
boolean isHeaderClick(Component clickedComponent) { boolean isHeaderClick(Component clickedComponent) {

View file

@ -31,6 +31,7 @@ import docking.ActionContext;
import docking.GenericHeader; import docking.GenericHeader;
import docking.action.DockingAction; import docking.action.DockingAction;
import docking.action.ToolBarData; import docking.action.ToolBarData;
import docking.theme.GThemeDefaults.Colors;
import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.FieldPanel;
import docking.widgets.fieldpanel.Layout; import docking.widgets.fieldpanel.Layout;
import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.field.Field;
@ -204,7 +205,6 @@ public class ListingGraphComponentPanel extends AbstractGraphComponentPanel {
previewListingPanel = new FGVertexListingPanel(controller, previewListingPanel = new FGVertexListingPanel(controller,
getFormatManager(useFullSizeTooltip), program, addressSet); getFormatManager(useFullSizeTooltip), program, addressSet);
previewListingPanel.setTextBackgroundColor(FGVertex.TOOLTIP_BACKGROUND_COLOR); previewListingPanel.setTextBackgroundColor(FGVertex.TOOLTIP_BACKGROUND_COLOR);
// previewListingPanel.getFieldPanel().setSelectionMode( FieldPanel.NO_SELECTION );
previewListingPanel.getFieldPanel().setCursorOn(false); previewListingPanel.getFieldPanel().setCursorOn(false);
// keep the tooltip window from getting too big; use an arbitrary, reasonable max // keep the tooltip window from getting too big; use an arbitrary, reasonable max
@ -224,7 +224,7 @@ public class ListingGraphComponentPanel extends AbstractGraphComponentPanel {
JPanel headerPanel = new JPanel(new BorderLayout()); JPanel headerPanel = new JPanel(new BorderLayout());
headerPanel.add(tooltipTitleLabel); headerPanel.add(tooltipTitleLabel);
headerPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK)); headerPanel.setBorder(BorderFactory.createLineBorder(Colors.Java.BORDER));
panel.add(headerPanel, BorderLayout.NORTH); panel.add(headerPanel, BorderLayout.NORTH);
panel.add(previewListingPanel, BorderLayout.CENTER); panel.add(previewListingPanel, BorderLayout.CENTER);

View file

@ -19,6 +19,7 @@ import java.awt.Color;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import docking.theme.GColor;
import ghidra.app.plugin.core.functiongraph.FunctionGraphPlugin; import ghidra.app.plugin.core.functiongraph.FunctionGraphPlugin;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutOptions; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutOptions;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
@ -85,24 +86,21 @@ public class FunctionGraphOptions extends VisualGraphOptions {
"Signals that any user color changes to a group vertex will apply that same color to " + "Signals that any user color changes to a group vertex will apply that same color to " +
"all grouped vertices as well."; "all grouped vertices as well.";
public static final Color DEFAULT_VERTEX_BACKGROUND_COLOR = Color.WHITE;
public static final Color DEFAULT_GROUP_BACKGROUND_COLOR = new Color(226, 255, 155);
private static final Color HOVER_HIGHLIGHT_FALL_THROUGH_COLOR = new Color(255, 127, 127);
private static final Color HOVER_HIGHLIGHT_UNCONDITIONAL_COLOR = new Color(127, 127, 255);
private static final Color HOVER_HIGHLIGHT_CONDITIONAL_COLOR = Color.GREEN;
private Color defaultVertexBackgroundColor = DEFAULT_VERTEX_BACKGROUND_COLOR;
private boolean updateGroupColorsAutomatically = true; private boolean updateGroupColorsAutomatically = true;
private Color defaultGroupBackgroundColor = DEFAULT_GROUP_BACKGROUND_COLOR;
private Color fallthroughEdgeColor = Color.RED; //@formatter:off
private Color unconditionalJumpEdgeColor = Color.BLUE; public static final Color DEFAULT_GROUP_BACKGROUND_COLOR = new GColor("color.bg.functiongraph.vertex.group");
private Color conditionalJumpEdgeColor = Color.GREEN.darker().darker(); private Color defaultVertexBackgroundColor = new GColor("color.bg.functiongraph");
private Color defaultGroupBackgroundColor = new GColor("color.bg.functiongraph.vertex.group");
private Color fallthroughEdgeHighlightColor = HOVER_HIGHLIGHT_FALL_THROUGH_COLOR; private Color fallthroughEdgeColor = new GColor("color.bg.functiongraph.edge.fall-through");
private Color unconditionalJumpEdgeHighlightColor = HOVER_HIGHLIGHT_UNCONDITIONAL_COLOR; private Color conditionalJumpEdgeColor = new GColor("color.bg.functiongraph.edge.jump.conditional");
private Color conditionalJumpEdgeHighlightColor = HOVER_HIGHLIGHT_CONDITIONAL_COLOR; private Color unconditionalJumpEdgeColor = new GColor("color.bg.functiongraph.edge.jump.unconditional");
private Color fallthroughEdgeHighlightColor = new GColor("color.bg.functiongraph.edge.fall-through.highlight");
private Color conditionalJumpEdgeHighlightColor = new GColor("color.bg.functiongraph.edge.jump.conditional.highlight");
private Color unconditionalJumpEdgeHighlightColor = new GColor("color.bg.functiongraph.edge.jump.unconditional.highlight");
//@formatter:on
private boolean useFullSizeTooltip = false; private boolean useFullSizeTooltip = false;
@ -174,10 +172,10 @@ public class FunctionGraphOptions extends VisualGraphOptions {
options.registerOption(USE_CONDENSED_LAYOUT_KEY, useCondensedLayout(), options.registerOption(USE_CONDENSED_LAYOUT_KEY, useCondensedLayout(),
new HelpLocation(OWNER, "Layout_Compressing"), USE_CONDENSED_LAYOUT_DESCRIPTION); new HelpLocation(OWNER, "Layout_Compressing"), USE_CONDENSED_LAYOUT_DESCRIPTION);
options.registerOption(DEFAULT_VERTEX_BACKGROUND_COLOR_KEY, DEFAULT_VERTEX_BACKGROUND_COLOR, options.registerOption(DEFAULT_VERTEX_BACKGROUND_COLOR_KEY, defaultVertexBackgroundColor,
help, DEFAULT_VERTEX_BACKGROUND_COLOR_DESCRPTION); help, DEFAULT_VERTEX_BACKGROUND_COLOR_DESCRPTION);
options.registerOption(DEFAULT_GROUP_BACKGROUND_COLOR_KEY, DEFAULT_GROUP_BACKGROUND_COLOR, options.registerOption(DEFAULT_GROUP_BACKGROUND_COLOR_KEY, defaultGroupBackgroundColor,
help, DEFAULT_GROUP_BACKGROUND_COLOR_DESCRPTION); help, DEFAULT_GROUP_BACKGROUND_COLOR_DESCRPTION);
options.registerOption(UPDATE_GROUP_AND_UNGROUP_COLORS, updateGroupColorsAutomatically, options.registerOption(UPDATE_GROUP_AND_UNGROUP_COLORS, updateGroupColorsAutomatically,
@ -238,10 +236,10 @@ public class FunctionGraphOptions extends VisualGraphOptions {
useFullSizeTooltip = options.getBoolean(USE_FULL_SIZE_TOOLTIP_KEY, useFullSizeTooltip); useFullSizeTooltip = options.getBoolean(USE_FULL_SIZE_TOOLTIP_KEY, useFullSizeTooltip);
defaultVertexBackgroundColor = defaultVertexBackgroundColor =
options.getColor(DEFAULT_VERTEX_BACKGROUND_COLOR_KEY, DEFAULT_VERTEX_BACKGROUND_COLOR); options.getColor(DEFAULT_VERTEX_BACKGROUND_COLOR_KEY, defaultVertexBackgroundColor);
defaultGroupBackgroundColor = defaultGroupBackgroundColor =
options.getColor(DEFAULT_GROUP_BACKGROUND_COLOR_KEY, DEFAULT_GROUP_BACKGROUND_COLOR); options.getColor(DEFAULT_GROUP_BACKGROUND_COLOR_KEY, defaultGroupBackgroundColor);
updateGroupColorsAutomatically = updateGroupColorsAutomatically =
options.getBoolean(UPDATE_GROUP_AND_UNGROUP_COLORS, updateGroupColorsAutomatically); options.getBoolean(UPDATE_GROUP_AND_UNGROUP_COLORS, updateGroupColorsAutomatically);

View file

@ -23,6 +23,7 @@ import java.util.List;
import javax.swing.*; import javax.swing.*;
import docking.DialogComponentProvider; import docking.DialogComponentProvider;
import docking.theme.GThemeDefaults;
import docking.widgets.checkbox.GCheckBox; import docking.widgets.checkbox.GCheckBox;
import ghidra.feature.fid.db.FidFile; import ghidra.feature.fid.db.FidFile;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
@ -89,7 +90,7 @@ public class ActiveFidConfigureDialog extends DialogComponentProvider {
private Component buildCheckBoxPanel() { private Component buildCheckBoxPanel() {
JPanel panel = new JPanel(new VerticalLayout(5)); JPanel panel = new JPanel(new VerticalLayout(5));
panel.setOpaque(true); panel.setOpaque(true);
panel.setBackground(Color.WHITE); panel.setBackground(GThemeDefaults.Colors.BACKGROUND);
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
for (FidFile fidFile : fidFiles) { for (FidFile fidFile : fidFiles) {
GCheckBox checkbox = new GCheckBox(fidFile.getName(), fidFile.isActive()); GCheckBox checkbox = new GCheckBox(fidFile.getName(), fidFile.isActive());

View file

@ -1,6 +1,7 @@
##VERSION: 2.0 ##VERSION: 2.0
##MODULE IP: Oxygen Icons - LGPL 3.0 ##MODULE IP: Oxygen Icons - LGPL 3.0
Module.manifest||GHIDRA||||END| Module.manifest||GHIDRA||||END|
data/functioncall.theme.properties||GHIDRA||||END|
src/main/help/help/TOC_Source.xml||GHIDRA||||END| src/main/help/help/TOC_Source.xml||GHIDRA||||END|
src/main/help/help/shared/arrow.gif||GHIDRA||||END| src/main/help/help/shared/arrow.gif||GHIDRA||||END|
src/main/help/help/shared/close16.gif||GHIDRA||||END| src/main/help/help/shared/close16.gif||GHIDRA||||END|

View file

@ -0,0 +1,3 @@
[Defaults]
color.fcg.satellite.edge = rgba(0,0,0,0.1)

View file

@ -17,6 +17,7 @@ package functioncalls.graph.view;
import java.awt.Color; import java.awt.Color;
import docking.theme.GColor;
import edu.uci.ics.jung.visualization.RenderContext; import edu.uci.ics.jung.visualization.RenderContext;
import functioncalls.graph.*; import functioncalls.graph.*;
import functioncalls.graph.renderer.FcgEdgePaintTransformer; import functioncalls.graph.renderer.FcgEdgePaintTransformer;
@ -41,7 +42,7 @@ public class FcgComponent extends GraphComponent<FcgVertex, FcgEdge, FunctionCal
private Color lightGray = new Color(233, 233, 233); private Color lightGray = new Color(233, 233, 233);
// the satellite gets too cluttered, so wash out the edges // the satellite gets too cluttered, so wash out the edges
private Color washedOutBlack = new Color(0, 0, 0, 25); private Color washedOutBlack = new GColor("color.fcg.satellite.edge");
private FcgEdgePaintTransformer edgePaintTransformer = private FcgEdgePaintTransformer edgePaintTransformer =
new FcgEdgePaintTransformer(lightGreen, lightGray); new FcgEdgePaintTransformer(lightGreen, lightGray);

View file

@ -8,6 +8,7 @@
##MODULE IP: Tango Icons - Public Domain ##MODULE IP: Tango Icons - Public Domain
Module.manifest||GHIDRA||||END| Module.manifest||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END| data/ExtensionPoint.manifest||GHIDRA||||END|
data/graphservices.theme.properties||GHIDRA||||END|
src/main/help/help/TOC_Source.xml||GHIDRA||||END| src/main/help/help/TOC_Source.xml||GHIDRA||||END|
src/main/help/help/shared/arrow.gif||GHIDRA||||END| src/main/help/help/shared/arrow.gif||GHIDRA||||END|
src/main/help/help/shared/note.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END| src/main/help/help/shared/note.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|

View file

@ -0,0 +1,3 @@
[Defaults]
color.bg.graph = color.bg

View file

@ -50,6 +50,7 @@ import docking.action.builder.*;
import docking.menu.ActionState; import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction; import docking.menu.MultiStateDockingAction;
import docking.options.editor.OptionsDialog; import docking.options.editor.OptionsDialog;
import docking.theme.GColor;
import docking.widgets.EventTrigger; import docking.widgets.EventTrigger;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import generic.util.WindowUtilities; import generic.util.WindowUtilities;
@ -78,6 +79,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
private static final Dimension PREFERRED_VIEW_SIZE = new Dimension(1000, 1000); private static final Dimension PREFERRED_VIEW_SIZE = new Dimension(1000, 1000);
private static final Dimension PREFERRED_LAYOUT_SIZE = new Dimension(3000, 3000); private static final Dimension PREFERRED_LAYOUT_SIZE = new Dimension(3000, 3000);
private static Color BACKGROUND_COLOR = new GColor("color.bg.graph");
// layout algorithm categories // layout algorithm categories
static final String MIN_CROSS = "Hierarchical MinCross"; static final String MIN_CROSS = "Hierarchical MinCross";
static final String VERT_MIN_CROSS = "Vertical Hierarchical MinCross"; static final String VERT_MIN_CROSS = "Vertical Hierarchical MinCross";
@ -187,8 +190,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
if (graphDisplayProvider.getDefaultSatelliteState()) { if (graphDisplayProvider.getDefaultSatelliteState()) {
viewer.getComponent().add(satelliteViewer.getComponent()); viewer.getComponent().add(satelliteViewer.getComponent());
} }
layoutTransitionManager = layoutTransitionManager = new LayoutTransitionManager(viewer, this::isRoot, graphRenderer);
new LayoutTransitionManager(viewer, this::isRoot, graphRenderer);
viewer.getComponent().addComponentListener(new ComponentAdapter() { viewer.getComponent().addComponentListener(new ComponentAdapter() {
@Override @Override
@ -203,8 +205,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
} }
}); });
viewer.setInitialDimensionFunction(InitialDimensionFunction viewer.setInitialDimensionFunction(
.builder(viewer.getRenderContext().getVertexBoundsFunction()) InitialDimensionFunction.builder(viewer.getRenderContext().getVertexBoundsFunction())
.build()); .build());
createToolbarActions(); createToolbarActions();
createPopupActions(); createPopupActions();
@ -260,9 +262,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
} }
}; };
MutableTransformer transformer = viewer.getRenderContext() MutableTransformer transformer =
.getMultiLayerTransformer() viewer.getRenderContext().getMultiLayerTransformer().getTransformer(VIEW);
.getTransformer(VIEW);
MagnifyShapeTransformer shapeTransformer = MagnifyShapeTransformer.builder(lens) MagnifyShapeTransformer shapeTransformer = MagnifyShapeTransformer.builder(lens)
// this lens' delegate is the viewer's VIEW layer, abandoned above // this lens' delegate is the viewer's VIEW layer, abandoned above
@ -283,8 +284,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
viewer.removePostRenderPaintable(singleSelectedVertexPaintable); viewer.removePostRenderPaintable(singleSelectedVertexPaintable);
// for highlighting of multiple selected vertices // for highlighting of multiple selected vertices
this.multiSelectedVertexPaintable = this.multiSelectedVertexPaintable = MultiSelectedVertexPaintable.builder(viewer)
MultiSelectedVertexPaintable.builder(viewer)
.selectionStrokeMin(15.f) .selectionStrokeMin(15.f)
.selectionPaint(getSelectedVertexColor()) .selectionPaint(getSelectedVertexColor())
.useBounds(true) .useBounds(true)
@ -294,8 +294,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
.build(); .build();
// manages highlight painting of a single selected vertex // manages highlight painting of a single selected vertex
this.singleSelectedVertexPaintable = this.singleSelectedVertexPaintable = SingleSelectedVertexPaintable.builder(viewer)
SingleSelectedVertexPaintable.builder(viewer)
.selectionStrokeMin(4.f) .selectionStrokeMin(4.f)
.selectionPaint(getSelectedVertexColor()) .selectionPaint(getSelectedVertexColor())
.selectedVertexFunction(vs -> this.focusedVertex) .selectedVertexFunction(vs -> this.focusedVertex)
@ -352,8 +351,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
// create an icon button to reset the view transformations to identity (scaled to layout) // create an icon button to reset the view transformations to identity (scaled to layout)
new ActionBuilder("Reset View", ACTION_OWNER) new ActionBuilder("Reset View", ACTION_OWNER).description("Fit Graph to Window")
.description("Fit Graph to Window")
.toolBarIcon(DefaultDisplayGraphIcons.FIT_TO_WINDOW) .toolBarIcon(DefaultDisplayGraphIcons.FIT_TO_WINDOW)
.onAction(context -> centerAndScale()) .onAction(context -> centerAndScale())
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
@ -363,8 +361,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
ToggleDockingAction lensToggle = new ToggleActionBuilder("View Magnifier", ACTION_OWNER) ToggleDockingAction lensToggle = new ToggleActionBuilder("View Magnifier", ACTION_OWNER)
.description("Show View Magnifier") .description("Show View Magnifier")
.toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON) .toolBarIcon(DefaultDisplayGraphIcons.VIEW_MAGNIFIER_ICON)
.onAction(context -> magnifyViewSupport.activate( .onAction(context -> magnifyViewSupport
((AbstractButton) context.getSourceObject()).isSelected())) .activate(((AbstractButton) context.getSourceObject()).isSelected()))
.build(); .build();
magnifyViewSupport.addItemListener( magnifyViewSupport.addItemListener(
itemEvent -> lensToggle.setSelected(itemEvent.getStateChange() == ItemEvent.SELECTED)); itemEvent -> lensToggle.setSelected(itemEvent.getStateChange() == ItemEvent.SELECTED));
@ -388,40 +386,35 @@ public class DefaultGraphDisplay implements GraphDisplay {
} }
private void createPopupActions() { private void createPopupActions() {
new ActionBuilder("Select Vertex", ACTION_OWNER) new ActionBuilder("Select Vertex", ACTION_OWNER).popupMenuPath("Select Vertex")
.popupMenuPath("Select Vertex")
.popupMenuGroup("selection", "1") .popupMenuGroup("selection", "1")
.withContext(VertexGraphActionContext.class) .withContext(VertexGraphActionContext.class)
.enabledWhen(c -> !isSelected(c.getClickedVertex())) .enabledWhen(c -> !isSelected(c.getClickedVertex()))
.onAction(c -> viewer.getSelectedVertexState().select(c.getClickedVertex())) .onAction(c -> viewer.getSelectedVertexState().select(c.getClickedVertex()))
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
new ActionBuilder("Deselect Vertex", ACTION_OWNER) new ActionBuilder("Deselect Vertex", ACTION_OWNER).popupMenuPath("Deselect Vertex")
.popupMenuPath("Deselect Vertex")
.popupMenuGroup("selection", "2") .popupMenuGroup("selection", "2")
.withContext(VertexGraphActionContext.class) .withContext(VertexGraphActionContext.class)
.enabledWhen(c -> isSelected(c.getClickedVertex())) .enabledWhen(c -> isSelected(c.getClickedVertex()))
.onAction(c -> viewer.getSelectedVertexState().deselect(c.getClickedVertex())) .onAction(c -> viewer.getSelectedVertexState().deselect(c.getClickedVertex()))
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
new ActionBuilder("Select Edge", ACTION_OWNER) new ActionBuilder("Select Edge", ACTION_OWNER).popupMenuPath("Select Edge")
.popupMenuPath("Select Edge")
.popupMenuGroup("selection", "1") .popupMenuGroup("selection", "1")
.withContext(EdgeGraphActionContext.class) .withContext(EdgeGraphActionContext.class)
.enabledWhen(c -> !isSelected(c.getClickedEdge())) .enabledWhen(c -> !isSelected(c.getClickedEdge()))
.onAction(c -> selectEdge(c.getClickedEdge())) .onAction(c -> selectEdge(c.getClickedEdge()))
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
new ActionBuilder("Deselect Edge", ACTION_OWNER) new ActionBuilder("Deselect Edge", ACTION_OWNER).popupMenuPath("Deselect Edge")
.popupMenuPath("Deselect Edge")
.popupMenuGroup("selection", "2") .popupMenuGroup("selection", "2")
.withContext(EdgeGraphActionContext.class) .withContext(EdgeGraphActionContext.class)
.enabledWhen(c -> isSelected(c.getClickedEdge())) .enabledWhen(c -> isSelected(c.getClickedEdge()))
.onAction(c -> deselectEdge(c.getClickedEdge())) .onAction(c -> deselectEdge(c.getClickedEdge()))
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
new ActionBuilder("Edge Source", ACTION_OWNER) new ActionBuilder("Edge Source", ACTION_OWNER).popupMenuPath("Go To Edge Source")
.popupMenuPath("Go To Edge Source")
.popupMenuGroup("Go To") .popupMenuGroup("Go To")
.withContext(EdgeGraphActionContext.class) .withContext(EdgeGraphActionContext.class)
.onAction(c -> { .onAction(c -> {
@ -430,8 +423,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
}) })
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
new ActionBuilder("Edge Target", ACTION_OWNER) new ActionBuilder("Edge Target", ACTION_OWNER).popupMenuPath("Go To Edge Target")
.popupMenuPath("Go To Edge Target")
.popupMenuGroup("Go To") .popupMenuGroup("Go To")
.withContext(EdgeGraphActionContext.class) .withContext(EdgeGraphActionContext.class)
.onAction(c -> { .onAction(c -> {
@ -440,8 +432,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
}) })
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
hideSelectedAction = new ToggleActionBuilder("Hide Selected", ACTION_OWNER) hideSelectedAction =
.popupMenuPath("Hide Selected") new ToggleActionBuilder("Hide Selected", ACTION_OWNER).popupMenuPath("Hide Selected")
.popupMenuGroup("z", "1") .popupMenuGroup("z", "1")
.description("Toggles whether or not to show selected vertices and edges") .description("Toggles whether or not to show selected vertices and edges")
.onAction(c -> manageVertexDisplay()) .onAction(c -> manageVertexDisplay())
@ -454,8 +446,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
.onAction(c -> manageVertexDisplay()) .onAction(c -> manageVertexDisplay())
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
new ActionBuilder("Invert Selection", ACTION_OWNER) new ActionBuilder("Invert Selection", ACTION_OWNER).popupMenuPath("Invert Selection")
.popupMenuPath("Invert Selection")
.popupMenuGroup("z", "3") .popupMenuGroup("z", "3")
.description("Inverts the current selection") .description("Inverts the current selection")
.onAction(c -> invertSelection()) .onAction(c -> invertSelection())
@ -493,8 +484,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
.onAction(c -> growSelection(getAllComponentVerticesFromSelected())) .onAction(c -> growSelection(getAllComponentVerticesFromSelected()))
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
new ActionBuilder("Clear Selection", ACTION_OWNER) new ActionBuilder("Clear Selection", ACTION_OWNER).popupMenuPath("Clear Selection")
.popupMenuPath("Clear Selection")
.popupMenuGroup("z", "5") .popupMenuGroup("z", "5")
.keyBinding("escape") .keyBinding("escape")
.enabledWhen(c -> hasSelection()) .enabledWhen(c -> hasSelection())
@ -516,8 +506,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
.onAction(c -> groupSelectedVertices()) .onAction(c -> groupSelectedVertices())
.buildAndInstallLocal(componentProvider); .buildAndInstallLocal(componentProvider);
new ActionBuilder("Expand Selected", ACTION_OWNER) new ActionBuilder("Expand Selected", ACTION_OWNER).popupMenuPath("Expand Selected Vertices")
.popupMenuPath("Expand Selected Vertices")
.popupMenuGroup("zz", "6") .popupMenuGroup("zz", "6")
.description("Expands all selected collapsed vertices into their previous form") .description("Expands all selected collapsed vertices into their previous form")
.onAction(c -> ungroupSelectedVertices()) .onAction(c -> ungroupSelectedVertices())
@ -579,8 +568,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
private void askToNameGroupVertex(AttributedVertex vertex) { private void askToNameGroupVertex(AttributedVertex vertex) {
String name = vertex.getName(); String name = vertex.getName();
String userName = OptionDialog.showInputMultilineDialog(null, "Enter Group Vertex Text", String userName =
"Text", name); OptionDialog.showInputMultilineDialog(null, "Enter Group Vertex Text", "Text", name);
updateVertexName(vertex, userName != null ? userName : name); updateVertexName(vertex, userName != null ? userName : name);
} }
@ -602,8 +591,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
} }
private boolean hasSelection() { private boolean hasSelection() {
return !(viewer.getSelectedVertices().isEmpty() && return !(viewer.getSelectedVertices().isEmpty() && viewer.getSelectedEdges().isEmpty());
viewer.getSelectedEdges().isEmpty());
} }
private boolean isSelected(AttributedVertex v) { private boolean isSelected(AttributedVertex v) {
@ -648,15 +636,11 @@ public class DefaultGraphDisplay implements GraphDisplay {
// select all the edges that connect the supplied vertices // select all the edges that connect the supplied vertices
private void selectEdgesConnecting(Collection<AttributedVertex> vertices) { private void selectEdgesConnecting(Collection<AttributedVertex> vertices) {
Set<AttributedEdge> edges = graph.edgeSet() Set<AttributedEdge> edges = graph.edgeSet().stream().filter(e -> {
.stream()
.filter(
e -> {
AttributedVertex source = graph.getEdgeSource(e); AttributedVertex source = graph.getEdgeSource(e);
AttributedVertex target = graph.getEdgeTarget(e); AttributedVertex target = graph.getEdgeTarget(e);
return vertices.contains(source) && vertices.contains(target); return vertices.contains(source) && vertices.contains(target);
}) }).collect(Collectors.toSet());
.collect(Collectors.toSet());
viewer.getSelectedEdgeState().select(edges); viewer.getSelectedEdgeState().select(edges);
} }
@ -813,12 +797,9 @@ public class DefaultGraphDisplay implements GraphDisplay {
private SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> createSatelliteViewer( private SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> createSatelliteViewer(
VisualizationViewer<AttributedVertex, AttributedEdge> parentViewer) { VisualizationViewer<AttributedVertex, AttributedEdge> parentViewer) {
Dimension viewerSize = parentViewer.getSize(); Dimension viewerSize = parentViewer.getSize();
Dimension satelliteSize = new Dimension( Dimension satelliteSize = new Dimension(viewerSize.width / 4, viewerSize.height / 4);
viewerSize.width / 4, viewerSize.height / 4);
final SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> satellite = final SatelliteVisualizationViewer<AttributedVertex, AttributedEdge> satellite =
SatelliteVisualizationViewer.builder(parentViewer) SatelliteVisualizationViewer.builder(parentViewer).viewSize(satelliteSize).build();
.viewSize(satelliteSize)
.build();
// //
// JUNGRAPHT CHANGE 3 // JUNGRAPHT CHANGE 3
@ -1124,10 +1105,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
componentProvider.setTitle(title); componentProvider.setTitle(title);
int count = graph.getVertexCount(); int count = graph.getVertexCount();
if (count > options.getMaxNodeCount()) { if (count > options.getMaxNodeCount()) {
Msg.showWarn(this, null, "Graph Not Rendered - Too many nodes!", Msg.showWarn(this, null, "Graph Not Rendered - Too many nodes!", "Exceeded limit of " +
"Exceeded limit of " + options.getMaxNodeCount() + " nodes.\n\n Graph contained " + options.getMaxNodeCount() + " nodes.\n\n Graph contained " + count + " nodes!");
count +
" nodes!");
graph = new AttributedGraph("Aborted", graph.getGraphType(), "Too Many Nodes"); graph = new AttributedGraph("Aborted", graph.getGraphType(), "Too Many Nodes");
graph.addVertex("1", "Graph Aborted"); graph.addVertex("1", "Graph Aborted");
} }
@ -1293,7 +1272,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
graphRenderer.initializeViewer(vv); graphRenderer.initializeViewer(vv);
vv.getComponent().requestFocus(); vv.getComponent().requestFocus();
vv.setBackground(Color.WHITE); vv.setBackground(BACKGROUND_COLOR);
MouseListener[] mouseListeners = vv.getComponent().getMouseListeners(); MouseListener[] mouseListeners = vv.getComponent().getMouseListeners();
for (MouseListener mouseListener : mouseListeners) { for (MouseListener mouseListener : mouseListeners) {
vv.getComponent().removeMouseListener(mouseListener); vv.getComponent().removeMouseListener(mouseListener);
@ -1334,8 +1313,8 @@ public class DefaultGraphDisplay implements GraphDisplay {
public void addAction(DockingActionIf action) { public void addAction(DockingActionIf action) {
if (containsAction(action)) { if (containsAction(action)) {
Msg.warn(this, "Action with same name and owner already exixts in graph: " + Msg.warn(this,
action.getFullName()); "Action with same name and owner already exixts in graph: " + action.getFullName());
return; return;
} }
@ -1387,20 +1366,17 @@ public class DefaultGraphDisplay implements GraphDisplay {
MutableSelectedState<AttributedVertex> selectedVertexState = MutableSelectedState<AttributedVertex> selectedVertexState =
viewer.getSelectedVertexState(); viewer.getSelectedVertexState();
if (hideSelected && hideUnselected) { if (hideSelected && hideUnselected) {
viewer.getRenderContext() viewer.getRenderContext().setVertexIncludePredicate(v -> false);
.setVertexIncludePredicate(v -> false);
} }
else if (hideSelected) { else if (hideSelected) {
viewer.getRenderContext() viewer.getRenderContext()
.setVertexIncludePredicate(Predicate.not(selectedVertexState::isSelected)); .setVertexIncludePredicate(Predicate.not(selectedVertexState::isSelected));
} }
else if (hideUnselected) { else if (hideUnselected) {
viewer.getRenderContext() viewer.getRenderContext().setVertexIncludePredicate(selectedVertexState::isSelected);
.setVertexIncludePredicate(selectedVertexState::isSelected);
} }
else { else {
viewer.getRenderContext() viewer.getRenderContext().setVertexIncludePredicate(v -> true);
.setVertexIncludePredicate(v -> true);
} }
viewer.repaint(); viewer.repaint();
} }
@ -1467,8 +1443,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
@Override @Override
public AttributedVertex getVertex(MouseEvent event) { public AttributedVertex getVertex(MouseEvent event) {
LayoutModel<AttributedVertex> layoutModel = LayoutModel<AttributedVertex> layoutModel = vv.getVisualizationModel().getLayoutModel();
vv.getVisualizationModel().getLayoutModel();
Point2D p = vv.getTransformSupport().inverseTransform(vv, event.getPoint()); Point2D p = vv.getTransformSupport().inverseTransform(vv, event.getPoint());
AttributedVertex vertex = AttributedVertex vertex =
vv.getPickSupport().getVertex(layoutModel, p.getX(), p.getY()); vv.getPickSupport().getVertex(layoutModel, p.getX(), p.getY());
@ -1477,8 +1452,7 @@ public class DefaultGraphDisplay implements GraphDisplay {
@Override @Override
public AttributedEdge getEdge(MouseEvent event) { public AttributedEdge getEdge(MouseEvent event) {
LayoutModel<AttributedVertex> layoutModel = LayoutModel<AttributedVertex> layoutModel = vv.getVisualizationModel().getLayoutModel();
vv.getVisualizationModel().getLayoutModel();
Point2D p = vv.getTransformSupport().inverseTransform(vv, event.getPoint()); Point2D p = vv.getTransformSupport().inverseTransform(vv, event.getPoint());
AttributedEdge edge = vv.getPickSupport().getEdge(layoutModel, p.getX(), p.getY()); AttributedEdge edge = vv.getPickSupport().getEdge(layoutModel, p.getX(), p.getY());
return edge; return edge;

View file

@ -6,6 +6,7 @@
##MODULE IP: Oxygen Icons - LGPL 3.0 ##MODULE IP: Oxygen Icons - LGPL 3.0
##MODULE IP: Tango Icons - Public Domain ##MODULE IP: Tango Icons - Public Domain
Module.manifest||GHIDRA||||END| Module.manifest||GHIDRA||||END|
data/programdiff.theme.properties||GHIDRA||||END|
src/main/help/help/TOC_Source.xml||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/arrow.gif||GHIDRA||reviewed||END|
src/main/help/help/shared/close16.gif||GHIDRA||reviewed||END| src/main/help/help/shared/close16.gif||GHIDRA||reviewed||END|

View file

@ -0,0 +1,7 @@
[Defaults]
color.bg.programdiff.highlight = moccasin
[Dark Defaults]
color.bg.programdiff.highlight = darkRed

View file

@ -26,6 +26,7 @@ import javax.swing.text.*;
import javax.swing.tree.TreeSelectionModel; import javax.swing.tree.TreeSelectionModel;
import docking.DockingUtils; import docking.DockingUtils;
import docking.theme.GColor;
import docking.widgets.EventTrigger; import docking.widgets.EventTrigger;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import docking.widgets.fieldpanel.FieldPanel; 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 SELECTION_GROUP = "Selection Colors";
private static final String DIFF_HIGHLIGHT_COLOR_NAME = private static final String DIFF_HIGHLIGHT_COLOR_NAME =
SELECTION_GROUP + Options.DELIMITER + "Difference Color"; 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; private Color cursorHighlightColor;
protected static final HelpService help = Help.getHelpService(); protected static final HelpService help = Help.getHelpService();

View file

@ -0,0 +1,2 @@
MODULE FILE LICENSE: lib/flatlaf-2.1.jar Apache License 2.0

View file

@ -27,6 +27,7 @@ dependencies {
api project(':Generic') api project(':Generic')
api project(':Help') api project(':Help')
api 'com.formdev:flatlaf:2.2'
// include code from src/test in Generic // include code from src/test in Generic
testImplementation project(path: ':Generic', configuration: 'testArtifacts') testImplementation project(path: ':Generic', configuration: 'testArtifacts')

View file

@ -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/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/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| 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/dnd/package.html||GHIDRA||reviewed||END|
src/main/java/docking/options/editor/package.html||GHIDRA||reviewed||END| src/main/java/docking/options/editor/package.html||GHIDRA||reviewed||END|
src/main/java/docking/widgets/fieldpanel/package.html||GHIDRA||reviewed||END| src/main/java/docking/widgets/fieldpanel/package.html||GHIDRA||reviewed||END|

View file

@ -1,3 +1,4 @@
ColumnConstraintProvider ColumnConstraintProvider
TypeMapper TypeMapper
TableColumn TableColumn
Theme

View file

@ -0,0 +1,5 @@
[Defaults]
color.palette.lightgreen = rgb(127, 255, 127)
color.palette.lightred = rgb(255, 127, 127)
color.palette.yellow = yellow

View file

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

View file

@ -37,7 +37,7 @@ import docking.widgets.list.GList;
import docking.widgets.list.GListCellRenderer; import docking.widgets.list.GListCellRenderer;
import docking.widgets.table.GTableCellRenderer; import docking.widgets.table.GTableCellRenderer;
import docking.widgets.tree.support.GTreeRenderer; import docking.widgets.tree.support.GTreeRenderer;
import ghidra.docking.util.DockingWindowsLookAndFeelUtils; import ghidra.docking.util.LookAndFeelUtils;
import ghidra.util.HTMLUtilities; import ghidra.util.HTMLUtilities;
import resources.ResourceManager; import resources.ResourceManager;
@ -125,7 +125,7 @@ public class DockingUtils {
public static JSeparator createToolbarSeparator() { public static JSeparator createToolbarSeparator() {
Dimension sepDim = new Dimension(2, ICON_SIZE + 2); Dimension sepDim = new Dimension(2, ICON_SIZE + 2);
JSeparator separator = new JSeparator(SwingConstants.VERTICAL); JSeparator separator = new JSeparator(SwingConstants.VERTICAL);
if (DockingWindowsLookAndFeelUtils.isUsingAquaUI(separator.getUI())) { if (LookAndFeelUtils.isUsingAquaUI(separator.getUI())) {
separator.setUI(new BasicSeparatorUI()); separator.setUI(new BasicSeparatorUI());
} }
separator.setPreferredSize(sepDim); // ugly work around to force height of separator separator.setPreferredSize(sepDim); // ugly work around to force height of separator

View file

@ -16,8 +16,8 @@
package docking.framework; package docking.framework;
import docking.DockingErrorDisplay; import docking.DockingErrorDisplay;
import docking.theme.Gui;
import docking.widgets.PopupKeyStorePasswordProvider; import docking.widgets.PopupKeyStorePasswordProvider;
import ghidra.docking.util.DockingWindowsLookAndFeelUtils;
import ghidra.framework.ApplicationConfiguration; import ghidra.framework.ApplicationConfiguration;
import ghidra.net.ApplicationKeyManagerFactory; import ghidra.net.ApplicationKeyManagerFactory;
import ghidra.util.ErrorDisplay; import ghidra.util.ErrorDisplay;
@ -48,7 +48,7 @@ public class DockingApplicationConfiguration extends ApplicationConfiguration {
protected void initializeApplication() { protected void initializeApplication() {
super.initializeApplication(); super.initializeApplication();
DockingWindowsLookAndFeelUtils.loadFromPreferences(); Gui.initialize();
if (showSplashScreen) { if (showSplashScreen) {
SplashScreen.showSplashScreen(); SplashScreen.showSplashScreen();

View file

@ -23,6 +23,7 @@ import javax.swing.*;
import javax.swing.border.BevelBorder; import javax.swing.border.BevelBorder;
import docking.*; import docking.*;
import docking.theme.GColor;
import docking.widgets.label.GDLabel; import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel; import docking.widgets.label.GLabel;
import generic.util.WindowUtilities; import generic.util.WindowUtilities;
@ -37,8 +38,6 @@ import utility.application.ApplicationLayout;
*/ */
public class SplashScreen extends JWindow { 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 SplashScreen splashWindow; // splash window displayed while ghidra is coming up
private static DockingFrame hiddenFrame; private static DockingFrame hiddenFrame;
private static JLabel statusLabel; private static JLabel statusLabel;
@ -286,7 +285,7 @@ public class SplashScreen extends JWindow {
private JPanel createMainPanel() { private JPanel createMainPanel() {
JPanel mainPanel = new JPanel(new BorderLayout()); 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(createTitlePanel(), BorderLayout.NORTH);
mainPanel.add(createContentPanel(), BorderLayout.CENTER); mainPanel.add(createContentPanel(), BorderLayout.CENTER);
return mainPanel; return mainPanel;
@ -331,7 +330,7 @@ public class SplashScreen extends JWindow {
statusLabel.setFont(f); statusLabel.setFont(f);
statusLabel.setBorder(BorderFactory.createEmptyBorder(0, 10, 2, 10)); statusLabel.setBorder(BorderFactory.createEmptyBorder(0, 10, 2, 10));
statusLabel.setBackground(DEFAULT_BACKGROUND_COLOR); statusLabel.setBackground(new GColor("color.bg.splash"));
statusLabel.setOpaque(true); statusLabel.setOpaque(true);
return statusLabel; return statusLabel;
} }

View file

@ -32,6 +32,7 @@ import javax.swing.UIManager;
import docking.ComponentProvider; import docking.ComponentProvider;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.theme.GColor;
import generic.concurrent.GThreadPool; import generic.concurrent.GThreadPool;
import generic.util.WindowUtilities; import generic.util.WindowUtilities;
import ghidra.util.*; 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. * you can see the highlights when you do a search in the JavaHelp.
*/ */
private void setColorResources() { 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")); UIManager.put("EditorPane.selectionForeground", UIManager.get("EditorPane.foreground"));
} }

View file

@ -24,7 +24,7 @@ import javax.swing.KeyStroke;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import ghidra.docking.util.DockingWindowsLookAndFeelUtils; import ghidra.docking.util.LookAndFeelUtils;
import ghidra.util.StringUtilities; import ghidra.util.StringUtilities;
class DockingToolBarUtils { class DockingToolBarUtils {
@ -96,7 +96,7 @@ class DockingToolBarUtils {
builder.append(InputEvent.getModifiersExText(modifiers)); builder.append(InputEvent.getModifiersExText(modifiers));
// The Aqua LaF does not use the '+' symbol between modifiers // The Aqua LaF does not use the '+' symbol between modifiers
if (!DockingWindowsLookAndFeelUtils.isUsingAquaUI(button.getUI())) { if (!LookAndFeelUtils.isUsingAquaUI(button.getUI())) {
builder.append('+'); builder.append('+');
} }
} }

View file

@ -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<Color> {
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]);
}
}

View file

@ -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());
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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<Font> {
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());
}
}

View file

@ -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;
}
}
}

View file

@ -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<TextAttribute, ?> getAttributes() {
return delegate.getAttributes();
}
@Override
public Attribute[] getAvailableAttributes() {
return delegate.getAvailableAttributes();
}
@Override
public Font deriveFont(int newStyle, float newSize) {
return delegate.deriveFont(newStyle, newSize);
}
@Override
public Font deriveFont(int newStyle, AffineTransform trans) {
return delegate.deriveFont(newStyle, trans);
}
@Override
public Font deriveFont(float newSize) {
return delegate.deriveFont(newSize);
}
@Override
public Font deriveFont(AffineTransform trans) {
return delegate.deriveFont(trans);
}
@Override
public Font deriveFont(int newStyle) {
return delegate.deriveFont(newStyle);
}
@Override
public Font deriveFont(Map<? extends Attribute, ?> attributes) {
return delegate.deriveFont(attributes);
}
@Override
public boolean canDisplay(char c) {
return delegate.canDisplay(c);
}
@Override
public boolean canDisplay(int codePoint) {
return delegate.canDisplay(codePoint);
}
@Override
public int canDisplayUpTo(String str) {
return delegate.canDisplayUpTo(str);
}
@Override
public int canDisplayUpTo(char[] text, int start, int limit) {
return delegate.canDisplayUpTo(text, start, limit);
}
@Override
public int canDisplayUpTo(CharacterIterator iter, int start, int limit) {
return delegate.canDisplayUpTo(iter, start, limit);
}
@Override
public float getItalicAngle() {
return delegate.getItalicAngle();
}
@Override
public boolean hasUniformLineMetrics() {
return delegate.hasUniformLineMetrics();
}
@Override
public LineMetrics getLineMetrics(String str, FontRenderContext frc) {
return delegate.getLineMetrics(str, frc);
}
@Override
public LineMetrics getLineMetrics(String str, int beginIndex, int limit,
FontRenderContext frc) {
return delegate.getLineMetrics(str, beginIndex, limit, frc);
}
@Override
public LineMetrics getLineMetrics(char[] chars, int beginIndex, int limit,
FontRenderContext frc) {
return delegate.getLineMetrics(chars, beginIndex, limit, frc);
}
@Override
public LineMetrics getLineMetrics(CharacterIterator ci, int beginIndex, int limit,
FontRenderContext frc) {
return delegate.getLineMetrics(ci, beginIndex, limit, frc);
}
@Override
public Rectangle2D getStringBounds(String str, FontRenderContext frc) {
return delegate.getStringBounds(str, frc);
}
@Override
public Rectangle2D getStringBounds(String str, int beginIndex, int limit,
FontRenderContext frc) {
return delegate.getStringBounds(str, beginIndex, limit, frc);
}
@Override
public Rectangle2D getStringBounds(char[] chars, int beginIndex, int limit,
FontRenderContext frc) {
return delegate.getStringBounds(chars, beginIndex, limit, frc);
}
@Override
public Rectangle2D getStringBounds(CharacterIterator ci, int beginIndex, int limit,
FontRenderContext frc) {
return delegate.getStringBounds(ci, beginIndex, limit, frc);
}
@Override
public Rectangle2D getMaxCharBounds(FontRenderContext frc) {
return delegate.getMaxCharBounds(frc);
}
@Override
public GlyphVector createGlyphVector(FontRenderContext frc, String str) {
return delegate.createGlyphVector(frc, str);
}
@Override
public GlyphVector createGlyphVector(FontRenderContext frc, char[] chars) {
return delegate.createGlyphVector(frc, chars);
}
@Override
public GlyphVector createGlyphVector(FontRenderContext frc, CharacterIterator ci) {
return delegate.createGlyphVector(frc, ci);
}
@Override
public GlyphVector createGlyphVector(FontRenderContext frc, int[] glyphCodes) {
return delegate.createGlyphVector(frc, glyphCodes);
}
@Override
public GlyphVector layoutGlyphVector(FontRenderContext frc, char[] text, int start, int limit,
int flags) {
return delegate.layoutGlyphVector(frc, text, start, limit, flags);
}
}

View file

@ -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;
}
}
}

View file

@ -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<ColorValue> colors = values.getColors();
Collections.sort(colors);
List<FontValue> fonts = values.getFonts();
Collections.sort(fonts);
List<IconValue> 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;
}
}

View file

@ -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);
}
}
}

View file

@ -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<ColorValue> filterTable = new GFilterTable<>(colorTableModel);
filterTable.getTable().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
return filterTable;
}
}

View file

@ -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<String, ColorValue> colorMap = new HashMap<>();
Map<String, FontValue> fontMap = new HashMap<>();
Map<String, IconValue> 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<ColorValue> getColors() {
return new ArrayList<>(colorMap.values());
}
public List<FontValue> getFonts() {
return new ArrayList<>(fontMap.values());
}
public List<IconValue> 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();
}
}

View file

@ -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<GTheme> 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<GTheme> 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<String> 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<GTheme> findThemes() {
Set<GTheme> set = new HashSet<>();
set.addAll(findDiscoverableThemes());
set.addAll(loadThemesFromFiles());
// The set should contains a duplicate of the active theme. Make sure the active theme
// instance is the one in the set
set.remove(activeTheme);
set.add(activeTheme);
return set;
}
private static Collection<GTheme> loadThemesFromFiles() {
List<File> fileList = new ArrayList<>();
File dir = Application.getUserSettingsDirectory();
FileFilter themeFileFilter = file -> file.getName().endsWith(".theme");
fileList.addAll(Arrays.asList(dir.listFiles(themeFileFilter)));
List<GTheme> 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<DiscoverableGTheme> 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;
}
}

View file

@ -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<String> {
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);
}
}

View file

@ -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();
}

View file

@ -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<ColorValue, Object> {
private List<ColorValue> colors;
public ThemeColorTableModel(GTheme theme) {
super(new ServiceProviderStub());
colors = Gui.getAllValues().getColors();
}
@Override
public String getName() {
return "Users";
}
@Override
public List<ColorValue> getModelData() {
return colors;
}
@Override
protected TableColumnDescriptor<ColorValue> createTableColumnDescriptor() {
TableColumnDescriptor<ColorValue> 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<ColorValue, String, Object> {
@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<ColorValue, String, Object> {
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<String> getColumnRenderer() {
return renderer;
}
public Comparator<String> getComparator() {
return (s1, s2) -> valueMap.getColor(s1).compareValue(valueMap.getColor(s2));
}
}
class IsLafPropertyColumn extends AbstractDynamicTableColumn<ColorValue, Boolean, Object> {
@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<String> {
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 = "<No Value>";
}
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;
}
}
}

View file

@ -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<ResourceFile> 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;
}
}

View file

@ -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<String, List<String>> aliasMap = new HashedMap<>();
private List<String> 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<String, List<String>> getAliases() {
return aliasMap;
}
public List<String> getErrors() {
return errors;
}
private void read(Reader reader) throws IOException {
List<Section> 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<Section> readSections(LineNumberReader reader) throws IOException {
List<Section> 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<String, String> properties = new HashMap<>();
Map<String, Integer> 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<String> 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);
}
}
}

View file

@ -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;
}
}

View file

@ -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<T> implements Comparable<ThemeValue<T>> {
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<T> result = this;
Set<String> 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<T> getReferredValue(GThemeValueMap preferredValues,
String theRefId);
@Override
public int compareTo(ThemeValue<T> o) {
return id.compareTo(o.id);
}
public int compareValue(ThemeValue<T> 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 + ")";
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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");
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -21,6 +21,7 @@ import javax.swing.BorderFactory;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.border.Border; import javax.swing.border.Border;
import docking.theme.GColor;
import docking.widgets.label.GDHtmlLabel; import docking.widgets.label.GDHtmlLabel;
/** /**
@ -31,8 +32,8 @@ import docking.widgets.label.GDHtmlLabel;
* *
*/ */
public abstract class AbstractGCellRenderer extends GDHtmlLabel { public abstract class AbstractGCellRenderer extends GDHtmlLabel {
private static final Color BACKGROUND_COLOR = new GColor("color.bg.table.row");
private static final Color ALTERNATE_BACKGROUND_COLOR = new Color(237, 243, 254); 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 */ /** Allows the user to disable alternating row colors on JLists and JTables */
private static final String DISABLE_ALTERNATING_ROW_COLORS_PROPERTY = private static final String DISABLE_ALTERNATING_ROW_COLORS_PROPERTY =
@ -156,7 +157,7 @@ public abstract class AbstractGCellRenderer extends GDHtmlLabel {
} }
protected Color getDefaultBackgroundColor() { protected Color getDefaultBackgroundColor() {
return Color.WHITE; return BACKGROUND_COLOR;
} }
protected Color getBackgroundColorForRow(int row) { protected Color getBackgroundColorForRow(int row) {
@ -164,7 +165,7 @@ public abstract class AbstractGCellRenderer extends GDHtmlLabel {
if ((row & 1) == 1) { if ((row & 1) == 1) {
return getDefaultBackgroundColor(); return getDefaultBackgroundColor();
} }
return ALTERNATE_BACKGROUND_COLOR; return ALT_BACKGROUND_COLOR;
} }
// ================================================================================================== // ==================================================================================================

View file

@ -24,7 +24,7 @@ import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import ghidra.docking.util.DockingWindowsLookAndFeelUtils; import ghidra.docking.util.LookAndFeelUtils;
import resources.ResourceManager; 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 // Mac OSX LNF doesn't give us rollover callbacks, so we have to add a mouse listener to
// do the work // do the work
if (DockingWindowsLookAndFeelUtils.isUsingAquaUI(getUI())) { if (LookAndFeelUtils.isUsingAquaUI(getUI())) {
addMouseListener(new MouseAdapter() { addMouseListener(new MouseAdapter() {
@Override @Override
public void mouseEntered(MouseEvent e) { public void mouseEntered(MouseEvent e) {

View file

@ -15,7 +15,7 @@
*/ */
package docking.widgets.fieldpanel; package docking.widgets.fieldpanel;
import static docking.widgets.EventTrigger.INTERNAL_ONLY; import static docking.widgets.EventTrigger.*;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
@ -29,6 +29,7 @@ import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener; import javax.swing.event.ChangeListener;
import docking.DockingUtils; import docking.DockingUtils;
import docking.theme.GColor;
import docking.util.GraphicsUtils; import docking.util.GraphicsUtils;
import docking.widgets.EventTrigger; import docking.widgets.EventTrigger;
import docking.widgets.fieldpanel.field.Field; import docking.widgets.fieldpanel.field.Field;
@ -51,7 +52,7 @@ public class FieldPanel extends JPanel
private boolean inFocus; private boolean inFocus;
protected BackgroundColorModel backgroundColorModel = protected BackgroundColorModel backgroundColorModel =
new DefaultBackgroundColorModel(Color.WHITE); new DefaultBackgroundColorModel(new GColor("color.bg.fieldpanel"));
protected PaintContext paintContext = new PaintContext(); protected PaintContext paintContext = new PaintContext();
private AnchoredLayoutHandler layoutHandler; private AnchoredLayoutHandler layoutHandler;
@ -415,7 +416,6 @@ public class FieldPanel extends JPanel
*/ */
public void setBackgroundColor(Color c) { public void setBackgroundColor(Color c) {
backgroundColorModel.setDefaultBackgroundColor(c); backgroundColorModel.setDefaultBackgroundColor(c);
paintContext.setDefaultBackgroundColor(c);
} }
public Color getBackgroundColor(BigInteger index) { public Color getBackgroundColor(BigInteger index) {
@ -1238,8 +1238,7 @@ public class FieldPanel extends JPanel
if (layout == null) { if (layout == null) {
return null; return null;
} }
Rectangle r = Rectangle r = layout.getCursorRect(location.fieldNum, location.row, location.col);
layout.getCursorRect(location.fieldNum, location.row, location.col);
return r.getLocation(); return r.getLocation();
} }

View file

@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 java.awt.Color;
import docking.theme.GColor;
/** /**
* Miscellaneous information needed by fields to paint. * Miscellaneous information needed by fields to paint.
*/ */
public class PaintContext { public class PaintContext {
private Color defaultBackground;
private Color background; private Color background;
private Color foreground; private Color foreground;
private Color selectionColor; private Color selectionColor;
@ -43,20 +43,18 @@ public class PaintContext {
* Create a new PaintContext with default color values. * Create a new PaintContext with default color values.
*/ */
public PaintContext() { public PaintContext() {
defaultBackground = Color.white; background = new GColor("color.bg.fieldpanel");
background = Color.white; foreground = new GColor("color.fg.fieldpanel");
foreground = Color.black; selectionColor = new GColor("color.bg.fieldpanel.selection");
selectionColor = new Color(180, 255, 180); highlightColor = new GColor("color.bg.fieldpanel.highlight");
highlightColor = new Color(255, 255, 150); selectedHighlightColor = new GColor("color.bg.fieldpanel.selection-highlight");
selectedHighlightColor = Color.green; focusedCursorColor = new GColor("color.cursor.focused");
focusedCursorColor = Color.RED; notFocusedCursorColor = new GColor("color.cursor.unfocused");
cursorColor = focusedCursorColor; cursorColor = focusedCursorColor;
invisibleCursorColor = new Color(255, 0, 0, 1); invisibleCursorColor = new Color(255, 0, 0, 1);
notFocusedCursorColor = Color.PINK;
} }
public PaintContext(PaintContext other) { public PaintContext(PaintContext other) {
defaultBackground = other.defaultBackground;
background = other.background; background = other.background;
foreground = other.foreground; foreground = other.foreground;
selectionColor = other.selectionColor; selectionColor = other.selectionColor;
@ -69,16 +67,9 @@ public class PaintContext {
printColor = other.printColor; 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. * Returns the current background color setting.
* @return the current background color setting.
*/ */
public final Color getBackground() { public final Color getBackground() {
return background; return background;
@ -86,6 +77,7 @@ public class PaintContext {
/** /**
* Returns the current foreground color setting. * Returns the current foreground color setting.
* @return the current foreground color setting.
*/ */
public final Color getForeground() { public final Color getForeground() {
return foreground; return foreground;
@ -93,27 +85,31 @@ public class PaintContext {
/** /**
* Returns the current selection color setting. * Returns the current selection color setting.
* @return the current selection color setting.
*/ */
public final Color getSelectionColor() { public final Color getSelectionColor() {
return selectionColor; return selectionColor;
} }
/** /**
* Returns the current selection color setting. * Returns the current highlight color setting.
* @return the current highlight color setting.
*/ */
public final Color getHighlightColor() { public final Color getHighlightColor() {
return highlightColor; 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() { public final Color getSelectedHighlightColor() {
return selectedHighlightColor; return selectedHighlightColor;
} }
/** /**
* Returns the current cursor color setting. * Returns the current cursor color.
* @return the current cursor color.
*/ */
public final Color getCursorColor() { public final Color getCursorColor() {
return cursorColor; return cursorColor;
@ -133,17 +129,6 @@ public class PaintContext {
adjustSelectedHighlightColor(); 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() { private void adjustSelectedHighlightColor() {
int red = (selectionColor.getRed() + highlightColor.getRed()) / 2; int red = (selectionColor.getRed() + highlightColor.getRed()) / 2;
int green = (selectionColor.getGreen() + highlightColor.getGreen()) / 2; int green = (selectionColor.getGreen() + highlightColor.getGreen()) / 2;

View file

@ -15,23 +15,23 @@
*/ */
package docking.widgets.filechooser; 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.*;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; 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.*;
import javax.swing.event.CellEditorListener; import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeEvent;
import javax.swing.filechooser.FileSystemView; import javax.swing.filechooser.FileSystemView;
import docking.*; import docking.*;
import docking.theme.GColor;
import docking.widgets.*; import docking.widgets.*;
import docking.widgets.combobox.GComboBox; import docking.widgets.combobox.GComboBox;
import docking.widgets.label.GDLabel; import docking.widgets.label.GDLabel;
@ -73,8 +73,8 @@ import util.HistoryList;
public class GhidraFileChooser extends DialogComponentProvider implements FileFilter { public class GhidraFileChooser extends DialogComponentProvider implements FileFilter {
static final String UP_BUTTON_NAME = "UP_BUTTON"; static final String UP_BUTTON_NAME = "UP_BUTTON";
private static final Color FOREROUND_COLOR = Color.BLACK; private static final Color FOREROUND_COLOR = new GColor("color.fg.filechooser");
private static final Color BACKGROUND_COLOR = Color.WHITE; private static final Color BACKGROUND_COLOR = new GColor("color.bg.filechooser");
static final String PREFERENCES_PREFIX = "G_FILE_CHOOSER"; static final String PREFERENCES_PREFIX = "G_FILE_CHOOSER";
private static final String WIDTH_PREFERENCE_PREFIX = PREFERENCES_PREFIX + ".WIDTH."; private static final String WIDTH_PREFERENCE_PREFIX = PREFERENCES_PREFIX + ".WIDTH.";
private static final String HEIGHT_PREFERENCE_PREFIX = PREFERENCES_PREFIX + ".HEIGHT."; private static final String HEIGHT_PREFERENCE_PREFIX = PREFERENCES_PREFIX + ".HEIGHT.";

View file

@ -24,6 +24,7 @@ import javax.swing.*;
import javax.swing.border.*; import javax.swing.border.*;
import javax.swing.table.*; import javax.swing.table.*;
import docking.theme.GColor;
import docking.widgets.label.GDLabel; import docking.widgets.label.GDLabel;
import resources.*; import resources.*;
import resources.icons.EmptyIcon; import resources.icons.EmptyIcon;
@ -37,11 +38,17 @@ import resources.icons.TranslateIcon;
*/ */
public class GTableHeaderRenderer extends JPanel implements TableCellRenderer { public class GTableHeaderRenderer extends JPanel implements TableCellRenderer {
private static final int PADDING_FOR_COLUMN_NUMBER = 10; 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); // static {
private static final Color PRIMARY_SORT_GRADIENT_END = new Color(126, 186, 233); // Gui.registerAltColor(GThemeDefaults.DARK, GRADIENT_START_ID, new Color(20, 20, 20));
private static final Color DEFAULT_GRADIENT_START = Color.WHITE; // Gui.registerAltColor(GThemeDefaults.DARK, GRADIENT_END_ID, new Color(40, 40, 40));
private static final Color DEFAULT_GRADIENT_END = new Color(215, 215, 215); // }
private static final Icon UP_ICON = private static final Icon UP_ICON =
ResourceManager.getScaledIcon(Icons.SORT_ASCENDING_ICON, 14, 14); ResourceManager.getScaledIcon(Icons.SORT_ASCENDING_ICON, 14, 14);
@ -219,12 +226,12 @@ public class GTableHeaderRenderer extends JPanel implements TableCellRenderer {
} }
protected Paint getBackgroundPaint() { protected Paint getBackgroundPaint() {
if (isPaintingPrimarySortColumn) { Color startColor =
return new GradientPaint(0, 0, PRIMARY_SORT_GRADIENT_START, 0, getHeight() - 11, isPaintingPrimarySortColumn ? GRADIENT_START_PRIMARY_COLOR : GRADIENT_START_COLOR;
PRIMARY_SORT_GRADIENT_END, true); Color endColor =
} isPaintingPrimarySortColumn ? GRADIENT_END_PRIMARY_COLOR : GRADIENT_END_COLOR;
return new GradientPaint(0, 0, DEFAULT_GRADIENT_START, 0, getHeight() - 11,
DEFAULT_GRADIENT_END, true); return new GradientPaint(0, 0, startColor, 0, getHeight() - 11, endColor, true);
} }
private void updateHelpIcon(JTable table, int currentColumnIndex, Icon icon) { private void updateHelpIcon(JTable table, int currentColumnIndex, Icon icon) {

View file

@ -22,6 +22,8 @@ import javax.swing.JTextField;
import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
import docking.theme.GColor;
/** /**
* Simple text field that shows a text hint when the field is empty. * 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. // some indication of what the field should contain.
private String hint; private String hint;
private Color INVALID_COLOR = new Color(255, 225, 225); private Color VALID_COLOR = new GColor("color.bg.textfield.hint.valid");
private Color VALID_COLOR = Color.WHITE; private Color INVALID_COLOR = new GColor("color.bg.textfield.hint.invalid");
private Color defaultBackgroundColor; private Color defaultBackgroundColor;
/** /**
@ -126,8 +128,7 @@ public class HintTextField extends JTextField {
Graphics2D g2 = (Graphics2D) g; Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.LIGHT_GRAY); g2.setColor(Color.LIGHT_GRAY);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
RenderingHints.VALUE_ANTIALIAS_ON);
Dimension size = getSize(); Dimension size = getSize();
Insets insets = getInsets(); Insets insets = getInsets();
@ -179,7 +180,7 @@ public class HintTextField extends JTextField {
*/ */
private void setAttributes() { private void setAttributes() {
setFont(getFont().deriveFont(Font.PLAIN)); setFont(getFont().deriveFont(Font.PLAIN));
setForeground(Color.BLACK); setForeground(new GColor("color.fg.textfield.hint"));
} }
/** /**

View file

@ -15,16 +15,15 @@
*/ */
package ghidra.docking.util; package ghidra.docking.util;
import java.awt.Dimension; import java.awt.*;
import java.awt.Font;
import java.util.*; import java.util.*;
import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import javax.swing.*; import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo; import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.plaf.ComponentUI; import javax.swing.plaf.ComponentUI;
import ghidra.docking.util.painting.GRepaintManager;
import ghidra.framework.OperatingSystem; import ghidra.framework.OperatingSystem;
import ghidra.framework.Platform; import ghidra.framework.Platform;
import ghidra.framework.preferences.Preferences; import ghidra.framework.preferences.Preferences;
@ -33,56 +32,50 @@ import ghidra.util.*;
/** /**
* A utility class to manage LookAndFeel (LaF) settings. * 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"; public final static String SYSTEM_LOOK_AND_FEEL = "System";
/**
* Preference name for whether to use inverted colors.
*/
public final static String USE_INVERTED_COLORS_KEY = "LookAndFeel.UseInvertedColors";
/** /**
* Metal is the non-system, generic Java Look and Feel. * Metal is the non-system, generic Java Look and Feel.
*/ */
public final static String METAL_LOOK_AND_FEEL = "Metal"; 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. * 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. * 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 // utils class, cannot create
} }
/** /**
* Loads settings from {@link Preferences}. * Loads settings from {@link Preferences}.
*/ */
public static void loadFromPreferences() { public static void installGlobalOverrides() {
boolean useHistoricalValue = true;
String laf = Preferences.getProperty(LAST_LOOK_AND_FEEL_KEY, getDefaultLookAndFeelName(),
useHistoricalValue);
setLookAndFeel(laf);
boolean useInvertedColors = getUseInvertedColorsPreference();
setUseInvertedColors(useInvertedColors);
// //
// Users can change this via the SystemUtilities.FONT_SIZE_OVERRIDE_PROPERTY_NAME // 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. * Returns the currently installed LaF.
* @return the currently installed LaF. * @return the currently installed LaF.
@ -137,19 +118,45 @@ public class DockingWindowsLookAndFeelUtils {
installPopupMenuSettingsOverride(); installPopupMenuSettingsOverride();
} }
catch (Exception exc) { catch (Exception exc) {
Msg.error(DockingWindowsLookAndFeelUtils.class, Msg.error(LookAndFeelUtils.class,
"Error loading Look and Feel: " + exc, exc); "Error loading Look and Feel: " + exc, exc);
} }
}); });
} }
public static void dumpUIProperties() {
List<String> colorKeys = getLookAndFeelIdsForType(Color.class);
Collections.sort(colorKeys);
for (String string : colorKeys) {
Msg.debug(LookAndFeelUtils.class, string + "\t\t" + UIManager.get(string));
}
}
public static List<String> getLookAndFeelIdsForType(Class<?> clazz) {
UIDefaults defaults = UIManager.getDefaults();
List<String> 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. * Returns all installed LaFs. This will vary by OS.
* @return all installed LaFs. * @return all installed LaFs.
*/ */
public static List<String> getLookAndFeelNames() { public static List<String> getLookAndFeelNames() {
List<String> list = new ArrayList<>(); List<String> 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(); LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels();
for (LookAndFeelInfo info : installedLookAndFeels) { for (LookAndFeelInfo info : installedLookAndFeels) {
@ -171,6 +178,12 @@ public class DockingWindowsLookAndFeelUtils {
if (lookAndFeelName.equalsIgnoreCase(SYSTEM_LOOK_AND_FEEL)) { if (lookAndFeelName.equalsIgnoreCase(SYSTEM_LOOK_AND_FEEL)) {
return UIManager.getSystemLookAndFeelClassName(); 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(); LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels();
for (LookAndFeelInfo info : installedLookAndFeels) { 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); "Unable to find requested Look and Feel: " + lookAndFeelName);
return UIManager.getSystemLookAndFeelClassName(); 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. * 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. * 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(); OperatingSystem OS = Platform.CURRENT_PLATFORM.getOperatingSystem();
if (OS == OperatingSystem.LINUX) { if (OS == OperatingSystem.LINUX) {
return NIMBUS_LOOK_AND_FEEL; return NIMBUS_LOOK_AND_FEEL;
@ -369,8 +368,4 @@ public class DockingWindowsLookAndFeelUtils {
return NIMBUS_LOOK_AND_FEEL.equals(lookAndFeel.getName()); return NIMBUS_LOOK_AND_FEEL.equals(lookAndFeel.getName());
} }
public static boolean isUsingMotifUI() {
LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
return MOTIF_LOOK_AND_FEEL.equals(lookAndFeel.getName());
}
} }

View file

@ -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));
}
}

View file

@ -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<String, List<String>> 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<String> 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());
// }
// }
}

View file

@ -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<String> 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();
}
}

View file

@ -135,6 +135,13 @@ public abstract class AbstractOptions implements Options {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Can't register an option of type: " + OptionType.NO_TYPE); "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) { if (type == OptionType.CUSTOM_TYPE && editor == null) {
throw new IllegalStateException( throw new IllegalStateException(
"Can't register a custom option without a property editor"); "Can't register a custom option without a property editor");
@ -173,8 +180,7 @@ public abstract class AbstractOptions implements Options {
if (option == null) { if (option == null) {
return null; return null;
} }
if (option.getOptionType() != type) {
if (!isCompatibleOption(option, type, defaultValue)) {
Msg.error(this, "Registered option incompatible with existing option: " + optionName, Msg.error(this, "Registered option incompatible with existing option: " + optionName,
new AssertException()); new AssertException());
return null; return null;
@ -182,15 +188,6 @@ public abstract class AbstractOptions implements Options {
return option; 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 @Override
public synchronized void removeOption(String optionName) { public synchronized void removeOption(String optionName) {
aliasMap.remove(optionName); aliasMap.remove(optionName);
@ -700,7 +697,16 @@ public abstract class AbstractOptions implements Options {
return true; 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;
} }
/** /**

View file

@ -64,6 +64,10 @@ public enum OptionType {
return stringAdapter.objectToString(object); return stringAdapter.objectToString(object);
} }
public boolean isCompatible(Object object) {
return object == null || clazz.isAssignableFrom(object.getClass());
}
public Class<?> getValueClass() { public Class<?> getValueClass() {
return clazz; return clazz;
} }

View file

@ -18,8 +18,6 @@ package ghidra.util;
import java.awt.Color; import java.awt.Color;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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 * 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. * those strings back to a color.
*/ */
public abstract class WebColors { 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<String, Color> nameToColorMap = new HashMap<>(); private static final Map<String, Color> nameToColorMap = new HashMap<>();
private static final Map<Color, String> colorToNameMap = new HashMap<>(); private static final Map<Color, String> colorToNameMap = new HashMap<>();
@ -172,7 +170,7 @@ public abstract class WebColors {
public static final Color MEDUM_AQUA_MARINE = registerColor("MediumAquaMarine", Color.decode("0x66CDAA")); 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 MEDIUM_TURQOISE = registerColor("MediumTurquoise", Color.decode("0x48D1CC"));
public static final Color DARK_OLIVE_GREEN = registerColor("DarkOliveGreen", Color.decode("0x556B2F")); 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 //@formatter:on
// cannot instantiate nor extend // cannot instantiate nor extend
@ -193,57 +191,202 @@ public abstract class WebColors {
return color != null ? color : defaultColor; 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, * 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 * 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 * follows. If the color has an non-opaque alpha value, it will be of the form #rrggbb. If
* {@link #getColor(String)} and be guaranteed that a color will be returned * it has an alpha value,then the format will be #rrggbbaa.
* *
* @param color the color to convert to a string. * @param color the color to convert to a string.
* @return the string representation for the given color. * @return the string representation for the given color.
*/ */
public static String toString(Color color) { public static String toString(Color color) {
return toString(color, true);
}
/**
* 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); String name = colorToNameMap.get(color);
if (name != null) { if (name != null) {
return name; return name;
} }
// this will format a color value as a 6 digit hex (e.g. #rrggbb) }
return String.format("#%06X", color.getRGB() & 0xffffff); 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);
} }
private static Color getHexColor(String hexString) { /**
Matcher matcher = HEX_PATTERN.matcher(hexString); * Returns the WebColor name for the given color. Returns null if the color is not a WebColor
if (matcher.matches()) { * @param color the color to lookup a WebColor name.
return Color.decode(hexString); * @return the WebColor name for the given color. Returns null if the color is not a WebColor
} */
return null; public static String toWebColorName(Color color) {
return colorToNameMap.get(color);
} }
private static Color registerColor(String name, Color color) { private static Color registerColor(String name, Color color) {
nameToColorMap.put(name, color); nameToColorMap.put(name.toLowerCase(), color);
colorToNameMap.put(color, name); colorToNameMap.put(color, name);
return color; 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:
* <pre>
* #rrggbb
* #rrggbbaa
* 0xrrggbb
* 0xrrggbbaa
* rgb(red, green, blue)
* rgba(red, green, alpha)
* </pre>
* 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.
* <BR><BR>
* @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:
* <pre>
* #rrggbb
* #rrggbbaa
* 0xrrggbb
* 0xrrggbbaa
* </pre>
*
* Each of the hex digits "rr", "gg", "bb", and "aa" specify the red, green, blue, and alpha
* values respectively.
* <br><br>
*
* @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:
* <pre>
* rgb(red, green, blue)
* rgb(red, green, blue, alpha)
* </pre>
* Each of the values "red", "green", "blue", and "alpha" must be integer values between 0-255
* <br><br>
* @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;
}
}
} }

View file

@ -39,27 +39,16 @@ public class WebColorsTest {
} }
@Test @Test
public void testGetColorFromName() { public void testGetColor() {
assertEquals(WebColors.NAVY, WebColors.getColor("Navy")); assertEquals(WebColors.NAVY, WebColors.getColor("Navy"));
}
@Test
public void testGetColorFromHexString() {
assertEquals(WebColors.NAVY, WebColors.getColor("0x000080")); assertEquals(WebColors.NAVY, WebColors.getColor("0x000080"));
}
@Test
public void testGetColorFromHexString2() {
assertEquals(WebColors.NAVY, WebColors.getColor("#000080")); 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 assertEquals(new Color(0x123456), WebColors.getColor("0x123456"));
public void testGetColorWithNoDefinedValue() { assertEquals(new Color(0x80102030, true), WebColors.getColor("rgba(16, 32, 48, 0.5)"));
assertEquals(new Color(0x12, 0x34, 0x56), WebColors.getColor("0x123456"));
}
@Test assertNull(WebColors.getColor("asdfasdfas"));
public void testGetColorByBadName() {
assertNull(WebColors.getColor("ABCDEFG"));
} }
} }

View file

@ -5,6 +5,7 @@
##MODULE IP: Oxygen Icons - LGPL 3.0 ##MODULE IP: Oxygen Icons - LGPL 3.0
Module.manifest||GHIDRA||||END| Module.manifest||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END| data/ExtensionPoint.manifest||GHIDRA||||END|
data/graph.theme.properties||GHIDRA||||END|
src/main/docs/README.txt||GHIDRA||||END| src/main/docs/README.txt||GHIDRA||||END|
src/main/docs/VerticesAndEdges.png||GHIDRA||||END| src/main/docs/VerticesAndEdges.png||GHIDRA||||END|
src/main/docs/VerticesAndEdges.xml||GHIDRA||||END| src/main/docs/VerticesAndEdges.xml||GHIDRA||||END|

View file

@ -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]

Some files were not shown because too many files have changed in this diff Show more