Merge remote-tracking branch 'origin/patch'

This commit is contained in:
Ryan Kurtz 2023-10-13 07:16:42 -04:00
commit fae64a82c0
45 changed files with 1087 additions and 445 deletions

View file

@ -1188,13 +1188,11 @@ void CollapseStructure::orderLoopBodies(void)
bool CollapseStructure::updateLoopBody(void) bool CollapseStructure::updateLoopBody(void)
{ {
if (finaltrace) { // If we've already performed trace on DAG with no likely goto edges
return false; // don't repeat the trace
}
FlowBlock *loopbottom = (FlowBlock *)0; FlowBlock *loopbottom = (FlowBlock *)0;
FlowBlock *looptop = (FlowBlock *)0; FlowBlock *looptop = (FlowBlock *)0;
if (finaltrace) { // If we've already performed the final trace
if (likelyiter == likelygoto.end())
return false; // We have nothing more to give
return true;
}
while (loopbodyiter != loopbody.end()) { // Last innermost loop while (loopbodyiter != loopbody.end()) { // Last innermost loop
loopbottom = (*loopbodyiter).getCurrentBounds(&looptop,&graph); loopbottom = (*loopbodyiter).getCurrentBounds(&looptop,&graph);
if (loopbottom != (FlowBlock *)0) { if (loopbottom != (FlowBlock *)0) {
@ -1206,8 +1204,10 @@ bool CollapseStructure::updateLoopBody(void)
likelylistfull = false; // Need to generate likely list for new loopbody (or no loopbody) likelylistfull = false; // Need to generate likely list for new loopbody (or no loopbody)
loopbottom = (FlowBlock *)0; loopbottom = (FlowBlock *)0;
} }
if (likelylistfull) return true; if (likelylistfull && likelyiter != likelygoto.end())
// If we reach here, need to generate likely gotos for a new inner loop return true;
// If we reach here, need to generate likely gotos for a new inner loop or DAG
likelygoto.clear(); // Clear out any old likely gotos from last inner loop likelygoto.clear(); // Clear out any old likely gotos from last inner loop
TraceDAG tracer(likelygoto); TraceDAG tracer(likelygoto);
if (loopbottom != (FlowBlock *)0) { if (loopbottom != (FlowBlock *)0) {
@ -1216,7 +1216,6 @@ bool CollapseStructure::updateLoopBody(void)
(*loopbodyiter).setExitMarks(&graph); // Set the bounds of the TraceDAG (*loopbodyiter).setExitMarks(&graph); // Set the bounds of the TraceDAG
} }
else { else {
finaltrace = true;
for(uint4 i=0;i<graph.getSize();++i) { for(uint4 i=0;i<graph.getSize();++i) {
FlowBlock *bl = graph.getBlock(i); FlowBlock *bl = graph.getBlock(i);
if (bl->sizeIn() == 0) if (bl->sizeIn() == 0)
@ -1225,11 +1224,15 @@ bool CollapseStructure::updateLoopBody(void)
} }
tracer.initialize(); tracer.initialize();
tracer.pushBranches(); tracer.pushBranches();
likelylistfull = true; // Mark likelygoto generation complete for current loop or DAG
if (loopbottom != (FlowBlock *)0) { if (loopbottom != (FlowBlock *)0) {
(*loopbodyiter).emitLikelyEdges(likelygoto,&graph); (*loopbodyiter).emitLikelyEdges(likelygoto,&graph);
(*loopbodyiter).clearExitMarks(&graph); (*loopbodyiter).clearExitMarks(&graph);
} }
likelylistfull = true; else if (likelygoto.empty()) {
finaltrace = true; // No loops left and trace didn't find gotos
return false;
}
likelyiter = likelygoto.begin(); likelyiter = likelygoto.begin();
return true; return true;
} }

View file

@ -333,10 +333,10 @@ color.fg.listing.bytes = orange
<P> <P>
Properties defined by the theming system do not follow this pattern. To reference a Properties defined by the theming system do not follow this pattern. To reference a
property that does not have a standard prefix, an ID can be prefixed with <COD>[color] property that does not have a standard prefix, an ID can be prefixed with <CODE>[color]
</CODE>, <CODE>[font]</CODE>, or <CODE>[icon]</CODE> as appropriate to allow the theme </CODE>, <CODE>[font]</CODE>, or <CODE>[icon]</CODE> as appropriate to allow the theme
property parser to recognize the values as IDs to other properties. For example, to refer to a property parser to recognize the values as IDs to other properties. For example, to refer to a
system property named<CODE>system.color.bg.view</CODE>, system property named <CODE>system.color.bg.view</CODE>,
you would use the following definition: you would use the following definition:
<P> <P>
<BLOCKUOTE> <BLOCKUOTE>
@ -454,7 +454,7 @@ color.fg.listing.bytes = orange
<BLOCKQUOTE> <BLOCKQUOTE>
<PRE> <PRE>
<CODE> <CODE>
<I>iconName</I>[size(width,height)][disabled]{iconOverlayName[size(width,height)[disabled][move(x,y)]}{...} <I>iconName</I>[size(width,height)][disabled]{overlayIconName[size(width,height)[disabled][move(x,y)]}{...}
</CODE> </CODE>
</PRE> </PRE>
@ -522,7 +522,7 @@ color.fg.listing.bytes = orange
<BLOCKQUOTE> <BLOCKQUOTE>
<P> <P>
A list of palette colors has been defined in <CODE>gui.palette.theme.properties.</CODE>. A list of palette colors has been defined in <CODE>gui.palette.theme.properties</CODE>.
These palette colors values are meant to be used by developers to reduce the total number These palette colors values are meant to be used by developers to reduce the total number
of colors used in the application. These color ids and values are viewable in the of colors used in the application. These color ids and values are viewable in the
<A href="ThemingUserDocs.html#Edit_Theme">Theme Editor Dialog</A>. <A href="ThemingUserDocs.html#Edit_Theme">Theme Editor Dialog</A>.

View file

@ -38,8 +38,8 @@
<CODE>ColorValue</CODE>, <CODE>FontValue</CODE>, and <CODE>ColorValue</CODE>, <CODE>FontValue</CODE>, and
<CODE>IconValue</CODE>. Resource values are stored in these <CODE>ThemeValue</CODE> sub-classes <CODE>IconValue</CODE>. Resource values are stored in these <CODE>ThemeValue</CODE> sub-classes
because the value can be because the value can be
either a concrete value or be a reference to some other resource of the same type. So, for either a concrete value or a reference to some other resource of the same type. So, for
example, "color.bg.foo" could map directly to an actual color or its value could be reference example, "color.bg.foo" could map directly to an actual color or its value could be a reference
to some other indirect color like "color.bg.bar". In any <CODE>ThemeValue</CODE> object, either to some other indirect color like "color.bg.bar". In any <CODE>ThemeValue</CODE> object, either
the referenced ID or the direct value must be null. To get the ultimate concrete value, there the referenced ID or the direct value must be null. To get the ultimate concrete value, there
is a convenience method called <CODE>get()</CODE> on <CODE>ThemeValue</CODE>s that takes a is a convenience method called <CODE>get()</CODE> on <CODE>ThemeValue</CODE>s that takes a

View file

@ -2,7 +2,7 @@
<HTML> <HTML>
<HEAD> <HEAD>
<TITLE>General Overivew</TITLE> <TITLE>Theming Overivew</TITLE>
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css"> <LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
</HEAD> </HEAD>
@ -86,7 +86,7 @@
themes to choose from and by simply switching the theme, the system will then update the Look themes to choose from and by simply switching the theme, the system will then update the Look
and Feel along with the colors, fonts, and icons, all with one action. The set of themes to and Feel along with the colors, fonts, and icons, all with one action. The set of themes to
choose from is a mix of built-in themes and saved custom themes. There is one built-in theme choose from is a mix of built-in themes and saved custom themes. There is one built-in theme
for each supported Look and Feel. The chosen theme will use the UI values defied by the Look for each supported Look and Feel. The chosen theme will use the UI values defined by the Look
and Feel, as well as all the values for the defined property IDs. Users are able to and Feel, as well as all the values for the defined property IDs. Users are able to
create custom themes to change any color, font, or icon defined by the application, along with create custom themes to change any color, font, or icon defined by the application, along with
UI values supplied by the associated Look and Feel.</P> UI values supplied by the associated Look and Feel.</P>

View file

@ -233,9 +233,9 @@
<BlOCKQUOTE> <BlOCKQUOTE>
<P>Theme Property Names (also referred to as IDs or keys) that are defined by the application <P>Theme Property Names (also referred to as IDs or keys) that are defined by the application
use a common format to help make sorting and viewing properties more intuitive as to their use. See use a common format to help make sorting and viewing properties more intuitive as to their use.
the <A href="ThemingDeveloperDocs.html#Resource_Ids">Developer Documentation</A> for more details on the property ID See the <A href="ThemingDeveloperDocs.html#Resource_Ids">Developer Documentation</A> for more
format and naming conventions.</P> details on the property ID format and naming conventions.</P>
</BlOCKQUOTE> </BlOCKQUOTE>
<H2>Theme Files</H2> <H2>Theme Files</H2>
@ -243,10 +243,10 @@
<P>Theme Files are used to store saved custom themes. They are simple text files and are <P>Theme Files are used to store saved custom themes. They are simple text files and are
stored in the user's home application directory under stored in the user's home application directory under
<code>&lt;home&gt;/.ghidra/.ghidra-&lt;version&gt/themes</code>. The first three properties are always the <code>&lt;home&gt;/.ghidra/.ghidra-&lt;version&gt/themes</code>. The first three properties
theme name, the Look and Feel name, and whether the theme uses standard defaults or dark are always the theme name, the Look and Feel name, and whether the theme uses standard
defaults. Finally, there is a list of overridden property "name = value" lines. The format defaults or dark defaults. Finally, there is a list of overridden property "name = value"
is:</P> lines. The format is:</P>
<CODE> <CODE>
<PRE> <PRE>
name = [theme name] name = [theme name]
@ -270,18 +270,18 @@
color.bg = Black color.bg = Black
color.bg.foo = #012345 color.bg.foo = #012345
[color]Panel.background = Red
font.button = dialog-PLAIN-14 font.button = dialog-PLAIN-14
icon.refresh = images/reload3.png icon.refresh = images/reload3.png
color.bg.bar = color.bg.foo color.bg.bar = color.bg.foo
color.bg.xxx = [color]Panel.background [laf.color]Panel.background = silver
[laf.color]TextArea.background = [laf.color]Panel.background
</PRE> </PRE>
<P>Each property line is expected to begin with either "color.", "font.", or "icon." Since <P>Each property line is expected to begin with either "color.", "font.", or "icon." Since
java defined properties don't start with these prefixes, they will have "[color]", "[font]", java defined properties don't start with these prefixes, they will have "[laf.color]",
or "[icon]" prepended to their property name. These brackets are only used to aid in "[laf.font]", or "[laf.icon]" prepended to their property name. These brackets are only used
parsing this file. When the properties are used in Ghidra, the bracketed prefixes are to aid in parsing this file. When the properties are used in Ghidra, the bracketed prefixes are
removed.</P> removed.</P>
<P>Also, note that the values of these properties can reference other property names. If the <P>Also, note that the values of these properties can reference other property names. If the

View file

@ -24,7 +24,7 @@ import ghidra.util.task.Task;
/** /**
* A version of {@link DialogComponentProvider} for clients to extend when they intend for their * A version of {@link DialogComponentProvider} for clients to extend when they intend for their
* dialog to be reused. Typically, dialogs are used once and then no longer referenced. * dialog to be reused. Typically, dialogs are used once and then no longer referenced.
* Alternatively, some clients create a dialog and use it for the lifetime of their code. This * Alternatively, some clients create a dialog and use it for the lifetime of their code. This
* is typical of non-modal plugins. * is typical of non-modal plugins.
* <p> * <p>
@ -32,7 +32,7 @@ import ghidra.util.task.Task;
* with the dialog, such as in your plugin's {@code dispose()} method. * with the dialog, such as in your plugin's {@code dispose()} method.
* <p> * <p>
* The primary benefit of using this dialog is that any updates to the current theme will update * The primary benefit of using this dialog is that any updates to the current theme will update
* this dialog, even when the dialog is not visible. For dialogs that extend * this dialog, even when the dialog is not visible. For dialogs that extend
* {@link DialogComponentProvider} directly, they only receive theme updates if they are visible. * {@link DialogComponentProvider} directly, they only receive theme updates if they are visible.
* *
* @see DialogComponentProvider * @see DialogComponentProvider
@ -62,12 +62,7 @@ public class ReusableDialogComponentProvider extends DialogComponentProvider {
} }
private void themeChanged(ThemeEvent ev) { private void themeChanged(ThemeEvent ev) {
if (!ev.isLookAndFeelChanged()) { // if we are visible, then we don't need to update as the system updates all visible components
return; // we only care if the Look and Feel changes
}
// if we are visible, then we don't need to update as the system updates all
// visible components
if (isVisible()) { if (isVisible()) {
return; return;
} }

View file

@ -127,6 +127,7 @@ public class ComponentThemeInspectorAction extends DockingAction {
Color bg = component.getBackground(); Color bg = component.getBackground();
Color fg = component.getForeground(); Color fg = component.getForeground();
Font font = component.getFont();
String id; String id;
String clazz = component.getClass().getSimpleName(); String clazz = component.getClass().getSimpleName();
if (clazz.isEmpty()) { if (clazz.isEmpty()) {
@ -175,6 +176,10 @@ public class ComponentThemeInspectorAction extends DockingAction {
.append(spacer) .append(spacer)
.append("fg: ") .append("fg: ")
.append(fgText) .append(fgText)
.append(tabs)
.append(spacer)
.append("font: ")
.append(font)
.append('\n'); .append('\n');
} }

View file

@ -174,6 +174,18 @@ public class ThemeEditorDialog extends DialogComponentProvider {
updateButtons(); updateButtons();
} }
private void resetSelectedLookAndFeel() {
Swing.runLater(() -> {
try {
combo.removeItemListener(comboListener);
combo.setSelectedItem(themeManager.getActiveTheme().getLookAndFeelType());
}
finally {
combo.addItemListener(comboListener);
}
});
}
private void themeComboChanged(ItemEvent e) { private void themeComboChanged(ItemEvent e) {
if (e.getStateChange() != ItemEvent.SELECTED) { if (e.getStateChange() != ItemEvent.SELECTED) {
@ -181,24 +193,43 @@ public class ThemeEditorDialog extends DialogComponentProvider {
} }
LafType lafType = (LafType) e.getItem(); LafType lafType = (LafType) e.getItem();
Swing.runLater(() -> { if (!themeManager.hasThemeValueChanges()) {
setLookAndFeel(lafType);
return;
}
themeManager.setLookAndFeel(lafType, lafType.usesDarkDefaults()); //@formatter:off
if (lafType == LafType.GTK) { int result = OptionDialog.showOptionDialog(null, "Discard Changes?",
setStatusText( "Changing the Look and Feel type will cause you to lose your changes.\n" +
"Warning - Themes using the GTK LookAndFeel do not support changing java " + "If you would like to keep your changes, cancel this dialog and then save the theme\n" +
"component colors, fonts or icons.", "Would you like to continue?",
MessageType.ERROR); "Lose Changes");
} //@formatter:on
else { if (result == OptionDialog.CANCEL_OPTION) {
setStatusText(""); resetSelectedLookAndFeel();
} return;
colorTree.rebuild(); }
colorTable.reloadAll();
paletteTable.reloadAll(); setLookAndFeel(lafType);
fontTable.reloadAll(); }
iconTable.reloadAll();
}); private void setLookAndFeel(LafType lafType) {
themeManager.setLookAndFeel(lafType, lafType.usesDarkDefaults());
if (lafType == LafType.GTK) {
setStatusText(
"Warning - Themes using the GTK LookAndFeel do not support changing java " +
"component colors, fonts or icons.",
MessageType.WARNING);
}
else {
setStatusText("");
}
colorTree.rebuild();
colorTable.reloadAll();
paletteTable.reloadAll();
fontTable.reloadAll();
iconTable.reloadAll();
} }
private void updateButtons() { private void updateButtons() {

View file

@ -4,9 +4,6 @@
color.bg = [color]system.color.bg.view color.bg = [color]system.color.bg.view
color.fg = [color]system.color.fg.view color.fg = [color]system.color.fg.view
// On some LaFs the tables and trees use the bg color we define. Make that consistent for all LaFs.
[color]Viewport.background = color.bg
color.fg.error = color.palette.red color.fg.error = color.palette.red
color.fg.disabled = color.palette.lightgray color.fg.disabled = color.palette.lightgray
color.bg.uneditable = [color]system.color.bg.control color.bg.uneditable = [color]system.color.bg.control
@ -40,6 +37,27 @@ font.standard = [font]system.font.control
font.monospaced = monospaced-PLAIN-12 font.monospaced = monospaced-PLAIN-12
//
// Java LaF Fixups
//
// Prefer buttons that change on hover
[laf.boolean]Button.rollover = true
[laf.boolean]Toolbar.isRollover = true
// Java 1.6 UI consumes MousePressed event when dismissing popup menu
// which prevents application components from getting this event.
[laf.boolean]PopupMenu.consumeEventOnClose = false
// On some LaFs the tables and trees use the bg color we define. Make that consistent for all LaFs.
[laf.color]Viewport.background = color.bg
// Fix up the default fonts that Java 1.5.0 changed to Courier
[laf.font]TextArea.font = font.monospaced
[laf.font]PasswordField.font = font.monospaced
// Icons files // Icons files
icon.flag = flag.png icon.flag = flag.png
icon.lock = kgpg.png icon.lock = kgpg.png
@ -108,3 +126,15 @@ color.cursor.unfocused = color.palette.darkgray
icon.make.selection = stack.png icon.make.selection = stack.png
[Flat Dark]
[laf.boolean]ToolBar.focusableButtons = true
[Flat Light]
[laf.boolean]ToolBar.focusableButtons = true

View file

@ -94,6 +94,18 @@ public abstract class AbstractThemeReader {
reportDuplicateKey(oldValue, lineNumber); reportDuplicateKey(oldValue, lineNumber);
} }
} }
// 'external' look and feel property used by the UIManager
else if (BooleanPropertyValue.isBooleanKey(key)) {
JavaPropertyValue oldValue =
valueMap.addProperty(parseBooleanProperty(key, value, lineNumber));
reportDuplicateKey(oldValue, lineNumber);
}
else if (StringPropertyValue.isStringKey(key)) {
JavaPropertyValue oldValue =
valueMap.addProperty(parseStringProperty(key, value, lineNumber));
reportDuplicateKey(oldValue, lineNumber);
}
else { else {
error(lineNumber, "Can't process property: " + key + " = " + value); error(lineNumber, "Can't process property: " + key + " = " + value);
} }
@ -143,6 +155,22 @@ public abstract class AbstractThemeReader {
return parsedValue; return parsedValue;
} }
private BooleanPropertyValue parseBooleanProperty(String key, String value, int lineNumber) {
BooleanPropertyValue parsedValue = BooleanPropertyValue.parse(key, value);
if (parsedValue == null) {
error(lineNumber, "Could not parse boolean property value: " + value);
}
return parsedValue;
}
private StringPropertyValue parseStringProperty(String key, String value, int lineNumber) {
StringPropertyValue parsedValue = StringPropertyValue.parse(key, value);
if (parsedValue == null) {
error(lineNumber, "Could not parse String property value: " + value);
}
return parsedValue;
}
private List<Section> readSections(LineNumberReader reader) throws IOException { private List<Section> readSections(LineNumberReader reader) throws IOException {
List<Section> sections = new ArrayList<>(); List<Section> sections = new ArrayList<>();
@ -208,7 +236,7 @@ public abstract class AbstractThemeReader {
} }
/** /**
* Represents all the value found in a section of the theme properties file. Sections are * Represents all the value found in a section of the theme properties file. Sections are
* defined by a line containing just "[section name]" * defined by a line containing just "[section name]"
*/ */
protected class Section { protected class Section {
@ -287,7 +315,7 @@ public abstract class AbstractThemeReader {
} }
/** /**
* Adds a raw line from the file to this section. The line will be parsed into a a * Adds a raw line from the file to this section. The line will be parsed into a a
* key-value pair. * key-value pair.
* @param line the line to be added/parsed * @param line the line to be added/parsed
* @param lineNumber the line number in the file for this line * @param lineNumber the line number in the file for this line

View file

@ -73,7 +73,7 @@ public class ApplicationThemeManager extends ThemeManager {
@Override @Override
public void restoreThemeValues() { public void restoreThemeValues() {
applicationDefaults = getApplicationDefaults(); applicationDefaults = loadApplicationDefaults();
buildCurrentValues(); buildCurrentValues();
lookAndFeelManager.resetAll(javaDefaults); lookAndFeelManager.resetAll(javaDefaults);
notifyThemeChanged(new AllValuesChangedThemeEvent(false)); notifyThemeChanged(new AllValuesChangedThemeEvent(false));
@ -288,6 +288,11 @@ public class ApplicationThemeManager extends ThemeManager {
return false; return false;
} }
@Override
public boolean hasThemeValueChanges() {
return !changedValuesMap.isEmpty();
}
@Override @Override
public void registerFont(Component component, String fontId) { public void registerFont(Component component, String fontId) {
lookAndFeelManager.registerFont(component, fontId); lookAndFeelManager.registerFont(component, fontId);

View file

@ -0,0 +1,79 @@
/* ###
* 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 generic.theme;
import ghidra.util.Msg;
/**
* A Java property value for keys that use boolean values.
*/
public class BooleanPropertyValue extends JavaPropertyValue {
private static final String EXTERNAL_LAF_ID_PREFIX = "[laf.boolean]";
public BooleanPropertyValue(String id, boolean value) {
this(id, null, value);
}
public BooleanPropertyValue(String id, String refId, Boolean value) {
super(id, refId, value);
}
public static boolean isBooleanKey(String key) {
return key.toLowerCase().startsWith(EXTERNAL_LAF_ID_PREFIX);
}
public static BooleanPropertyValue parse(String key, String value) {
String id = fromExternalId(key);
if (isBooleanKey(value)) {
String refId = fromExternalId(value);
return new BooleanPropertyValue(key, refId, null);
}
boolean b = Boolean.parseBoolean(value);
return new BooleanPropertyValue(id, b);
}
private static String fromExternalId(String externalId) {
if (!externalId.toLowerCase().startsWith(EXTERNAL_LAF_ID_PREFIX)) {
return externalId;
}
// We return the raw property name (e.g., TextArea.background), not the normalized name
// (e.g., laf.color.TextArea.background), since the system currently does not provide the
// end-user a way to change these values from the UI.
return externalId.substring(EXTERNAL_LAF_ID_PREFIX.length());
}
@Override
protected Object getUnresolvedReferenceValue(String primaryId, String unresolvedId) {
Msg.warn(this,
"Could not resolve indirect property for \"" + unresolvedId +
"\" for primary id \"" + primaryId + "\", using last resort default");
return false;
}
@Override
protected String toExternalId(String internalId) {
return EXTERNAL_LAF_ID_PREFIX + internalId;
}
@Override
protected String getSerializedValue() {
return Boolean.toString((Boolean) value);
}
}

View file

@ -17,6 +17,8 @@ package generic.theme;
import java.awt.Color; import java.awt.Color;
import org.apache.commons.lang3.StringUtils;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.WebColors; import ghidra.util.WebColors;
import utilities.util.reflection.ReflectionUtilities; import utilities.util.reflection.ReflectionUtilities;
@ -28,6 +30,10 @@ import utilities.util.reflection.ReflectionUtilities;
* and if the class's refId is non-null, then the color value will be null. * and if the class's refId is non-null, then the color value will be null.
*/ */
public class ColorValue extends ThemeValue<Color> { public class ColorValue extends ThemeValue<Color> {
public static final String LAF_ID_PREFIX = "laf.color.";
public static final String EXTERNAL_LAF_ID_PREFIX = "[laf.color]";
private static final String COLOR_ID_PREFIX = "color."; private static final String COLOR_ID_PREFIX = "color.";
private static final String EXTERNAL_PREFIX = "[color]"; private static final String EXTERNAL_PREFIX = "[color]";
@ -65,13 +71,14 @@ public class ColorValue extends ThemeValue<Color> {
return !id.startsWith(COLOR_ID_PREFIX); return !id.startsWith(COLOR_ID_PREFIX);
} }
/** /**
* Returns true if the given key string is a valid external key for a color value * Returns true if the given key string is a valid external key for a color value
* @param key the key string to test * @param key the key string to test
* @return true if the given key string is a valid external key for a color value * @return true if the given key string is a valid external key for a color value
*/ */
public static boolean isColorKey(String key) { public static boolean isColorKey(String key) {
return key.startsWith(COLOR_ID_PREFIX) || key.startsWith(EXTERNAL_PREFIX); return StringUtils.startsWithAny(key, COLOR_ID_PREFIX, EXTERNAL_PREFIX,
EXTERNAL_LAF_ID_PREFIX);
} }
/** /**
@ -115,6 +122,12 @@ public class ColorValue extends ThemeValue<Color> {
if (internalId.startsWith(COLOR_ID_PREFIX)) { if (internalId.startsWith(COLOR_ID_PREFIX)) {
return internalId; return internalId;
} }
if (internalId.startsWith(LAF_ID_PREFIX)) {
String baseId = internalId.substring(LAF_ID_PREFIX.length());
return EXTERNAL_LAF_ID_PREFIX + baseId;
}
return EXTERNAL_PREFIX + internalId; return EXTERNAL_PREFIX + internalId;
} }
@ -122,6 +135,9 @@ public class ColorValue extends ThemeValue<Color> {
if (externalId.startsWith(EXTERNAL_PREFIX)) { if (externalId.startsWith(EXTERNAL_PREFIX)) {
return externalId.substring(EXTERNAL_PREFIX.length()); return externalId.substring(EXTERNAL_PREFIX.length());
} }
if (externalId.startsWith(EXTERNAL_LAF_ID_PREFIX)) {
return LAF_ID_PREFIX + externalId.substring(EXTERNAL_LAF_ID_PREFIX.length());
}
return externalId; return externalId;
} }

View file

@ -18,6 +18,8 @@ package generic.theme;
import java.awt.Font; import java.awt.Font;
import java.text.ParseException; import java.text.ParseException;
import org.apache.commons.lang3.StringUtils;
import ghidra.util.Msg; import ghidra.util.Msg;
/** /**
@ -27,9 +29,14 @@ import ghidra.util.Msg;
* and if the class's refId is non-null, then the font value will be null. * and if the class's refId is non-null, then the font value will be null.
*/ */
public class FontValue extends ThemeValue<Font> { public class FontValue extends ThemeValue<Font> {
public static final String LAF_ID_PREFIX = "laf.font.";
public static final String EXTERNAL_LAF_ID_PREFIX = "[laf.font]";
static final String FONT_ID_PREFIX = "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]"; private static final String EXTERNAL_PREFIX = "[font]";
public static final Font LAST_RESORT_DEFAULT = new Font("monospaced", Font.PLAIN, 12);
private FontModifier modifier; private FontModifier modifier;
/** /**
@ -97,13 +104,14 @@ public class FontValue extends ThemeValue<Font> {
return String.format("%s-%s-%s", font.getName(), getStyleString(font), font.getSize()); return String.format("%s-%s-%s", font.getName(), getStyleString(font), font.getSize());
} }
/** /**
* Returns true if the given key string is a valid external key for a font value * Returns true if the given key string is a valid external key for a font value
* @param key the key string to test * @param key the key string to test
* @return true if the given key string is a valid external key for a font value * @return true if the given key string is a valid external key for a font value
*/ */
public static boolean isFontKey(String key) { public static boolean isFontKey(String key) {
return key.startsWith(FONT_ID_PREFIX) || key.startsWith(EXTERNAL_PREFIX); return StringUtils.startsWithAny(key, FONT_ID_PREFIX, EXTERNAL_PREFIX,
EXTERNAL_LAF_ID_PREFIX);
} }
/** /**
@ -112,7 +120,7 @@ public class FontValue extends ThemeValue<Font> {
* @param key the key to associate the parsed value with * @param key the key to associate the parsed value with
* @param value the font value to parse * @param value the font value to parse
* @return a FontValue with the given key and the parsed value * @return a FontValue with the given key and the parsed value
* @throws ParseException if there is an exception parsing * @throws ParseException if there is an exception parsing
*/ */
public static FontValue parse(String key, String value) throws ParseException { public static FontValue parse(String key, String value) throws ParseException {
String id = fromExternalId(key); String id = fromExternalId(key);
@ -164,6 +172,12 @@ public class FontValue extends ThemeValue<Font> {
if (internalId.startsWith(FONT_ID_PREFIX)) { if (internalId.startsWith(FONT_ID_PREFIX)) {
return internalId; return internalId;
} }
if (internalId.startsWith(LAF_ID_PREFIX)) {
String baseId = internalId.substring(LAF_ID_PREFIX.length());
return EXTERNAL_LAF_ID_PREFIX + baseId;
}
return EXTERNAL_PREFIX + internalId; return EXTERNAL_PREFIX + internalId;
} }
@ -171,6 +185,9 @@ public class FontValue extends ThemeValue<Font> {
if (externalId.startsWith(EXTERNAL_PREFIX)) { if (externalId.startsWith(EXTERNAL_PREFIX)) {
return externalId.substring(EXTERNAL_PREFIX.length()); return externalId.substring(EXTERNAL_PREFIX.length());
} }
if (externalId.startsWith(EXTERNAL_LAF_ID_PREFIX)) {
return LAF_ID_PREFIX + externalId.substring(EXTERNAL_LAF_ID_PREFIX.length());
}
return externalId; return externalId;
} }
@ -198,9 +215,7 @@ public class FontValue extends ThemeValue<Font> {
} }
private static FontValue getRefFontValue(String id, String value) throws ParseException { private static FontValue getRefFontValue(String id, String value) throws ParseException {
if (value.startsWith(EXTERNAL_PREFIX)) { value = fromExternalId(value);
value = value.substring(EXTERNAL_PREFIX.length());
}
int modIndex = value.indexOf("["); int modIndex = value.indexOf("[");
if (modIndex < 0) { if (modIndex < 0) {
return new FontValue(id, fromExternalId(value)); return new FontValue(id, fromExternalId(value));

View file

@ -33,6 +33,7 @@ public class GThemeValueMap {
protected Map<String, ColorValue> colorMap = new HashMap<>(); protected Map<String, ColorValue> colorMap = new HashMap<>();
protected Map<String, FontValue> fontMap = new HashMap<>(); protected Map<String, FontValue> fontMap = new HashMap<>();
protected Map<String, IconValue> iconMap = new HashMap<>(); protected Map<String, IconValue> iconMap = new HashMap<>();
protected Map<String, JavaPropertyValue> propertyMap = new HashMap<>();
/** /**
* Constructs a new empty map. * Constructs a new empty map.
@ -88,6 +89,19 @@ public class GThemeValueMap {
return null; return null;
} }
/**
* Adds the given property value to this map. If a property value already exists in the map with
* the same id, it will be replaced.
* @param value the {@link JavaPropertyValue} to store in the map.
* @return the previous value for the icon key or null if no previous value existed.
*/
public JavaPropertyValue addProperty(JavaPropertyValue value) {
if (value != null) {
return propertyMap.put(value.getId(), value);
}
return null;
}
/** /**
* Returns the current {@link ColorValue} for the given id or null if none exists. * Returns the current {@link ColorValue} for the given id or null if none exists.
* @param id the id to look up a color for * @param id the id to look up a color for
@ -116,7 +130,16 @@ public class GThemeValueMap {
} }
/** /**
* Loads all the values from the given map into this map, replacing values with the * Returns the current {@link JavaPropertyValue} for the given id or null if none exists.
* @param id the id to look up a icon for
* @return the current {@link JavaPropertyValue} for the given id or null if none exists.
*/
public JavaPropertyValue getProperty(String id) {
return propertyMap.get(id);
}
/**
* Loads all the values from the given map into this map, replacing values with the
* same ids. * same ids.
* @param valueMap the map whose values are to be loaded into this map * @param valueMap the map whose values are to be loaded into this map
*/ */
@ -127,6 +150,7 @@ public class GThemeValueMap {
valueMap.colorMap.values().forEach(v -> addColor(v)); valueMap.colorMap.values().forEach(v -> addColor(v));
valueMap.fontMap.values().forEach(v -> addFont(v)); valueMap.fontMap.values().forEach(v -> addFont(v));
valueMap.iconMap.values().forEach(v -> addIcon(v)); valueMap.iconMap.values().forEach(v -> addIcon(v));
valueMap.propertyMap.values().forEach(v -> addProperty(v));
} }
/** /**
@ -153,6 +177,14 @@ public class GThemeValueMap {
return new ArrayList<>(iconMap.values()); return new ArrayList<>(iconMap.values());
} }
/**
* Returns a list of all the {@link JavaPropertyValue}s stored in this map.
* @return a list of all the {@link JavaPropertyValue}s stored in this map.
*/
public List<JavaPropertyValue> getProperties() {
return new ArrayList<>(propertyMap.values());
}
/** /**
* Returns true if a {@link ColorValue} exists in this map for the given id. * Returns true if a {@link ColorValue} exists in this map for the given id.
* @param id the id to check * @param id the id to check
@ -181,11 +213,20 @@ public class GThemeValueMap {
} }
/** /**
* Returns the total number of color, font, and icon values stored in this map * Returns true if an {@link JavaPropertyValue} exists in this map for the given id.
* @return the total number of color, font, and icon values stored in this map * @param id the id to check
* @return true if an {@link JavaPropertyValue} exists in this map for the given id
*/
public boolean containsProperty(String id) {
return propertyMap.containsKey(id);
}
/**
* Returns the total number of color, font, icon and property values stored in this map
* @return the total number of color, font, icon and property values stored in this map
*/ */
public Object size() { public Object size() {
return colorMap.size() + fontMap.size() + iconMap.size(); return colorMap.size() + fontMap.size() + iconMap.size() + propertyMap.size();
} }
/** /**
@ -195,14 +236,16 @@ public class GThemeValueMap {
colorMap.clear(); colorMap.clear();
fontMap.clear(); fontMap.clear();
iconMap.clear(); iconMap.clear();
propertyMap.clear();
} }
/** /**
* Returns true if there are not color, font, or icon values in this map * Returns true if there are not color, font, icon or property values in this map
* @return true if there are not color, font, or icon values in this map * @return true if there are not color, font, icon or property values in this map
*/ */
public boolean isEmpty() { public boolean isEmpty() {
return colorMap.isEmpty() && fontMap.isEmpty() && iconMap.isEmpty(); return colorMap.isEmpty() && fontMap.isEmpty() && iconMap.isEmpty() &&
propertyMap.isEmpty();
} }
/** /**
@ -229,10 +272,18 @@ public class GThemeValueMap {
iconMap.remove(id); iconMap.remove(id);
} }
/**
* removes any {@link JavaPropertyValue} with the given id from this map.
* @param id the id to remove
*/
public void removeProperty(String id) {
propertyMap.remove(id);
}
/** /**
* Returns a new {@link GThemeValueMap} that is only populated by values that don't exist * Returns a new {@link GThemeValueMap} that is only populated by values that don't exist
* in the give map. * in the give map.
* @param base the set of values (usually the default set) to compare against to determine * @param base the set of values (usually the default set) to compare against to determine
* what values are changed. * what values are changed.
* @return a new {@link GThemeValueMap} that is only populated by values that don't exist * @return a new {@link GThemeValueMap} that is only populated by values that don't exist
* in the give map * in the give map
@ -254,13 +305,18 @@ public class GThemeValueMap {
map.addIcon(icon); map.addIcon(icon);
} }
} }
for (JavaPropertyValue property : propertyMap.values()) {
if (!property.equals(base.getProperty(property.getId()))) {
map.addProperty(property);
}
}
return map; return map;
} }
/** /**
* Gets the set of icon (.png, .gif) files that are used by IconValues that came from files * Gets the set of icon (.png, .gif) files that are used by IconValues that came from files
* versus resources in the classpath. These are the icon files that need to be included * versus resources in the classpath. These are the icon files that need to be included when
* when exporting this set of values to a zip file. * exporting this set of values to a zip file.
* @return the set of icon (.png, .gif) files that are used by IconValues that came from files * @return the set of icon (.png, .gif) files that are used by IconValues that came from files
* versus resources in the classpath * versus resources in the classpath
*/ */
@ -268,18 +324,24 @@ public class GThemeValueMap {
Set<File> files = new HashSet<>(); Set<File> files = new HashSet<>();
for (IconValue iconValue : iconMap.values()) { for (IconValue iconValue : iconMap.values()) {
Icon icon = iconValue.getRawValue(); Icon icon = iconValue.getRawValue();
if (icon instanceof UrlImageIcon urlIcon) { if (!(icon instanceof UrlImageIcon urlIcon)) {
String originalPath = urlIcon.getOriginalPath(); continue;
if (originalPath.startsWith(ResourceManager.EXTERNAL_ICON_PREFIX)) { }
URL url = urlIcon.getUrl();
String filePath = url.getFile(); String originalPath = urlIcon.getOriginalPath();
if (filePath != null) { if (!originalPath.startsWith(ResourceManager.EXTERNAL_ICON_PREFIX)) {
File iconFile = new File(filePath); continue;
if (iconFile.exists()) { }
files.add(iconFile);
} URL url = urlIcon.getUrl();
} String filePath = url.getFile();
} if (filePath == null) {
continue;
}
File iconFile = new File(filePath);
if (iconFile.exists()) {
files.add(iconFile);
} }
} }
return files; return files;
@ -287,7 +349,7 @@ public class GThemeValueMap {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(colorMap, fontMap, iconMap); return Objects.hash(colorMap, fontMap, iconMap, propertyMap);
} }
@Override @Override
@ -302,8 +364,10 @@ public class GThemeValueMap {
return false; return false;
} }
GThemeValueMap other = (GThemeValueMap) obj; GThemeValueMap other = (GThemeValueMap) obj;
return Objects.equals(colorMap, other.colorMap) && Objects.equals(fontMap, other.fontMap) && return Objects.equals(colorMap, other.colorMap) &&
Objects.equals(iconMap, other.iconMap); Objects.equals(fontMap, other.fontMap) &&
Objects.equals(iconMap, other.iconMap) &&
Objects.equals(propertyMap, other.propertyMap);
} }
public void checkForUnresolvedReferences() { public void checkForUnresolvedReferences() {
@ -317,6 +381,9 @@ public class GThemeValueMap {
for (IconValue iconValue : iconMap.values()) { for (IconValue iconValue : iconMap.values()) {
iconValue.get(this); iconValue.get(this);
} }
for (JavaPropertyValue propertyValue : propertyMap.values()) {
propertyValue.get(this);
}
} }
/** /**
@ -344,10 +411,18 @@ public class GThemeValueMap {
} }
/** /**
* Returns the resolved color, following indirections as need to get the color ultimately * Returns the set of all Java property ids in this map
* @return the set of all Java property ids in this map
*/
public Set<String> getPropertyIds() {
return propertyMap.keySet();
}
/**
* Returns the resolved color, following indirections as needed to get the color ultimately
* assigned to the given id. * assigned to the given id.
* @param id the id for which to get a color * @param id the id for which to get a color
* @return the resolved color, following indirections as need to get the color ultimately * @return the resolved color, following indirections as needed to get the color ultimately
* assigned to the given id. * assigned to the given id.
*/ */
public Color getResolvedColor(String id) { public Color getResolvedColor(String id) {
@ -359,10 +434,10 @@ public class GThemeValueMap {
} }
/** /**
* Returns the resolved font, following indirections as need to get the font ultimately * Returns the resolved font, following indirections as needed to get the font ultimately
* assigned to the given id. * assigned to the given id.
* @param id the id for which to get a font * @param id the id for which to get a font
* @return the resolved font, following indirections as need to get the font ultimately * @return the resolved font, following indirections as needed to get the font ultimately
* assigned to the given id * assigned to the given id
*/ */
public Font getResolvedFont(String id) { public Font getResolvedFont(String id) {
@ -374,10 +449,10 @@ public class GThemeValueMap {
} }
/** /**
* Returns the resolved icon, following indirections as need to get the icon ultimately * Returns the resolved icon, following indirections as needed to get the icon ultimately
* assigned to the given id. * assigned to the given id.
* @param id the id for which to get an icon * @param id the id for which to get an icon
* @return the resolved icon, following indirections as need to get the icon ultimately * @return the resolved icon, following indirections as needed to get the icon ultimately
* assigned to the given id * assigned to the given id
*/ */
public Icon getResolvedIcon(String id) { public Icon getResolvedIcon(String id) {
@ -388,4 +463,18 @@ public class GThemeValueMap {
return null; return null;
} }
/**
* Returns the resolved property, following indirections as needed to get the property
* ultimately assigned to the given id.
* @param id the id for which to get an property
* @return the resolved property, following indirections as needed to get the property
* ultimately assigned to the given id
*/
public Object getResolvedProperty(String id) {
JavaPropertyValue propertyValue = propertyMap.get(id);
if (propertyValue != null) {
return propertyValue.get(this);
}
return null;
}
} }

View file

@ -19,6 +19,8 @@ import java.text.ParseException;
import javax.swing.Icon; import javax.swing.Icon;
import org.apache.commons.lang3.StringUtils;
import ghidra.util.Msg; import ghidra.util.Msg;
import resources.ResourceManager; import resources.ResourceManager;
import resources.icons.EmptyIcon; import resources.icons.EmptyIcon;
@ -33,14 +35,14 @@ import resources.icons.UrlImageIcon;
public class IconValue extends ThemeValue<Icon> { public class IconValue extends ThemeValue<Icon> {
private static final String EMPTY_ICON_STRING = "EMPTY_ICON"; private static final String EMPTY_ICON_STRING = "EMPTY_ICON";
public static final String LAF_ID_PREFIX = "laf.icon.";
public static final String EXTERNAL_LAF_ID_PREFIX = "[laf.icon]";
static final String ICON_ID_PREFIX = "icon."; static final String ICON_ID_PREFIX = "icon.";
public static final Icon LAST_RESORT_DEFAULT = ResourceManager.getDefaultIcon();
private static final String EXTERNAL_PREFIX = "[icon]"; private static final String EXTERNAL_PREFIX = "[icon]";
public static final Icon LAST_RESORT_DEFAULT = ResourceManager.getDefaultIcon();
private static final int STANDARD_EMPTY_ICON_SIZE = 16; private static final int STANDARD_EMPTY_ICON_SIZE = 16;
private IconModifier modifier; private IconModifier modifier;
/** /**
@ -94,13 +96,14 @@ public class IconValue extends ThemeValue<Icon> {
return icon; return icon;
} }
/** /**
* Returns true if the given key string is a valid external key for an icon value * Returns true if the given key string is a valid external key for an icon value
* @param key the key string to test * @param key the key string to test
* @return true if the given key string is a valid external key for an icon value * @return true if the given key string is a valid external key for an icon value
*/ */
public static boolean isIconKey(String key) { public static boolean isIconKey(String key) {
return key.startsWith(ICON_ID_PREFIX) || key.startsWith(EXTERNAL_PREFIX); return StringUtils.startsWithAny(key, ICON_ID_PREFIX, EXTERNAL_PREFIX,
EXTERNAL_LAF_ID_PREFIX);
} }
/** /**
@ -172,9 +175,7 @@ public class IconValue extends ThemeValue<Icon> {
} }
private static IconValue parseRefIcon(String id, String value) throws ParseException { private static IconValue parseRefIcon(String id, String value) throws ParseException {
if (value.startsWith(EXTERNAL_PREFIX)) { value = fromExternalId(value);
value = value.substring(EXTERNAL_PREFIX.length());
}
int modifierIndex = getModifierIndex(value); int modifierIndex = getModifierIndex(value);
if (modifierIndex < 0) { if (modifierIndex < 0) {
return new IconValue(id, value); return new IconValue(id, value);
@ -213,6 +214,12 @@ public class IconValue extends ThemeValue<Icon> {
if (internalId.startsWith(ICON_ID_PREFIX)) { if (internalId.startsWith(ICON_ID_PREFIX)) {
return internalId; return internalId;
} }
if (internalId.startsWith(LAF_ID_PREFIX)) {
String baseId = internalId.substring(LAF_ID_PREFIX.length());
return EXTERNAL_LAF_ID_PREFIX + baseId;
}
return EXTERNAL_PREFIX + internalId; return EXTERNAL_PREFIX + internalId;
} }
@ -220,6 +227,9 @@ public class IconValue extends ThemeValue<Icon> {
if (externalId.startsWith(EXTERNAL_PREFIX)) { if (externalId.startsWith(EXTERNAL_PREFIX)) {
return externalId.substring(EXTERNAL_PREFIX.length()); return externalId.substring(EXTERNAL_PREFIX.length());
} }
if (externalId.startsWith(EXTERNAL_LAF_ID_PREFIX)) {
return LAF_ID_PREFIX + externalId.substring(EXTERNAL_LAF_ID_PREFIX.length());
}
return externalId; return externalId;
} }

View file

@ -0,0 +1,55 @@
/* ###
* 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 generic.theme;
/**
* A base class that represents a Java UIManager property. This value is used to allow for
* overriding Java UI values using the theme properties files.
*/
public abstract class JavaPropertyValue extends ThemeValue<Object> {
public JavaPropertyValue(String id, String refId, Object value) {
super(id, refId, value);
}
@Override
public boolean isExternal() {
// Java properties are always used to define 'external' UIManager values
return true;
}
@Override
public String getSerializationString() {
String outputId = toExternalId(id);
return outputId + " = " + getSerializedValue();
}
protected abstract String toExternalId(String internalId);
protected abstract String getSerializedValue();
@Override
protected ThemeValue<Object> getReferredValue(GThemeValueMap values, String refId) {
return values.getProperty(refId);
}
@Override
public void installValue(ThemeManager themeManager) {
// We do not currently support changing these values from the UI or API. Assuming that,
// then this method is probably not needed for properties
throw new UnsupportedOperationException();
}
}

View file

@ -0,0 +1,78 @@
/* ###
* 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 generic.theme;
import ghidra.util.Msg;
/**
* A Java property value for keys that use String values.
*/
public class StringPropertyValue extends JavaPropertyValue {
private static final String EXTERNAL_LAF_ID_PREFIX = "[laf.string]";
public StringPropertyValue(String id, String value) {
this(id, null, value);
}
public StringPropertyValue(String id, String refId, String value) {
super(id, refId, value);
}
public static boolean isStringKey(String key) {
return key.toLowerCase().startsWith(EXTERNAL_LAF_ID_PREFIX);
}
public static StringPropertyValue parse(String key, String value) {
String id = fromExternalId(key);
if (isStringKey(value)) {
String refId = fromExternalId(value);
return new StringPropertyValue(id, refId, null);
}
return new StringPropertyValue(id, value);
}
private static String fromExternalId(String externalId) {
if (!externalId.toLowerCase().startsWith(EXTERNAL_LAF_ID_PREFIX)) {
return externalId;
}
// We return the raw property name (e.g., TextArea.background), not the normalized name
// (e.g., laf.color.TextArea.background), since the system currently does not provide the
// end-user a way to change these values from the UI.
return externalId.substring(EXTERNAL_LAF_ID_PREFIX.length());
}
@Override
protected Object getUnresolvedReferenceValue(String primaryId, String unresolvedId) {
Msg.warn(this,
"Could not resolve indirect property for \"" + unresolvedId +
"\" for primary id \"" + primaryId + "\", using last resort default");
return "";
}
@Override
protected String toExternalId(String internalId) {
return EXTERNAL_LAF_ID_PREFIX + internalId;
}
@Override
protected String getSerializedValue() {
return String.valueOf(value);
}
}

View file

@ -211,7 +211,7 @@ public class StubThemeManager extends ThemeManager {
} }
@Override @Override
protected ApplicationThemeDefaults getApplicationDefaults() { protected ApplicationThemeDefaults loadApplicationDefaults() {
return new ApplicationThemeDefaults() { return new ApplicationThemeDefaults() {
@Override @Override

View file

@ -22,7 +22,7 @@ import generic.theme.laf.UiDefaultsMapper;
* and Feel (LaF) is being used. * and Feel (LaF) is being used.
* <P> * <P>
* Various LaFs have different names for common concepts and even define additional concepts not * Various LaFs have different names for common concepts and even define additional concepts not
* listed here. The values in this class are those the application used use regardless of the LaF * listed here. The values in this class are those the application uses use regardless of the LaF
* being used. When we load a specific LaF, a {@link UiDefaultsMapper} specific to that LaF is used * being used. When we load a specific LaF, a {@link UiDefaultsMapper} specific to that LaF is used
* to map its common LaF ids to these standard system ids. The {@link GThemeDefaults} uses these * to map its common LaF ids to these standard system ids. The {@link GThemeDefaults} uses these
* system ids to define colors that can be used throughout the application without using these ids * system ids to define colors that can be used throughout the application without using these ids

View file

@ -70,6 +70,7 @@ public abstract class ThemeManager {
protected LafType activeLafType = activeTheme.getLookAndFeelType(); protected LafType activeLafType = activeTheme.getLookAndFeelType();
protected boolean useDarkDefaults = activeTheme.useDarkDefaults(); protected boolean useDarkDefaults = activeTheme.useDarkDefaults();
// this use our normalized ids (e.g., 'laf.')
protected GThemeValueMap javaDefaults = new GThemeValueMap(); protected GThemeValueMap javaDefaults = new GThemeValueMap();
protected GThemeValueMap currentValues = new GThemeValueMap(); protected GThemeValueMap currentValues = new GThemeValueMap();
@ -89,10 +90,11 @@ public abstract class ThemeManager {
// default behavior is only install to INSTANCE if first time // default behavior is only install to INSTANCE if first time
INSTANCE = this; INSTANCE = this;
} }
applicationDefaults = getApplicationDefaults();
applicationDefaults = loadApplicationDefaults();
} }
protected ApplicationThemeDefaults getApplicationDefaults() { protected ApplicationThemeDefaults loadApplicationDefaults() {
return new PropertyFileThemeDefaults(); return new PropertyFileThemeDefaults();
} }
@ -100,9 +102,35 @@ public abstract class ThemeManager {
Gui.setThemeManager(this); Gui.setThemeManager(this);
} }
/**
* This method is called to create the internal set of theme value used by the application. To
* do this, we use a layered approach to install values, with the last values added overwriting
* any pre-existing values with the same key. The values are added in the following order:
* <pre>
* java defaults -> light values -> dark values -> look and feel values -> property file values -> theme values
* </pre>
* <p>
* At the point this method is called, this is the state of these various values:
* <ul>
* <li>The 'javaValues' are normalized in the form of 'laf.font.TextArea'
* </li>
* <li>The 'applicationDefaults' contains values loaded from the {@code theme.properties}
* files:
* <pre>
* font.listing.base
* font.monospaced
* [color]Viewport.background = color.bg
* [laf.font]TextArea.font = font.monospaced
* [laf.boolean]Button.rollover = true
* </pre>
* </li>
* <li>The 'activeTheme' values are those loaded by the current theme, which has any changes
* made to the default values
* </li>
* </ul>
*/
protected void buildCurrentValues() { protected void buildCurrentValues() {
GThemeValueMap map = new GThemeValueMap(); GThemeValueMap map = new GThemeValueMap();
map.load(javaDefaults); map.load(javaDefaults);
map.load(applicationDefaults.getLightValues()); map.load(applicationDefaults.getLightValues());
if (useDarkDefaults) { if (useDarkDefaults) {
@ -500,6 +528,16 @@ public abstract class ThemeManager {
return false; return false;
} }
/**
* Returns true if any theme values have changed. This does not take into account the current
* Look and Feel. Use {@link #hasThemeChanges()} to also account for changes to the Look and
* Feel.
* @return true if any theme values have changed
*/
public boolean hasThemeValueChanges() {
return false;
}
/** /**
* Returns true if an color for the given Id has been defined * Returns true if an color for the given Id has been defined
* @param id the id to check for an existing color. * @param id the id to check for an existing color.

View file

@ -28,6 +28,7 @@ import ghidra.util.Msg;
* @param <T> the base type this ThemeValue works on (i.e., Colors, Fonts, Icons) * @param <T> the base type this ThemeValue works on (i.e., Colors, Fonts, Icons)
*/ */
public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> { public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> {
protected final String id; protected final String id;
protected final T value; protected final T value;
protected final String referenceId; protected final String referenceId;
@ -36,13 +37,19 @@ public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> {
if (id.equals(referenceId)) { if (id.equals(referenceId)) {
throw new IllegalArgumentException("Can't create a themeValue that referencs itself"); throw new IllegalArgumentException("Can't create a themeValue that referencs itself");
} }
if (id.startsWith("[")) {
throw new IllegalArgumentException(
"Theme values must be constructed with normalized, non-external ids");
}
this.id = id; this.id = id;
this.referenceId = referenceId; this.referenceId = referenceId;
this.value = value; this.value = value;
} }
/** /**
* True if this value is one that is one that is defined outside of the application, such as a * True if this value is one that is one that is defined outside of the application, such as a
* Java Look and Feel key. * Java Look and Feel key.
* @return true if external * @return true if external
*/ */
@ -84,7 +91,7 @@ public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> {
* reference chains, an error stack trace will be generated and the default T value will * reference chains, an error stack trace will be generated and the default T value will
* be returned. In rare situations where it is acceptable for the value to not be resolvable, * be returned. In rare situations where it is acceptable for the value to not be resolvable,
* use the {@link #hasResolvableValue(GThemeValueMap)} method first. * use the {@link #hasResolvableValue(GThemeValueMap)} method first.
* @param values the {@link GThemeValueMap} used to resolve references if this * @param values the {@link GThemeValueMap} used to resolve references if this
* instance doesn't have an actual value. * instance doesn't have an actual value.
* @return the T value for this instance, following references as needed. * @return the T value for this instance, following references as needed.
*/ */
@ -116,7 +123,7 @@ public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> {
* Returns true if the ThemeValue can resolve to the concrete T value (color, font, or icon) * Returns true if the ThemeValue can resolve to the concrete T value (color, font, or icon)
* from the given set of theme values. * from the given set of theme values.
* @param values the set of values to use to try and follow reference chains to ultimately * @param values the set of values to use to try and follow reference chains to ultimately
* resolve the ThemeValue to a an actual T value * resolve the ThemeValue to a an actual T value
* @return true if the ThemeValue can resolve to the concrete T value (color, font, or icon) * @return true if the ThemeValue can resolve to the concrete T value (color, font, or icon)
* from the given set of theme values. * from the given set of theme values.
*/ */
@ -197,7 +204,7 @@ public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> {
/** /**
* Returns the T to be used if the indirect reference couldn't be resolved. * Returns the T to be used if the indirect reference couldn't be resolved.
* @param primaryId the id we are trying to get a value for * @param primaryId the id we are trying to get a value for
* @param unresolvedId the reference id that couldn't be resolved * @param unresolvedId the reference id that couldn't be resolved
* @return the default value to be used if the indirect reference couldn't be resolved. * @return the default value to be used if the indirect reference couldn't be resolved.
*/ */
protected abstract T getUnresolvedReferenceValue(String primaryId, String unresolvedId); protected abstract T getUnresolvedReferenceValue(String primaryId, String unresolvedId);

View file

@ -64,7 +64,7 @@ public class CustomNimbusLookAndFeel extends NimbusLookAndFeel {
} }
protected void installJavaDefaultsIntoThemeManager(UiDefaultsMapper uiDefaultsMapper) { protected void installJavaDefaultsIntoThemeManager(UiDefaultsMapper uiDefaultsMapper) {
GThemeValueMap javaDefaults = uiDefaultsMapper.getJavaDefaults(); GThemeValueMap javaDefaults = uiDefaultsMapper.getNormalizedJavaDefaults();
themeManager.setJavaDefaults(javaDefaults); themeManager.setJavaDefaults(javaDefaults);
} }

View file

@ -30,13 +30,13 @@ public class FlatDarkUiDefaultsMapper extends FlatUiDefaultsMapper {
} }
@Override @Override
protected void assignSystemColorValues() { protected void pickRepresentativeValueForColorGroups() {
super.assignSystemColorValues(); super.pickRepresentativeValueForColorGroups();
// We don't think the FlatDark LaF's view background (Trees, Tables, Lists) is dark // We don't think the FlatDark LaF's view background (Trees, Tables, Lists) is dark
// enough, so we are overriding the view group background and foreground colors // enough, so we are overriding the view group background and foreground colors
assignSystemColorDirect(BG_VIEW_ID, new Color(0x1c1d1e)); setGroupColor(BG_VIEW_ID, new Color(0x1c1d1e));
assignSystemColorDirect(FG_VIEW_ID, WebColors.LIGHT_GRAY); setGroupColor(FG_VIEW_ID, WebColors.LIGHT_GRAY);
} }
@Override @Override

View file

@ -16,7 +16,6 @@
package generic.theme.laf; package generic.theme.laf;
import javax.swing.UIDefaults; import javax.swing.UIDefaults;
import javax.swing.UIManager;
import generic.theme.ApplicationThemeManager; import generic.theme.ApplicationThemeManager;
import generic.theme.LafType; import generic.theme.LafType;
@ -28,16 +27,7 @@ public class FlatLookAndFeelManager extends LookAndFeelManager {
} }
@Override @Override
protected void fixupLookAndFeelIssues() { protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
super.fixupLookAndFeelIssues();
// We have historically managed button focus-ability ourselves. Allow this by default so
// features continue to work as expected, such as right-clicking on ToolButtons.
UIManager.put("ToolBar.focusableButtons", Boolean.TRUE);
}
@Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) {
if (getLookAndFeelType() == LafType.FLAT_DARK) { if (getLookAndFeelType() == LafType.FLAT_DARK) {
return new FlatDarkUiDefaultsMapper(defaults); return new FlatDarkUiDefaultsMapper(defaults);
} }

View file

@ -24,42 +24,42 @@ public class FlatUiDefaultsMapper extends UiDefaultsMapper {
} }
@Override @Override
protected void registerIgnoredLafIds() { protected void registerIgnoredJavaIds() {
super.registerIgnoredLafIds(); super.registerIgnoredJavaIds();
ignoredLafIds.add("Actions.Blue"); ignoredJavaIds.add("Actions.Blue");
ignoredLafIds.add("Actions.Green"); ignoredJavaIds.add("Actions.Green");
ignoredLafIds.add("Actions.Grey"); ignoredJavaIds.add("Actions.Grey");
ignoredLafIds.add("Actions.Greyinline"); ignoredJavaIds.add("Actions.Greyinline");
ignoredLafIds.add("Actions.Red"); ignoredJavaIds.add("Actions.Red");
ignoredLafIds.add("Actions.Yellow"); ignoredJavaIds.add("Actions.Yellow");
ignoredLafIds.add("Objects.BlackText"); ignoredJavaIds.add("Objects.BlackText");
ignoredLafIds.add("Objects.Blue"); ignoredJavaIds.add("Objects.Blue");
ignoredLafIds.add("Objects.Green"); ignoredJavaIds.add("Objects.Green");
ignoredLafIds.add("Objects.GreenAndroid"); ignoredJavaIds.add("Objects.GreenAndroid");
ignoredLafIds.add("Objects.Grey"); ignoredJavaIds.add("Objects.Grey");
ignoredLafIds.add("Objects.Pink"); ignoredJavaIds.add("Objects.Pink");
ignoredLafIds.add("Objects.Purple"); ignoredJavaIds.add("Objects.Purple");
ignoredLafIds.add("Objects.Red"); ignoredJavaIds.add("Objects.Red");
ignoredLafIds.add("Objects.RedStatus"); ignoredJavaIds.add("Objects.RedStatus");
ignoredLafIds.add("Objects.Yellow"); ignoredJavaIds.add("Objects.Yellow");
ignoredLafIds.add("Objects.YellowDark"); ignoredJavaIds.add("Objects.YellowDark");
ignoredLafIds.add("h0.font"); ignoredJavaIds.add("h0.font");
ignoredLafIds.add("h00.font"); ignoredJavaIds.add("h00.font");
ignoredLafIds.add("h1.font"); ignoredJavaIds.add("h1.font");
ignoredLafIds.add("h1.regular.font"); ignoredJavaIds.add("h1.regular.font");
ignoredLafIds.add("h2.font"); ignoredJavaIds.add("h2.font");
ignoredLafIds.add("h2.regular.font"); ignoredJavaIds.add("h2.regular.font");
ignoredLafIds.add("h3.font"); ignoredJavaIds.add("h3.font");
ignoredLafIds.add("h3.regular.font"); ignoredJavaIds.add("h3.regular.font");
ignoredLafIds.add("h4.font"); ignoredJavaIds.add("h4.font");
ignoredLafIds.add("large.font"); ignoredJavaIds.add("large.font");
ignoredLafIds.add("light.font"); ignoredJavaIds.add("light.font");
ignoredLafIds.add("medium.font"); ignoredJavaIds.add("medium.font");
ignoredLafIds.add("mini.font"); ignoredJavaIds.add("mini.font");
ignoredLafIds.add("monospaced.font"); ignoredJavaIds.add("monospaced.font");
ignoredLafIds.add("small.font"); ignoredJavaIds.add("small.font");
} }
} }

View file

@ -30,7 +30,7 @@ public class GtkLookAndFeelManager extends LookAndFeelManager {
} }
@Override @Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
return new UiDefaultsMapper(defaults); return new UiDefaultsMapper(defaults);
} }
} }

View file

@ -70,7 +70,6 @@ public abstract class LookAndFeelManager {
doInstallLookAndFeel(); doInstallLookAndFeel();
processJavaDefaults(); processJavaDefaults();
fixupLookAndFeelIssues(); fixupLookAndFeelIssues();
installGlobalProperties();
installCustomLookAndFeelActions(); installCustomLookAndFeelActions();
updateComponentUis(); updateComponentUis();
} }
@ -157,22 +156,26 @@ public abstract class LookAndFeelManager {
/** /**
* Called when one or more fonts have changed. * Called when one or more fonts have changed.
* @param changedJavaFontIds the set of Java Font ids that are affected by this change * <p>
* This will update the Java {@link UIManager} and trigger a reload of the UIs.
*
* @param changedFontIds the set of Java Font ids that are affected by this change; these are
* the normalized ids
*/ */
public void fontsChanged(Set<String> changedJavaFontIds) { public void fontsChanged(Set<String> changedFontIds) {
UIDefaults defaults = UIManager.getDefaults(); UIDefaults defaults = UIManager.getDefaults();
for (String changedFontId : changedJavaFontIds) { for (String changedFontId : changedFontIds) {
// even though all these derive from the new font, they might be different // even though all these derive from the new font, they might be different
// because of FontModifiers. // because of FontModifiers.
Font font = Gui.getFont(changedFontId); Font font = Gui.getFont(changedFontId);
String lafFontId = normalizedIdToLafIdMap.get(changedFontId); String javaFontId = normalizedIdToLafIdMap.get(changedFontId);
if (lafFontId != null) { if (javaFontId != null) {
// lafFontId is null for group ids // lafFontId is null for group ids
defaults.put(lafFontId, new FontUIResource(font)); defaults.put(javaFontId, new FontUIResource(font));
} }
} }
if (!changedJavaFontIds.isEmpty()) { if (!changedFontIds.isEmpty()) {
updateComponentUis(); updateComponentUis();
} }
@ -231,10 +234,23 @@ public abstract class LookAndFeelManager {
} }
/** /**
* Subclass may override this method to do specific LookAndFeel fix ups * Subclass may override this method to do specific LookAndFeel fixes.
* <p>
* This will get called after default values are loaded. This means that any values installed
* by this method will overwrite any values registered by the theme.
* <p>
* Standard properties, such as strings and booleans, can be set inside of the theme
* properties files. For more complicated UIManager properties, look and feel classes will
* need to override this method and install those directly.
* <p>
* Any property installed here will not fully be part of the theme system, but rather will be
* directly installed into the Java Look and Feel. Thus, properties installed here will be
* hard-coded overrides for the system. If we decided that a hard-coded value should be put
* into the theme system, then we will need to add support for that property type so that it
* can be used when loading the theme files.
*/ */
protected void fixupLookAndFeelIssues() { protected void fixupLookAndFeelIssues() {
// no generic fix-ups at this time. installGlobalFontSizeOverride();
} }
/** /**
@ -244,15 +260,14 @@ public abstract class LookAndFeelManager {
*/ */
protected void processJavaDefaults() { protected void processJavaDefaults() {
UIDefaults defaults = UIManager.getDefaults(); UIDefaults defaults = UIManager.getDefaults();
UiDefaultsMapper uiDefaultsMapper = getUiDefaultsMapper(defaults); UiDefaultsMapper uiDefaultsMapper = createUiDefaultsMapper(defaults);
GThemeValueMap javaDefaults = uiDefaultsMapper.getNormalizedJavaDefaults();
GThemeValueMap javaDefaults = uiDefaultsMapper.getJavaDefaults();
themeManager.setJavaDefaults(javaDefaults); themeManager.setJavaDefaults(javaDefaults);
uiDefaultsMapper.installValuesIntoUIDefaults(themeManager.getCurrentValues()); uiDefaultsMapper.installValuesIntoUIDefaults(themeManager.getCurrentValues());
normalizedIdToLafIdMap = uiDefaultsMapper.getNormalizedIdToLafIdMap(); normalizedIdToLafIdMap = uiDefaultsMapper.getNormalizedIdToLafIdMap();
} }
protected abstract UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults); protected abstract UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults);
protected String findLookAndFeelClassName(String lookAndFeelName) { protected String findLookAndFeelClassName(String lookAndFeelName) {
LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels(); LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels();
@ -277,38 +292,20 @@ public abstract class LookAndFeelManager {
return false; return false;
} }
protected void setKeyBinding(String existingKsText, String newKsText, String[] prefixValues) { protected void setKeyBinding(String existingKsText, String newKsText,
String[] prefixValues) {
KeyStroke existingKs = KeyStroke.getKeyStroke(existingKsText); KeyStroke existingKs = KeyStroke.getKeyStroke(existingKsText);
KeyStroke newKs = KeyStroke.getKeyStroke(newKsText); KeyStroke newKs = KeyStroke.getKeyStroke(newKsText);
UIDefaults uiDefaults = UIManager.getDefaults();
for (String properyPrefix : prefixValues) { for (String properyPrefix : prefixValues) {
Object object = uiDefaults.get(properyPrefix + ".focusInputMap");
UIDefaults defaults = UIManager.getDefaults();
Object object = defaults.get(properyPrefix + ".focusInputMap");
InputMap inputMap = (InputMap) object; InputMap inputMap = (InputMap) object;
Object action = inputMap.get(existingKs); Object action = inputMap.get(existingKs);
inputMap.put(newKs, action); inputMap.put(newKs, action);
} }
} }
private void installGlobalLookAndFeelAttributes() {
// Fix up the default fonts that Java 1.5.0 changed to Courier, which looked terrible.
Font f = new Font("Monospaced", Font.PLAIN, 12);
UIManager.put("PasswordField.font", f);
UIManager.put("TextArea.font", f);
// We like buttons that change on hover, so force that to happen (see Tracker SCR 3966)
UIManager.put("Button.rollover", Boolean.TRUE);
UIManager.put("ToolBar.isRollover", Boolean.TRUE);
}
private void installPopupMenuSettingsOverride() {
// Java 1.6 UI consumes MousePressed event when dismissing popup menu
// which prevents application components from getting this event.
UIManager.put("PopupMenu.consumeEventOnClose", Boolean.FALSE);
}
private void installGlobalFontSizeOverride() { private void installGlobalFontSizeOverride() {
// only set a global size if the property is set // only set a global size if the property is set
@ -375,12 +372,6 @@ public abstract class LookAndFeelManager {
} }
} }
private void installGlobalProperties() {
installGlobalLookAndFeelAttributes();
installGlobalFontSizeOverride();
installPopupMenuSettingsOverride();
}
/** /**
* Searches the given UIDefaults for ids whose value matches the given class * Searches the given UIDefaults for ids whose value matches the given class
* @param defaults the UIDefaults to search * @param defaults the UIDefaults to search

View file

@ -34,7 +34,7 @@ public class MacLookAndFeelManager extends LookAndFeelManager {
} }
@Override @Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
return new MacUiDefaultsMapper(defaults); return new MacUiDefaultsMapper(defaults);
} }

View file

@ -27,7 +27,7 @@ public class MetalLookAndFeelManager extends LookAndFeelManager {
} }
@Override @Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
return new UiDefaultsMapper(defaults); return new UiDefaultsMapper(defaults);
} }
} }

View file

@ -30,12 +30,15 @@ public class MotifLookAndFeelManager extends LookAndFeelManager {
} }
@Override @Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
return new MotifUiDefaultsMapper(defaults); return new MotifUiDefaultsMapper(defaults);
} }
@Override @Override
protected void fixupLookAndFeelIssues() { protected void fixupLookAndFeelIssues() {
super.fixupLookAndFeelIssues();
// //
// The Motif LaF does not bind copy/paste/cut to Control-C/V/X by default. Rather, they // The Motif LaF does not bind copy/paste/cut to Control-C/V/X by default. Rather, they
// only use the COPY/PASTE/CUT keys. The other LaFs bind both shortcuts. // only use the COPY/PASTE/CUT keys. The other LaFs bind both shortcuts.

View file

@ -24,9 +24,9 @@ public class MotifUiDefaultsMapper extends UiDefaultsMapper {
} }
@Override @Override
protected void registerIgnoredLafIds() { protected void registerIgnoredJavaIds() {
super.registerIgnoredLafIds(); super.registerIgnoredJavaIds();
ignoredLafIds.add("controlLightShadow"); ignoredJavaIds.add("controlLightShadow");
} }
} }

View file

@ -103,14 +103,14 @@ public class NimbusLookAndFeelManager extends LookAndFeelManager {
// fix scroll bar grabber disappearing. See // fix scroll bar grabber disappearing. See
// https://bugs.openjdk.java.net/browse/JDK-8134828. This fix looks like it should not cause // https://bugs.openjdk.java.net/browse/JDK-8134828. This fix looks like it should not cause
// harm even if the bug is fixed on the jdk side. // harm even if the bug is fixed on the jdk side.
UIDefaults defaults = UIManager.getDefaults(); UIDefaults uiDefaults = UIManager.getDefaults();
defaults.put("ScrollBar.minimumThumbSize", new Dimension(30, 30)); uiDefaults.put("ScrollBar.minimumThumbSize", new Dimension(30, 30));
// (see NimbusDefaults for key values that can be changed here) // (see NimbusDefaults for key values that can be changed here)
} }
@Override @Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
return new NimbusUiDefaultsMapper(defaults); return new NimbusUiDefaultsMapper(defaults);
} }
} }

View file

@ -28,45 +28,45 @@ public class NimbusUiDefaultsMapper extends UiDefaultsMapper {
} }
@Override @Override
protected void registerIgnoredLafIds() { protected void registerIgnoredJavaIds() {
super.registerIgnoredLafIds(); super.registerIgnoredJavaIds();
ignoredLafIds.add("background"); ignoredJavaIds.add("background");
ignoredLafIds.add("controlLHighlight"); ignoredJavaIds.add("controlLHighlight");
ignoredLafIds.add("nimbusAlertYellow"); ignoredJavaIds.add("nimbusAlertYellow");
ignoredLafIds.add("nimbusBase"); ignoredJavaIds.add("nimbusBase");
ignoredLafIds.add("nimbusBlueGrey"); ignoredJavaIds.add("nimbusBlueGrey");
ignoredLafIds.add("nimbusDisabledText"); ignoredJavaIds.add("nimbusDisabledText");
ignoredLafIds.add("nimbusFocus"); ignoredJavaIds.add("nimbusFocus");
ignoredLafIds.add("nimbusGreen"); ignoredJavaIds.add("nimbusGreen");
ignoredLafIds.add("nimbusInfoBlue"); ignoredJavaIds.add("nimbusInfoBlue");
ignoredLafIds.add("nimbusOrange"); ignoredJavaIds.add("nimbusOrange");
ignoredLafIds.add("nimbusRed"); ignoredJavaIds.add("nimbusRed");
ignoredLafIds.add("nimbusSelectedText"); ignoredJavaIds.add("nimbusSelectedText");
ignoredLafIds.add("nimbusSelection"); ignoredJavaIds.add("nimbusSelection");
ignoredLafIds.add("nimbusSelectionBackground"); ignoredJavaIds.add("nimbusSelectionBackground");
} }
@Override @Override
protected void assignSystemColorValues() { protected void pickRepresentativeValueForColorGroups() {
// different from base class // different from base class
assignSystemColorFromLafId(BG_CONTROL_ID, "Button.background"); setGroupColorUsingJavaRepresentative(BG_CONTROL_ID, "Button.background");
assignSystemColorFromLafId(FG_CONTROL_ID, "Button.foreground"); setGroupColorUsingJavaRepresentative(FG_CONTROL_ID, "Button.foreground");
assignSystemColorFromLafId(BG_BORDER_ID, "nimbusBorder"); setGroupColorUsingJavaRepresentative(BG_BORDER_ID, "nimbusBorder");
assignSystemColorFromLafId(BG_VIEW_ID, "nimbusLightBackground"); setGroupColorUsingJavaRepresentative(BG_VIEW_ID, "nimbusLightBackground");
assignSystemColorFromLafId(FG_VIEW_ID, "controlText"); setGroupColorUsingJavaRepresentative(FG_VIEW_ID, "controlText");
// the following are the same as the base class (we can't just call super because // the following are the same as the base class (we can't just call super because
// it will report errors for missing lafIds such as "window" // it will report errors for missing lafIds such as "window"
assignSystemColorFromLafId(BG_VIEW_SELECTED_ID, "textHighlight"); setGroupColorUsingJavaRepresentative(BG_VIEW_SELECTED_ID, "textHighlight");
assignSystemColorFromLafId(FG_VIEW_SELECTED_ID, "textHighlightText"); setGroupColorUsingJavaRepresentative(FG_VIEW_SELECTED_ID, "textHighlightText");
assignSystemColorFromLafId(FG_DISABLED_ID, "textInactiveText"); setGroupColorUsingJavaRepresentative(FG_DISABLED_ID, "textInactiveText");
assignSystemColorFromLafId(BG_TOOLTIP_ID, "info"); setGroupColorUsingJavaRepresentative(BG_TOOLTIP_ID, "info");
assignSystemColorFromLafId(FG_TOOLTIP_ID, "infoText"); setGroupColorUsingJavaRepresentative(FG_TOOLTIP_ID, "infoText");
} }
@Override @Override
@ -83,8 +83,8 @@ public class NimbusUiDefaultsMapper extends UiDefaultsMapper {
super.installGColorsIntoUIDefaults(); super.installGColorsIntoUIDefaults();
// The Nimbus selected text field color is not honored if the value is a ColorUIResource. // The Nimbus selected text field color is not honored if the value is a ColorUIResource.
// We install GColorUIResources by default. Thus, our setting for this particular // We install GColorUIResources by default. Thus, our setting for this particular
// attribute was being ignored. We set it here to be a GColor, which causes Nimbus // attribute was being ignored. We set it here to be a GColor, which causes Nimbus
// to honor the value. We may need to add more entries here as they are discovered. // to honor the value. We may need to add more entries here as they are discovered.
defaults.put("TextField.selectionForeground", defaults.put("TextField.selectionForeground",

View file

@ -71,9 +71,13 @@ import ghidra.util.Msg;
* *
*/ */
public class UiDefaultsMapper { public class UiDefaultsMapper {
public static final String LAF_COLOR_ID_PREFIX = "laf.color.";
public static final String LAF_FONT_ID_PREFIX = "laf.font."; public static final String LAF_COLOR_ID_PREFIX = ColorValue.LAF_ID_PREFIX;
public static final String LAF_ICON_ID_PREFIX = "laf.icon."; public static final String LAF_FONT_ID_PREFIX = FontValue.LAF_ID_PREFIX;
public static final String LAF_ICON_ID_PREFIX = IconValue.LAF_ID_PREFIX;
/** A prefix for UIManager properties that are not colors, fonts or icons (e.g., boolean) */
public static final String LAF_PROPERTY_PREFIX = "laf.property.";
private static final String LAF_COLOR_PALETTE_PREFIX = "laf.palette.color."; private static final String LAF_COLOR_PALETTE_PREFIX = "laf.palette.color.";
private static final String LAF_FONT_PALETTE_PREFIX = "laf.palette.font."; private static final String LAF_FONT_PALETTE_PREFIX = "laf.palette.font.";
@ -86,22 +90,25 @@ public class UiDefaultsMapper {
protected UIDefaults defaults; protected UIDefaults defaults;
private GThemeValueMap extractedValues; private GThemeValueMap extractedValues;
/** 'normalized' values have keys that start with 'laf.' */
private GThemeValueMap normalizedValues = new GThemeValueMap(); private GThemeValueMap normalizedValues = new GThemeValueMap();
private Map<String, String> lafIdToNormalizedIdMap = new HashMap<>(); /** Maps Look and Feel keys to standardized keys that start with 'laf.' */
protected Set<String> ignoredLafIds = new HashSet<>(); private Map<String, String> javaIdToNormalizedId = new HashMap<>();
protected Set<String> ignoredJavaIds = new HashSet<>();
private Map<String, ColorMatcher> componentToColorMatcherMap = new HashMap<>(); private Map<String, ColorGrouper> componentToColorGrouper = new HashMap<>();
private Map<String, FontMatcher> componentToFontMatcherMap = new HashMap<>(); private Map<String, FontGrouper> componentToFontGrouper = new HashMap<>();
// @formatter:off // @formatter:off
protected ColorMatcher viewColorMatcher = new ColorMatcher(BG_VIEW_ID, protected ColorGrouper viewColorGrouper = new ColorGrouper(BG_VIEW_ID,
FG_VIEW_ID, FG_VIEW_ID,
BG_VIEW_SELECTED_ID, BG_VIEW_SELECTED_ID,
FG_VIEW_SELECTED_ID); FG_VIEW_SELECTED_ID);
protected ColorMatcher tooltipColorMatcher = new ColorMatcher(BG_TOOLTIP_ID, protected ColorGrouper tooltipColorGrouper = new ColorGrouper(BG_TOOLTIP_ID,
FG_TOOLTIP_ID); FG_TOOLTIP_ID);
protected ColorMatcher defaultColorMatcher = new ColorMatcher(BG_CONTROL_ID, protected ColorGrouper defaultColorMatcher = new ColorGrouper(BG_CONTROL_ID,
FG_CONTROL_ID, FG_CONTROL_ID,
BG_VIEW_ID, BG_VIEW_ID,
FG_VIEW_ID, FG_VIEW_ID,
@ -111,9 +118,9 @@ public class UiDefaultsMapper {
BG_TOOLTIP_ID, BG_TOOLTIP_ID,
BG_BORDER_ID); BG_BORDER_ID);
protected FontMatcher menuFontMatcher = new FontMatcher(FONT_MENU_ID); protected FontGrouper menuFontGrouper = new FontGrouper(FONT_MENU_ID);
protected FontMatcher viewFontMatcher = new FontMatcher(FONT_VIEW_ID); protected FontGrouper viewFontGrouper = new FontGrouper(FONT_VIEW_ID);
protected FontMatcher defaultFontMatcher = new FontMatcher(FONT_CONTROL_ID, protected FontGrouper defaultFontMatcher = new FontGrouper(FONT_CONTROL_ID,
FONT_VIEW_ID, FONT_VIEW_ID,
FONT_MENU_ID); FONT_MENU_ID);
@ -128,13 +135,13 @@ public class UiDefaultsMapper {
this.defaults = defaults; this.defaults = defaults;
this.extractedValues = extractColorFontAndIconValuesFromDefaults(); this.extractedValues = extractColorFontAndIconValuesFromDefaults();
assignSystemColorValues(); pickRepresentativeValueForColorGroups();
assignSystemFontValues(); pickRepresentativeValueForFontGroups();
registerIgnoredLafIds(); registerIgnoredJavaIds();
assignColorMatchersToComponentIds(); buildComponentToColorGrouperMap();
assignFontMatchersToComponentIds(); buildComponentToFontGrouperMap();
assignNormalizedColorValues(); assignNormalizedColorValues();
assignNormalizedFontValues(); assignNormalizedFontValues();
@ -142,12 +149,15 @@ public class UiDefaultsMapper {
} }
/** /**
* Returns the normalized id to value map that will be installed into the * Returns the normalized id to value map that will be installed into the theme manager to be
* ApplicationThemeManager to be the user changeable values for affecting the Java * the user changeable values for affecting the Java LookAndFeel colors, fonts, and icons.
* LookAndFeel colors, fonts, and icons * <p>
* The keys in the returned map have been normalized and all start with 'laf.'
*
*
* @return a map of changeable values that affect java LookAndFeel values * @return a map of changeable values that affect java LookAndFeel values
*/ */
public GThemeValueMap getJavaDefaults() { public GThemeValueMap getNormalizedJavaDefaults() {
return normalizedValues; return normalizedValues;
} }
@ -161,22 +171,25 @@ public class UiDefaultsMapper {
// //
// In the UI Defaults, colors use indirect values and fonts and icons use direct values. // In the UI Defaults, colors use indirect values and fonts and icons use direct values.
// Here we install our GColors for the indirect colors. Then we set any font and icon // Here we install our GColors for the indirect colors. Then we set any font and icon
// values that are different than the defaults. // values that are different than the defaults. Finally, we apply any overridden Java
// properties.
// //
installGColorsIntoUIDefaults(); installGColorsIntoUIDefaults();
installOverriddenFontsIntoUIDefaults(currentValues); installOverriddenFontsIntoUIDefaults(currentValues);
installOverriddenIconsIntoUIDefaults(currentValues); installOverriddenIconsIntoUIDefaults(currentValues);
installOverriddenPropertiesIntoUIDefaults(currentValues);
} }
/** /**
* Returns a mapping of normalized LaF Ids so that when fonts and icons get changed using the * Returns a mapping of normalized LaF Ids so that when fonts and icons get changed using the
* normalized ids that are presented to the user, we know which LaF ids need to be updated in * normalized ids that are presented to the user, we know which LaF ids need to be updated in
* the UiDefaults so that the LookAndFeel will pick up and use the changes. * the UiDefaults so that the LookAndFeel will pick up and use the changes.
*
* @return a mapping of normalized LaF ids to original LaF ids. * @return a mapping of normalized LaF ids to original LaF ids.
*/ */
public Map<String, String> getNormalizedIdToLafIdMap() { public Map<String, String> getNormalizedIdToLafIdMap() {
Map<String, String> map = new HashMap<>(); Map<String, String> map = new HashMap<>();
for (Entry<String, String> entry : lafIdToNormalizedIdMap.entrySet()) { for (Entry<String, String> entry : javaIdToNormalizedId.entrySet()) {
String lafId = entry.getKey(); String lafId = entry.getKey();
String standardId = entry.getValue(); String standardId = entry.getValue();
map.put(standardId, lafId); map.put(standardId, lafId);
@ -191,41 +204,41 @@ public class UiDefaultsMapper {
* used to seed the values for the system color and fonts. Subclasses should * used to seed the values for the system color and fonts. Subclasses should
* override this method to add additional ids so they won't show up in the theme values. * override this method to add additional ids so they won't show up in the theme values.
*/ */
protected void registerIgnoredLafIds() { protected void registerIgnoredJavaIds() {
ignoredLafIds.add("desktop"); ignoredJavaIds.add("desktop");
ignoredLafIds.add("activeCaption"); ignoredJavaIds.add("activeCaption");
ignoredLafIds.add("activeCaptionText"); ignoredJavaIds.add("activeCaptionText");
ignoredLafIds.add("activeCaptionBorder"); ignoredJavaIds.add("activeCaptionBorder");
ignoredLafIds.add("inactiveCaption"); ignoredJavaIds.add("inactiveCaption");
ignoredLafIds.add("inactiveCaptionText"); ignoredJavaIds.add("inactiveCaptionText");
ignoredLafIds.add("inactiveCaptionBorder"); ignoredJavaIds.add("inactiveCaptionBorder");
ignoredLafIds.add("window"); ignoredJavaIds.add("window");
ignoredLafIds.add("windowBorder"); ignoredJavaIds.add("windowBorder");
ignoredLafIds.add("windowText"); ignoredJavaIds.add("windowText");
ignoredLafIds.add("menu"); ignoredJavaIds.add("menu");
ignoredLafIds.add("menuText"); ignoredJavaIds.add("menuText");
ignoredLafIds.add("text"); ignoredJavaIds.add("text");
ignoredLafIds.add("textText"); ignoredJavaIds.add("textText");
ignoredLafIds.add("textHighlight"); ignoredJavaIds.add("textHighlight");
ignoredLafIds.add("textHighightText"); ignoredJavaIds.add("textHighightText");
ignoredLafIds.add("textInactiveText"); ignoredJavaIds.add("textInactiveText");
ignoredLafIds.add("control"); ignoredJavaIds.add("control");
ignoredLafIds.add("controlText"); ignoredJavaIds.add("controlText");
ignoredLafIds.add("controlHighlight"); ignoredJavaIds.add("controlHighlight");
ignoredLafIds.add("controlLtHighlight"); ignoredJavaIds.add("controlLtHighlight");
ignoredLafIds.add("controlShadow"); ignoredJavaIds.add("controlShadow");
ignoredLafIds.add("controlDkShadow"); ignoredJavaIds.add("controlDkShadow");
ignoredLafIds.add("info"); ignoredJavaIds.add("info");
ignoredLafIds.add("infoText"); ignoredJavaIds.add("infoText");
ignoredLafIds.add("scrollbar"); ignoredJavaIds.add("scrollbar");
} }
/** /**
* Defines the values to assign to all the system color ids based on the best representative * Defines the values to assign to all the system color ids based on the best representative
* value defined in the {@link BasicLookAndFeel} * value defined in the {@link BasicLookAndFeel}
*/ */
protected void assignSystemColorValues() { protected void pickRepresentativeValueForColorGroups() {
// Originally, these values were assigned to the corresponding concepts as defined // Originally, these values were assigned to the corresponding concepts as defined
// in the BasicLookAndFeel such as "control", "text", etc. Unfortunately, those // in the BasicLookAndFeel such as "control", "text", etc. Unfortunately, those
// conventions are rarely used by specific look and feels. It was discovered that using a // conventions are rarely used by specific look and feels. It was discovered that using a
@ -235,114 +248,118 @@ public class UiDefaultsMapper {
// subclassed where the values can be overridden. See the NimbusUiDefaultsMapper as an // subclassed where the values can be overridden. See the NimbusUiDefaultsMapper as an
// example. // example.
assignSystemColorFromLafId(BG_CONTROL_ID, "Button.background"); setGroupColorUsingJavaRepresentative(BG_CONTROL_ID, "Button.background");
assignSystemColorFromLafId(FG_CONTROL_ID, "Button.foreground"); setGroupColorUsingJavaRepresentative(FG_CONTROL_ID, "Button.foreground");
assignSystemColorFromLafId(BG_BORDER_ID, "InternalFrame.borderColor"); setGroupColorUsingJavaRepresentative(BG_BORDER_ID, "InternalFrame.borderColor");
assignSystemColorFromLafId(BG_VIEW_ID, "TextArea.background"); setGroupColorUsingJavaRepresentative(BG_VIEW_ID, "TextArea.background");
assignSystemColorFromLafId(FG_VIEW_ID, "TextArea.foreground"); setGroupColorUsingJavaRepresentative(FG_VIEW_ID, "TextArea.foreground");
assignSystemColorFromLafId(BG_VIEW_SELECTED_ID, "TextArea.selectionBackground"); setGroupColorUsingJavaRepresentative(BG_VIEW_SELECTED_ID, "TextArea.selectionBackground");
assignSystemColorFromLafId(FG_VIEW_SELECTED_ID, "TextArea.selectionForeground"); setGroupColorUsingJavaRepresentative(FG_VIEW_SELECTED_ID, "TextArea.selectionForeground");
assignSystemColorFromLafId(FG_DISABLED_ID, "Label.disabledForeground"); setGroupColorUsingJavaRepresentative(FG_DISABLED_ID, "Label.disabledForeground");
assignSystemColorFromLafId(BG_TOOLTIP_ID, "ToolTip.background"); setGroupColorUsingJavaRepresentative(BG_TOOLTIP_ID, "ToolTip.background");
assignSystemColorFromLafId(FG_TOOLTIP_ID, "ToolTip.foreground"); setGroupColorUsingJavaRepresentative(FG_TOOLTIP_ID, "ToolTip.foreground");
} }
/** /**
* Assigns the system color id to a color value from the UiDefaults map. * Assigns the system color id to a color value from the UiDefaults map.
* @param systemColorId the system color id to get a value for * @param group the system color id to get a value for
* @param lafId the LaF key to use to retrieve a color from the UiDefaults * @param javaId the LaF key to use to retrieve a color from the UiDefaults
*/ */
protected void assignSystemColorFromLafId(String systemColorId, String lafId) { protected void setGroupColorUsingJavaRepresentative(String group, String javaId) {
Color lafColor = defaults.getColor(lafId); Color javaColor = defaults.getColor(javaId);
if (lafColor == null) { if (javaColor == null) {
Msg.debug(this, "Missing value for system color: \"" + systemColorId + Msg.debug(this, "Missing value for system color: \"" + group +
"\". No value for laf id: \"" + lafId + "\"."); "\". No value for java id: \"" + javaId + "\".");
return; return;
} }
normalizedValues.addColor(new ColorValue(systemColorId, lafColor)); normalizedValues.addColor(new ColorValue(group, javaColor));
} }
/** /**
* Assigns the system color id to a directly specified color and does not use the LaF to populate * This allows clients to hard-code a chosen color for a group
* the system color. *
* @param systemColorId the system color id to assign the given color * @param group the system color id to assign the given color
* @param color the color to be assigned to the system color id * @param color the color to be assigned to the system color id
*/ */
protected void assignSystemColorDirect(String systemColorId, Color color) { protected void setGroupColor(String group, Color color) {
normalizedValues.addColor(new ColorValue(systemColorId, color)); normalizedValues.addColor(new ColorValue(group, color));
} }
/** /**
* Assigns the system font id a directly specified font and does not use the LaF to populate * This allows clients to hard-code a chosen font for a group
* the system font. *
* @param systemFontId the system font id to assign the given font * @param group the system font id to assign the given font
* @param font the font to be assigned to the system font id * @param font the font to be assigned to the system font id
*/ */
protected void assignSystemFontDirect(String systemFontId, Font font) { protected void setGroupFont(String group, Font font) {
normalizedValues.addFont(new FontValue(systemFontId, font)); normalizedValues.addFont(new FontValue(group, font));
}
protected void setComponentFont(String componentName, Font font) {
normalizedValues.addFont(new FontValue(componentName, font));
} }
/** /**
* Defines the values to use for the system fonts. * Defines the font values to use for each group based upon a chosen Java representative.
*/ */
protected void assignSystemFontValues() { protected void pickRepresentativeValueForFontGroups() {
assignSystemFontFromLafId(FONT_CONTROL_ID, "Button.font"); setGroupFontUsingRepresentative(FONT_CONTROL_ID, "Button.font");
assignSystemFontFromLafId(FONT_VIEW_ID, "Table.font"); setGroupFontUsingRepresentative(FONT_VIEW_ID, "Table.font");
assignSystemFontFromLafId(FONT_MENU_ID, "Menu.font"); setGroupFontUsingRepresentative(FONT_MENU_ID, "Menu.font");
} }
private void assignSystemFontFromLafId(String systemFontId, String lafId) { private void setGroupFontUsingRepresentative(String fontGroup, String javaId) {
Font lafFont = extractedValues.getResolvedFont(lafId); Font representativeFont = extractedValues.getResolvedFont(javaId);
if (lafFont == null) { if (representativeFont == null) {
Msg.debug(this, "Missing value for system font: \"" + systemFontId + Msg.debug(this, "Missing value for system font: \"" + fontGroup +
"\". No value for laf id: \"" + lafId + "\"."); "\". No value for java id: \"" + javaId + "\".");
return; return;
} }
normalizedValues.addFont(new FontValue(systemFontId, fromUiResource(lafFont))); normalizedValues.addFont(new FontValue(fontGroup, fromUiResource(representativeFont)));
} }
/** /**
* Assigns the appropriate font matcher to each component in the related component group * Sets the font grouper for each component group
*/ */
protected void assignFontMatchersToComponentIds() { protected void buildComponentToFontGrouperMap() {
defineComponentFontMatcher(MENU_COMPONENTS, menuFontMatcher); mapComponentsToFontGrouper(menuFontGrouper, MENU_COMPONENTS);
defineComponentFontMatcher(VIEW_COMPONENTS, viewFontMatcher); mapComponentsToFontGrouper(viewFontGrouper, VIEW_COMPONENTS);
} }
/** /**
* Assigns the appropriate color matcher to each component in the related component group * Sets the color grouper for each component group
*/ */
protected void assignColorMatchersToComponentIds() { protected void buildComponentToColorGrouperMap() {
defineComponentColorMatcher(VIEW_COMPONENTS, viewColorMatcher); mapComponentsToColorGrouper(viewColorGrouper, VIEW_COMPONENTS);
defineComponentColorMatcher(TOOLTIP_COMPONENTS, tooltipColorMatcher); mapComponentsToColorGrouper(tooltipColorGrouper, TOOLTIP_COMPONENTS);
} }
/** /**
* Assigns every component name in the component group to the given ColorValueMatcher * Assigns every component name in the component group to the given ColorValueMatcher
* @param componentGroups a list of component names * @param grouper the ColorMatcher that will provide the precedence of system ids to
* @param matcher the ColorMatcher that will provide the precedence of system ids to
* search when replacing LaF component specific values * search when replacing LaF component specific values
* @param componentGroup a list of component names
*/ */
private void defineComponentColorMatcher(String[] componentGroups, ColorMatcher matcher) { private void mapComponentsToColorGrouper(ColorGrouper grouper, String... componentGroup) {
for (String componentGroup : componentGroups) { for (String name : componentGroup) {
componentToColorMatcherMap.put(componentGroup, matcher); componentToColorGrouper.put(name, grouper);
} }
} }
/** /**
* Assigns every component name in a component group to the given FontValueMapper * Assigns every component name in a component group to the given FontValueMapper
* @param componentGroups a list of component names * @param grouper the FontValueMatcher that will provide the precedence of system font ids to
* @param matcher the FontValueMatcher that will provide the precedence of ststem font ids to
* search when replacing LaF component specific fonts with a system Font * search when replacing LaF component specific fonts with a system Font
* @param componentGroup a list of component names
*/ */
private void defineComponentFontMatcher(String[] componentGroups, FontMatcher matcher) { private void mapComponentsToFontGrouper(FontGrouper grouper, String... componentGroup) {
for (String componentGroup : componentGroups) { for (String name : componentGroup) {
componentToFontMatcherMap.put(componentGroup, matcher); componentToFontGrouper.put(name, grouper);
} }
} }
@ -356,12 +373,12 @@ public class UiDefaultsMapper {
for (String lafId : list) { for (String lafId : list) {
// we don't want to create java defaults for laf system ids since changing them would // we don't want to create java defaults for laf system ids since changing them would
// have no effect // have no effect
if (ignoredLafIds.contains(lafId)) { if (ignoredJavaIds.contains(lafId)) {
continue; continue;
} }
String createdId = LAF_FONT_ID_PREFIX + lafId; String createdId = LAF_FONT_ID_PREFIX + lafId;
lafIdToNormalizedIdMap.put(lafId, createdId); javaIdToNormalizedId.put(lafId, createdId);
Font lafFont = extractedValues.getResolvedFont(lafId); Font lafFont = extractedValues.getResolvedFont(lafId);
FontValue fontValue = getFontValue(createdId, lafId, lafFont); FontValue fontValue = getFontValue(createdId, lafId, lafFont);
@ -380,7 +397,7 @@ public class UiDefaultsMapper {
Icon icon = extractedValues.getResolvedIcon(lafId); Icon icon = extractedValues.getResolvedIcon(lafId);
if (icon != null) { if (icon != null) {
normalizedValues.addIcon(new IconValue(createdId, icon)); normalizedValues.addIcon(new IconValue(createdId, icon));
lafIdToNormalizedIdMap.put(lafId, createdId); javaIdToNormalizedId.put(lafId, createdId);
} }
} }
} }
@ -393,11 +410,11 @@ public class UiDefaultsMapper {
List<String> list = new ArrayList<>(extractedValues.getColorIds()); List<String> list = new ArrayList<>(extractedValues.getColorIds());
Collections.sort(list); Collections.sort(list);
for (String lafId : list) { for (String lafId : list) {
if (ignoredLafIds.contains(lafId)) { if (ignoredJavaIds.contains(lafId)) {
continue; continue;
} }
String createdId = LAF_COLOR_ID_PREFIX + lafId; String createdId = LAF_COLOR_ID_PREFIX + lafId;
lafIdToNormalizedIdMap.put(lafId, createdId); javaIdToNormalizedId.put(lafId, createdId);
Color lafColor = extractedValues.getResolvedColor(lafId); Color lafColor = extractedValues.getResolvedColor(lafId);
ColorValue colorValue = getColorValue(createdId, lafId, lafColor); ColorValue colorValue = getColorValue(createdId, lafId, lafColor);
@ -486,16 +503,16 @@ public class UiDefaultsMapper {
*/ */
private String findSystemColorId(String lafId, Color lafColor) { private String findSystemColorId(String lafId, Color lafColor) {
String componentName = getComponentName(lafId); String componentName = getComponentName(lafId);
ColorMatcher colorMatcher = componentToColorMatcherMap.get(componentName); ColorGrouper colorMatcher = componentToColorGrouper.get(componentName);
// check in widget specific group first // check in widget specific group first
if (colorMatcher != null) { if (colorMatcher != null) {
String systemId = colorMatcher.getSystemId(lafColor); String systemId = colorMatcher.getGroupId(lafColor);
if (systemId != null) { if (systemId != null) {
return systemId; return systemId;
} }
} }
// not found in widget specific group, check general component groups // not found in widget specific group, check general component groups
return defaultColorMatcher.getSystemId(lafColor); return defaultColorMatcher.getGroupId(lafColor);
} }
/** /**
@ -508,16 +525,16 @@ public class UiDefaultsMapper {
*/ */
private String findSystemFontId(String lafId, Font lafFont) { private String findSystemFontId(String lafId, Font lafFont) {
String componentName = getComponentName(lafId); String componentName = getComponentName(lafId);
FontMatcher fontMatcher = componentToFontMatcherMap.get(componentName); FontGrouper fontMatcher = componentToFontGrouper.get(componentName);
// check in widget specific group first // check in widget specific group first
if (fontMatcher != null) { if (fontMatcher != null) {
String systemId = fontMatcher.getSystemId(lafFont); String systemId = fontMatcher.getGroupId(lafFont);
if (systemId != null) { if (systemId != null) {
return systemId; return systemId;
} }
} }
// not found in widget specific group, check general component groups // not found in widget specific group, check general component groups
return defaultFontMatcher.getSystemId(lafFont); return defaultFontMatcher.getGroupId(lafFont);
} }
/** /**
@ -541,7 +558,7 @@ public class UiDefaultsMapper {
Map<String, GColorUIResource> cachedColors = new HashMap<>(); Map<String, GColorUIResource> cachedColors = new HashMap<>();
for (String lafId : extractedValues.getColorIds()) { for (String lafId : extractedValues.getColorIds()) {
String standardColorId = lafIdToNormalizedIdMap.get(lafId); String standardColorId = javaIdToNormalizedId.get(lafId);
if (standardColorId != null) { if (standardColorId != null) {
GColorUIResource sharedGColor = getSharedGColor(cachedColors, standardColorId); GColorUIResource sharedGColor = getSharedGColor(cachedColors, standardColorId);
defaults.put(lafId, sharedGColor); defaults.put(lafId, sharedGColor);
@ -556,7 +573,7 @@ public class UiDefaultsMapper {
private void installOverriddenIconsIntoUIDefaults(GThemeValueMap currentValues) { private void installOverriddenIconsIntoUIDefaults(GThemeValueMap currentValues) {
for (String lafId : extractedValues.getIconIds()) { for (String lafId : extractedValues.getIconIds()) {
Icon currentIcon = extractedValues.getResolvedIcon(lafId); Icon currentIcon = extractedValues.getResolvedIcon(lafId);
String standardId = lafIdToNormalizedIdMap.get(lafId); String standardId = javaIdToNormalizedId.get(lafId);
Icon overriddenIcon = currentValues.getResolvedIcon(standardId); Icon overriddenIcon = currentValues.getResolvedIcon(standardId);
if (overriddenIcon != null && currentIcon != overriddenIcon) { if (overriddenIcon != null && currentIcon != overriddenIcon) {
defaults.put(lafId, overriddenIcon); defaults.put(lafId, overriddenIcon);
@ -572,7 +589,7 @@ public class UiDefaultsMapper {
private void installOverriddenFontsIntoUIDefaults(GThemeValueMap currentValues) { private void installOverriddenFontsIntoUIDefaults(GThemeValueMap currentValues) {
for (String lafId : extractedValues.getFontIds()) { for (String lafId : extractedValues.getFontIds()) {
Font currentFont = extractedValues.getResolvedFont(lafId); Font currentFont = extractedValues.getResolvedFont(lafId);
String standardId = lafIdToNormalizedIdMap.get(lafId); String standardId = javaIdToNormalizedId.get(lafId);
Font overriddenFont = currentValues.getResolvedFont(standardId); Font overriddenFont = currentValues.getResolvedFont(standardId);
if (overriddenFont != null && overriddenFont != currentFont) { if (overriddenFont != null && overriddenFont != currentFont) {
defaults.put(lafId, new FontUIResource(overriddenFont)); defaults.put(lafId, new FontUIResource(overriddenFont));
@ -580,6 +597,18 @@ public class UiDefaultsMapper {
} }
} }
/**
* Updates all non- (color/font/icon) UIManager properties. These properties are UIManager
* properties that the user has overridden in the {@code theme.properties} files. These
* properties may use any type of value that is not a color/font/icon.
* @param currentValues the theme values that potentially override a laf font value
*/
private void installOverriddenPropertiesIntoUIDefaults(GThemeValueMap currentValues) {
for (JavaPropertyValue property : currentValues.getProperties()) {
defaults.put(property.getId(), property.get(currentValues));
}
}
/** /**
* When putting {@link GColorUIResource} values into the UiDefaults, we need to make sure * When putting {@link GColorUIResource} values into the UiDefaults, we need to make sure
* we use the same instance for the same color. Some LookAndFeels do "==" checks on colors * we use the same instance for the same color. Some LookAndFeels do "==" checks on colors
@ -598,7 +627,7 @@ public class UiDefaultsMapper {
} }
protected void overrideColor(String lafId, String sytemId) { protected void overrideColor(String lafId, String sytemId) {
String normalizedId = lafIdToNormalizedIdMap.get(lafId); String normalizedId = javaIdToNormalizedId.get(lafId);
if (normalizedId == null) { if (normalizedId == null) {
Msg.debug(this, "Missing value for laf id: \"" + lafId); Msg.debug(this, "Missing value for laf id: \"" + lafId);
return; return;
@ -644,54 +673,59 @@ public class UiDefaultsMapper {
* @return a list of all ids that have the given value type * @return a list of all ids that have the given value type
*/ */
private List<String> getLookAndFeelIdsForType(Class<?> clazz) { private List<String> getLookAndFeelIdsForType(Class<?> clazz) {
List<String> colorKeys = new ArrayList<>(); List<String> ids = new ArrayList<>();
List<Object> keyList = IteratorUtils.toList(defaults.keys().asIterator()); List<Object> keyList = IteratorUtils.toList(defaults.keys().asIterator());
for (Object key : keyList) { for (Object key : keyList) {
if (key instanceof String) { if (key instanceof String) {
Object value = defaults.get(key); Object value = defaults.get(key);
if (clazz.isInstance(value)) { if (clazz.isInstance(value)) {
colorKeys.add((String) key); ids.add((String) key);
} }
} }
} }
return colorKeys; return ids;
} }
/** /**
* Used to match values (Colors or Fonts) into appropriate system ids. System ids are searched * Used to match values (Colors or Fonts) into appropriate system groups. System group are
* in the order the system ids are given in the constructor. * searched in the order the groups are given in the constructor.
* <p>
* Groups allow us to use the same group id for many components that by default have the same
* value (Color or Font). This grouper allows us to specify the precedence to use when
* searching for the best group.
*
* @param <T> The theme value type (Color or Font) * @param <T> The theme value type (Color or Font)
*/ */
private abstract class ValueMatcher<T> { private abstract class ValueGrouper<T> {
private Map<T, String> map = new HashMap<>(); private Map<T, String> idsByFont = new HashMap<>();
private List<String> systemIdList; private List<String> groupIds;
private boolean initialized; private boolean initialized;
ValueMatcher(String... systemIds) { ValueGrouper(String... ids) {
systemIdList = new ArrayList<>(Arrays.asList(systemIds)); groupIds = new ArrayList<>(Arrays.asList(ids));
} }
private void initialize() { private void initialize() {
initialized = true; initialized = true;
// process in reverse order so that earlier items in the list can overwrite later // process in reverse order so that earlier items in the list can overwrite later
// items if they have the same font // items if they have the same value
for (int i = systemIdList.size() - 1; i >= 0; i--) { for (int i = groupIds.size() - 1; i >= 0; i--) {
String systemId = systemIdList.get(i); String groupId = groupIds.get(i);
T value = getValueFromJavaDefaults(systemId); T value = getValueFromJavaDefaults(groupId);
if (value != null) { if (value != null) {
map.put(value, systemId); idsByFont.put(value, groupId);
} }
} }
} }
protected abstract T getValueFromJavaDefaults(String systemId); protected abstract T getValueFromJavaDefaults(String systemId);
String getSystemId(T value) { String getGroupId(T value) {
if (!initialized) { if (!initialized) {
initialize(); initialize();
} }
return map.get(value); return idsByFont.get(value);
} }
} }
@ -700,9 +734,9 @@ public class UiDefaultsMapper {
* id that matches a given color. The order that color system ids are added is important and is * id that matches a given color. The order that color system ids are added is important and is
* the precedence order if more than one system color id has the same color. * the precedence order if more than one system color id has the same color.
*/ */
private class ColorMatcher extends ValueMatcher<Color> { private class ColorGrouper extends ValueGrouper<Color> {
ColorMatcher(String... systemIds) { ColorGrouper(String... systemIds) {
super(systemIds); super(systemIds);
} }
@ -718,8 +752,8 @@ public class UiDefaultsMapper {
* that matches a given font. The order that system font ids are added is important and is * that matches a given font. The order that system font ids are added is important and is
* the precedence order if more than one system id has the same font. * the precedence order if more than one system id has the same font.
*/ */
private class FontMatcher extends ValueMatcher<Font> { private class FontGrouper extends ValueGrouper<Font> {
FontMatcher(String... systemIds) { FontGrouper(String... systemIds) {
super(systemIds); super(systemIds);
} }

View file

@ -27,7 +27,7 @@ public class WindowsClassicLookAndFeelManager extends LookAndFeelManager {
} }
@Override @Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
return new UiDefaultsMapper(defaults); return new UiDefaultsMapper(defaults);
} }
} }

View file

@ -27,7 +27,7 @@ public class WindowsLookAndFeelManager extends LookAndFeelManager {
} }
@Override @Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
return new UiDefaultsMapper(defaults); return new UiDefaultsMapper(defaults);
} }

View file

@ -352,7 +352,7 @@ public class ApplicationThemeManagerTest {
} }
@Override @Override
protected ApplicationThemeDefaults getApplicationDefaults() { protected ApplicationThemeDefaults loadApplicationDefaults() {
return new ApplicationThemeDefaults() { return new ApplicationThemeDefaults() {
@Override @Override

View file

@ -22,7 +22,7 @@ import java.awt.Color;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import ghidra.util.WebColors; import ghidra.util.*;
public class ColorValueTest { public class ColorValueTest {
@ -30,6 +30,10 @@ public class ColorValueTest {
@Before @Before
public void setup() { public void setup() {
// disable warning messages when some test values cannot be found
Msg.setErrorLogger(new SpyErrorLogger());
values = new GThemeValueMap(); values = new GThemeValueMap();
} }
@ -150,4 +154,34 @@ public class ColorValueTest {
assertEquals("color.parent", value.getReferenceId()); assertEquals("color.parent", value.getReferenceId());
assertNull(value.getRawValue()); assertNull(value.getRawValue());
} }
@Test
public void testJavaColorValueRoundTrip() {
ColorValue value = ColorValue.parse("[laf.color]TextArea.background", "red");
values.addColor(value);
assertEquals("laf.color.TextArea.background", value.getId());
assertEquals(Color.RED, value.get(values));
assertEquals("[laf.color]TextArea.background = #ff0000 // Red",
value.getSerializationString());
}
@Test
public void testInheritsFrom_JavaValues() {
ColorValue parent = ColorValue.parse("[laf.color]TextArea.background", "red");
values.addColor(parent);
ColorValue value =
ColorValue.parse("[laf.color]Button.background", "[laf.color]TextArea.background");
values.addColor(value);
//
// Note: ColorValue.parse() works on external ids or normalized ids.
//
// ColorValue() constructor and inheritsFrom() only work on normalized ids
//
assertTrue(value.inheritsFrom("laf.color.TextArea.background", values));
}
} }

View file

@ -23,12 +23,19 @@ import java.text.ParseException;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import ghidra.util.Msg;
import ghidra.util.SpyErrorLogger;
public class FontValueTest { public class FontValueTest {
private static Font FONT = new Font("Dialog", Font.PLAIN, 12); private static Font FONT = new Font("Dialog", Font.PLAIN, 12);
private GThemeValueMap values; private GThemeValueMap values;
@Before @Before
public void setup() { public void setup() {
// disable warning messages when some test values cannot be found
Msg.setErrorLogger(new SpyErrorLogger());
values = new GThemeValueMap(); values = new GThemeValueMap();
} }
@ -140,4 +147,33 @@ public class FontValueTest {
assertFalse(grandParent.inheritsFrom("font.test", values)); assertFalse(grandParent.inheritsFrom("font.test", values));
} }
@Test
public void testJavaFontValueRoundTrip() throws Exception {
FontValue value = FontValue.parse("[laf.font]Button.font", "monospaced-PLAIN-12");
values.addFont(value);
assertEquals("laf.font.Button.font", value.getId());
assertEquals(new Font("monospaced", Font.PLAIN, 12), value.get(values));
assertEquals("[laf.font]Button.font = monospaced-PLAIN-12",
value.getSerializationString());
}
@Test
public void testInheritsFrom_JavaValues() throws Exception {
FontValue parent = FontValue.parse("[laf.font]Button.font", "monospaced-PLAIN-12");
values.addFont(parent);
FontValue value =
FontValue.parse("[laf.font]ToggleButton.font", "[laf.font]Button.font");
values.addFont(value);
//
// Note: ColorValue.parse() works on external ids or normalized ids.
//
// ColorValue() constructor and inheritsFrom() only work on normalized ids
//
assertTrue(value.inheritsFrom("laf.font.Button.font", values));
}
} }

View file

@ -83,6 +83,8 @@ public class GThemeTest extends AbstractGenericTest {
theme.setColor("foo.bar", Color.GREEN); theme.setColor("foo.bar", Color.GREEN);
theme.setColorRef("foo.bar.xyz", "foo.bar"); theme.setColorRef("foo.bar.xyz", "foo.bar");
theme.setColor("laf.color.TextArea.background", Color.GREEN);
theme.setFont("font.a.1", COURIER); theme.setFont("font.a.1", COURIER);
theme.setFont("font.a.2", DIALOG); theme.setFont("font.a.2", DIALOG);
theme.setFontRef("font.a.3", "font.a.1"); theme.setFontRef("font.a.3", "font.a.1");
@ -110,6 +112,7 @@ public class GThemeTest extends AbstractGenericTest {
assertEquals(Color.RED, theme.getColor("color.a.4").get(theme)); 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").get(theme));
assertEquals(Color.GREEN, theme.getColor("foo.bar.xyz").get(theme)); assertEquals(Color.GREEN, theme.getColor("foo.bar.xyz").get(theme));
assertEquals(Color.GREEN, theme.getColor("laf.color.TextArea.background").get(theme));
assertEquals(COURIER, theme.getFont("font.a.1").get(theme)); assertEquals(COURIER, theme.getFont("font.a.1").get(theme));
assertEquals(DIALOG, theme.getFont("font.a.2").get(theme)); assertEquals(DIALOG, theme.getFont("font.a.2").get(theme));

View file

@ -24,6 +24,8 @@ import javax.swing.Icon;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import ghidra.util.Msg;
import ghidra.util.SpyErrorLogger;
import resources.MultiIcon; import resources.MultiIcon;
import resources.ResourceManager; import resources.ResourceManager;
import resources.icons.EmptyIcon; import resources.icons.EmptyIcon;
@ -35,6 +37,10 @@ public class IconValueTest {
@Before @Before
public void setup() { public void setup() {
// disable warning messages when some test values cannot be found
Msg.setErrorLogger(new SpyErrorLogger());
values = new GThemeValueMap(); values = new GThemeValueMap();
} }
@ -225,4 +231,37 @@ public class IconValueTest {
assertEquals("icon.test = EMPTY_ICON[size(22,13)]", value.getSerializationString()); assertEquals("icon.test = EMPTY_ICON[size(22,13)]", value.getSerializationString());
} }
@Test
public void testJavaIconValueRoundTrip() throws Exception {
IconValue value =
IconValue.parse("[laf.icon]FileChooser.homeFolderIcon", "images/go-home.png");
values.addIcon(value);
assertEquals("laf.icon.FileChooser.homeFolderIcon", value.getId());
assertEquals(ResourceManager.loadIcon("images/go-home.png"), value.get(values));
assertEquals("[laf.icon]FileChooser.homeFolderIcon = images/go-home.png",
value.getSerializationString());
}
@Test
public void testInheritsFrom_JavaValues() throws Exception {
IconValue parent =
IconValue.parse("[laf.icon]FileChooser.homeFolderIcon", "images/go-home.png");
values.addIcon(parent);
IconValue value =
IconValue.parse("[laf.icon]FileView.computerIcon",
"[laf.icon]FileChooser.homeFolderIcon");
values.addIcon(value);
//
// Note: IconValue.parse() works on external ids or normalized ids.
//
// IconValue() constructor and inheritsFrom() only work on normalized ids
//
assertTrue(value.inheritsFrom("laf.icon.FileChooser.homeFolderIcon", values));
}
} }

View file

@ -36,8 +36,8 @@ public class ThemePropertyFileReaderTest {
@Test @Test
public void testDefaults() throws IOException { public void testDefaults() throws IOException {
//@formatter:off //@formatter:off
ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n", ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]", "[Defaults]",
" color.b.1 = white", // WHITE " color.b.1 = white", // WHITE
" color.b.2 = #ff0000", // RED " color.b.2 = #ff0000", // RED
" color.b.3 = 0x008000", // GREEN " color.b.3 = 0x008000", // GREEN
@ -53,12 +53,18 @@ public class ThemePropertyFileReaderTest {
" icon.a.12 = icon.a.10[size(17,21)]", " icon.a.12 = icon.a.10[size(17,21)]",
" icon.a.13 = core.png[size(17,21)]", " icon.a.13 = core.png[size(17,21)]",
" icon.a.14 = icon.a.10{core.png[size(4,4)][move(8, 8)]}", " icon.a.14 = icon.a.10{core.png[size(4,4)][move(8, 8)]}",
" [laf.font]PasswordField.font = font.a.8",
" [laf.font]TextArea.font = dialog-PLAIN-14",
" [laf.color]TextArea.background = color.b.1",
" [laf.string]Fake.title = This is my title",
" [laf.string]OtherFake.title = [laf.string]Fake.title",
" [laf.boolean]PopupMenu.consumeEventOnClose = false",
""))); "")));
//@formatter:on //@formatter:on
Color halfAlphaRed = new Color(0x80ff0000, true); Color halfAlphaRed = new Color(0x80ff0000, true);
GThemeValueMap values = reader.getDefaultValues(); GThemeValueMap values = reader.getDefaultValues();
assertEquals(15, values.size()); assertEquals(21, values.size());
assertEquals(WHITE, getColor(values, "color.b.1")); assertEquals(WHITE, getColor(values, "color.b.1"));
assertEquals(RED, getColor(values, "color.b.2")); assertEquals(RED, getColor(values, "color.b.2"));
@ -85,21 +91,27 @@ public class ThemePropertyFileReaderTest {
icon = getIcon(values, "icon.a.14"); icon = getIcon(values, "icon.a.14");
assertTrue(icon instanceof MultiIcon); assertTrue(icon instanceof MultiIcon);
Font f = new Font("dialog", Font.PLAIN, 14);
assertEquals(f, getFont(values, "laf.font.PasswordField.font")); // direct font
assertEquals(f, getFont(values, "laf.font.TextArea.font")); // font reference
assertEquals("This is my title", getLafString(values, "Fake.title"));
assertEquals("This is my title", getLafString(values, "OtherFake.title"));
assertEquals(false, getLafBoolean(values, "PopupMenu.consumeEventOnClose"));
} }
@Test @Test
public void testDarkDefaults() throws IOException { public void testDarkDefaults() throws IOException {
//@formatter:off //@formatter:off
ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n", ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]", "[Defaults]",
" color.b.1 = red", " color.b.1 = red",
" color.b.2 = red", " color.b.2 = red",
" color.b.3 = red", " color.b.3 = red",
" color.b.4 = red", " color.b.4 = red",
" color.b.5 = red", " color.b.5 = red",
" color.b.6 = red", " color.b.6 = red",
" color.b.7 = red", " color.b.7 = red",
"[Dark Defaults]", "[Dark Defaults]",
" color.b.1 = white", // WHITE " color.b.1 = white", // WHITE
" color.b.2 = #ff0000", // RED " color.b.2 = #ff0000", // RED
" color.b.3 = 0x008000", // GREEN " color.b.3 = 0x008000", // GREEN
@ -126,11 +138,11 @@ public class ThemePropertyFileReaderTest {
@Test @Test
public void testBothDefaultsAndDarkDefaultsInSameFile() throws IOException { public void testBothDefaultsAndDarkDefaultsInSameFile() throws IOException {
//@formatter:off //@formatter:off
ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n", ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]", "[Defaults]",
" color.b.1 = white", // WHITE " color.b.1 = white", // WHITE
" color.b.2 = #ff0000", // RED " color.b.2 = #ff0000", // RED
"[Dark Defaults]", "[Dark Defaults]",
" color.b.1 = black", // BLACK " color.b.1 = black", // BLACK
" color.b.2 = #0000ff", // BLUE " color.b.2 = #0000ff", // BLUE
""))); "")));
@ -151,14 +163,14 @@ public class ThemePropertyFileReaderTest {
@Test @Test
public void testLookAndFeelValues() throws IOException { public void testLookAndFeelValues() throws IOException {
//@formatter:off //@formatter:off
ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n", ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]", "[Defaults]",
" color.b.1 = white", " color.b.1 = white",
"[Dark Defaults]", "[Dark Defaults]",
" color.b.1 = black", " color.b.1 = black",
"[Metal]", "[Metal]",
" color.b.1 = red", " color.b.1 = red",
"[Nimbus]", "[Nimbus]",
" color.b.1 = green", " color.b.1 = green",
""))); "")));
//@formatter:on //@formatter:on
@ -188,8 +200,8 @@ public class ThemePropertyFileReaderTest {
@Test @Test
public void testParseColorError() throws IOException { public void testParseColorError() throws IOException {
//@formatter:off //@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n", ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]", "[Defaults]",
" color.b.1 = white", // WHITE " color.b.1 = white", // WHITE
" color.b.2 = sdfsdf", // RED " color.b.2 = sdfsdf", // RED
""))); "")));
@ -204,11 +216,11 @@ public class ThemePropertyFileReaderTest {
@Test @Test
public void testParseFontError() throws IOException { public void testParseFontError() throws IOException {
//@formatter:off //@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n", ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]", "[Defaults]",
" font.b.1 = Dialog-PLAIN-14", " font.b.1 = Dialog-PLAIN-14",
" font.b.2 = Dialog-PLANE-13", " font.b.2 = Dialog-PLANE-13",
" font.b.3 = Dialog-BOLD-ITALIC", " font.b.3 = Dialog-BOLD-ITALIC",
""))); "")));
//@formatter:on //@formatter:on
List<String> errors = reader.getErrors(); List<String> errors = reader.getErrors();
@ -219,10 +231,10 @@ public class ThemePropertyFileReaderTest {
@Test @Test
public void testParseFontModiferError() throws IOException { public void testParseFontModiferError() throws IOException {
//@formatter:off //@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n", ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]", "[Defaults]",
" font.b.1 = Dialog-PLAIN-14", " font.b.1 = Dialog-PLAIN-14",
" font.b.2 = (font.b.1[)", " font.b.2 = (font.b.1[)",
""))); "")));
//@formatter:on //@formatter:on
List<String> errors = reader.getErrors(); List<String> errors = reader.getErrors();
@ -233,10 +245,10 @@ public class ThemePropertyFileReaderTest {
@Test @Test
public void testIconNoRightHandValueError() throws IOException { public void testIconNoRightHandValueError() throws IOException {
//@formatter:off //@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n", ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]", "[Defaults]",
" icon.b.1 = core.png", " icon.b.1 = core.png",
" icon.b.2 = ", " icon.b.2 = ",
""))); "")));
//@formatter:on //@formatter:on
List<String> errors = reader.getErrors(); List<String> errors = reader.getErrors();
@ -247,8 +259,8 @@ public class ThemePropertyFileReaderTest {
@Test @Test
public void testColorIdDefinedInNonDefaultsSectionOnly() throws IOException { public void testColorIdDefinedInNonDefaultsSectionOnly() throws IOException {
//@formatter:off //@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n", ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]", "[Defaults]",
" color.foo = red", " color.foo = red",
"[Dark Defaults]", "[Dark Defaults]",
" color.bar = blue", " color.bar = blue",
@ -264,7 +276,7 @@ public class ThemePropertyFileReaderTest {
@Test @Test
public void testFontIdDefinedInNonDefaultsSectionOnly() throws IOException { public void testFontIdDefinedInNonDefaultsSectionOnly() throws IOException {
//@formatter:off //@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n", ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]", "[Defaults]",
"[Dark Defaults]", "[Dark Defaults]",
" font.bar = dialog-PLAIN-14", " font.bar = dialog-PLAIN-14",
@ -280,7 +292,7 @@ public class ThemePropertyFileReaderTest {
@Test @Test
public void testIconIdDefinedInNonDefaultsSectionOnly() throws IOException { public void testIconIdDefinedInNonDefaultsSectionOnly() throws IOException {
//@formatter:off //@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n", ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]", "[Defaults]",
"[Dark Defaults]", "[Dark Defaults]",
" icon.bar = core.png", " icon.bar = core.png",
@ -296,8 +308,8 @@ public class ThemePropertyFileReaderTest {
@Test @Test
public void testDefaultSectionMustBeFirst() throws Exception { public void testDefaultSectionMustBeFirst() throws Exception {
//@formatter:off //@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n", ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Dark Defaults]", "[Dark Defaults]",
" color.foo = red", " color.foo = red",
"[Defaults]", "[Defaults]",
" color.bar = blue", " color.bar = blue",
@ -326,6 +338,16 @@ public class ThemePropertyFileReaderTest {
return icon.get(values); return icon.get(values);
} }
private String getLafString(GThemeValueMap values, String id) {
StringPropertyValue value = (StringPropertyValue) values.getProperty(id);
return (String) value.get(values);
}
private boolean getLafBoolean(GThemeValueMap values, String id) {
BooleanPropertyValue value = (BooleanPropertyValue) values.getProperty(id);
return (Boolean) value.get(values);
}
private class SilentThemePropertyFileReader extends ThemePropertyFileReader { private class SilentThemePropertyFileReader extends ThemePropertyFileReader {
protected SilentThemePropertyFileReader(String source, Reader reader) throws IOException { protected SilentThemePropertyFileReader(String source, Reader reader) throws IOException {

View file

@ -28,6 +28,8 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import generic.theme.*; import generic.theme.*;
import ghidra.util.Msg;
import ghidra.util.SpyErrorLogger;
import resources.ResourceManager; import resources.ResourceManager;
public class UIDefaultsMapperTest { public class UIDefaultsMapperTest {
@ -40,6 +42,10 @@ public class UIDefaultsMapperTest {
@Before @Before
public void setup() { public void setup() {
// disable warning messages when some default UI values cannot be found
Msg.setErrorLogger(new SpyErrorLogger());
defaults = createDefaults(); defaults = createDefaults();
defaults.put("control", Color.RED); defaults.put("control", Color.RED);
defaults.put("Button.background", Color.RED); defaults.put("Button.background", Color.RED);
@ -51,7 +57,7 @@ public class UIDefaultsMapperTest {
@Test @Test
public void testGetJavaDefaults() { public void testGetJavaDefaults() {
mapper = new UiDefaultsMapper(defaults); mapper = new UiDefaultsMapper(defaults);
GThemeValueMap javaDefaults = mapper.getJavaDefaults(); GThemeValueMap javaDefaults = mapper.getNormalizedJavaDefaults();
assertEquals(Color.RED, javaDefaults.getResolvedColor("system.color.bg.control")); assertEquals(Color.RED, javaDefaults.getResolvedColor("system.color.bg.control"));
assertEquals(Color.RED, javaDefaults.getResolvedColor("laf.color.Button.background")); assertEquals(Color.RED, javaDefaults.getResolvedColor("laf.color.Button.background"));
@ -71,7 +77,7 @@ public class UIDefaultsMapperTest {
defaults.put("RadioButton.background", Color.BLUE); // Blue not defined in a color group defaults.put("RadioButton.background", Color.BLUE); // Blue not defined in a color group
mapper = new UiDefaultsMapper(defaults); mapper = new UiDefaultsMapper(defaults);
GThemeValueMap javaDefaults = mapper.getJavaDefaults(); GThemeValueMap javaDefaults = mapper.getNormalizedJavaDefaults();
// expecting two palette groups to be created // expecting two palette groups to be created
String greenPalette = findPaletteColor(javaDefaults, Color.GREEN); String greenPalette = findPaletteColor(javaDefaults, Color.GREEN);
@ -90,7 +96,7 @@ public class UIDefaultsMapperTest {
defaults.put("ToggleButton.font", SMALL_FONT); // Green not defined in a color group defaults.put("ToggleButton.font", SMALL_FONT); // Green not defined in a color group
mapper = new UiDefaultsMapper(defaults); mapper = new UiDefaultsMapper(defaults);
GThemeValueMap javaDefaults = mapper.getJavaDefaults(); GThemeValueMap javaDefaults = mapper.getNormalizedJavaDefaults();
assertDirectFont(javaDefaults, "laf.palette.font.01", SMALL_FONT); assertDirectFont(javaDefaults, "laf.palette.font.01", SMALL_FONT);
assertIndirectFont(javaDefaults, "laf.font.ToggleButton.font", "laf.palette.font.01"); assertIndirectFont(javaDefaults, "laf.font.ToggleButton.font", "laf.palette.font.01");

View file

@ -210,7 +210,7 @@
<target name="___chkstk_ms"/> <target name="___chkstk_ms"/>
<pcode> <pcode>
<body><![CDATA[ <body><![CDATA[
RSP = RSP + 0; RSP = RSP + 8;
]]></body> ]]></body>
</pcode> </pcode>
</callfixup> </callfixup>