Merge remote-tracking branch 'origin/patch'

This commit is contained in:
Ryan Kurtz
2023-10-13 07:16:42 -04:00
45 changed files with 1087 additions and 445 deletions
@@ -1188,13 +1188,11 @@ void CollapseStructure::orderLoopBodies(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 *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
loopbottom = (*loopbodyiter).getCurrentBounds(&looptop,&graph);
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)
loopbottom = (FlowBlock *)0;
}
if (likelylistfull) return true;
// If we reach here, need to generate likely gotos for a new inner loop
if (likelylistfull && likelyiter != likelygoto.end())
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
TraceDAG tracer(likelygoto);
if (loopbottom != (FlowBlock *)0) {
@@ -1216,7 +1216,6 @@ bool CollapseStructure::updateLoopBody(void)
(*loopbodyiter).setExitMarks(&graph); // Set the bounds of the TraceDAG
}
else {
finaltrace = true;
for(uint4 i=0;i<graph.getSize();++i) {
FlowBlock *bl = graph.getBlock(i);
if (bl->sizeIn() == 0)
@@ -1225,11 +1224,15 @@ bool CollapseStructure::updateLoopBody(void)
}
tracer.initialize();
tracer.pushBranches();
likelylistfull = true; // Mark likelygoto generation complete for current loop or DAG
if (loopbottom != (FlowBlock *)0) {
(*loopbodyiter).emitLikelyEdges(likelygoto,&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();
return true;
}
@@ -333,10 +333,10 @@ color.fg.listing.bytes = orange
<P>
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
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:
<P>
<BLOCKUOTE>
@@ -454,7 +454,7 @@ color.fg.listing.bytes = orange
<BLOCKQUOTE>
<PRE>
<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>
</PRE>
@@ -522,7 +522,7 @@ color.fg.listing.bytes = orange
<BLOCKQUOTE>
<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
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>.
@@ -38,8 +38,8 @@
<CODE>ColorValue</CODE>, <CODE>FontValue</CODE>, and
<CODE>IconValue</CODE>. Resource values are stored in these <CODE>ThemeValue</CODE> sub-classes
because the value can be
either a concrete value or be 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
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 a reference
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
is a convenience method called <CODE>get()</CODE> on <CODE>ThemeValue</CODE>s that takes a
@@ -2,7 +2,7 @@
<HTML>
<HEAD>
<TITLE>General Overivew</TITLE>
<TITLE>Theming Overivew</TITLE>
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
</HEAD>
@@ -86,7 +86,7 @@
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
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
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>
@@ -233,9 +233,9 @@
<BlOCKQUOTE>
<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
the <A href="ThemingDeveloperDocs.html#Resource_Ids">Developer Documentation</A> for more details on the property ID
format and naming conventions.</P>
use a common format to help make sorting and viewing properties more intuitive as to their use.
See the <A href="ThemingDeveloperDocs.html#Resource_Ids">Developer Documentation</A> for more
details on the property ID format and naming conventions.</P>
</BlOCKQUOTE>
<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
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
theme name, the Look and Feel name, and whether the theme uses standard defaults or dark
defaults. Finally, there is a list of overridden property "name = value" lines. The format
is:</P>
<code>&lt;home&gt;/.ghidra/.ghidra-&lt;version&gt/themes</code>. The first three properties
are always the theme name, the Look and Feel name, and whether the theme uses standard
defaults or dark defaults. Finally, there is a list of overridden property "name = value"
lines. The format is:</P>
<CODE>
<PRE>
name = [theme name]
@@ -270,18 +270,18 @@
color.bg = Black
color.bg.foo = #012345
[color]Panel.background = Red
font.button = dialog-PLAIN-14
icon.refresh = images/reload3.png
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>
<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]",
or "[icon]" prepended to their property name. These brackets are only used to aid in
parsing this file. When the properties are used in Ghidra, the bracketed prefixes are
java defined properties don't start with these prefixes, they will have "[laf.color]",
"[laf.font]", or "[laf.icon]" prepended to their property name. These brackets are only used
to aid in parsing this file. When the properties are used in Ghidra, the bracketed prefixes are
removed.</P>
<P>Also, note that the values of these properties can reference other property names. If the
@@ -24,7 +24,7 @@ import ghidra.util.task.Task;
/**
* 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
* is typical of non-modal plugins.
* <p>
@@ -32,7 +32,7 @@ import ghidra.util.task.Task;
* with the dialog, such as in your plugin's {@code dispose()} method.
* <p>
* 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.
*
* @see DialogComponentProvider
@@ -62,12 +62,7 @@ public class ReusableDialogComponentProvider extends DialogComponentProvider {
}
private void themeChanged(ThemeEvent ev) {
if (!ev.isLookAndFeelChanged()) {
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 we are visible, then we don't need to update as the system updates all visible components
if (isVisible()) {
return;
}
@@ -127,6 +127,7 @@ public class ComponentThemeInspectorAction extends DockingAction {
Color bg = component.getBackground();
Color fg = component.getForeground();
Font font = component.getFont();
String id;
String clazz = component.getClass().getSimpleName();
if (clazz.isEmpty()) {
@@ -175,6 +176,10 @@ public class ComponentThemeInspectorAction extends DockingAction {
.append(spacer)
.append("fg: ")
.append(fgText)
.append(tabs)
.append(spacer)
.append("font: ")
.append(font)
.append('\n');
}
@@ -174,6 +174,18 @@ public class ThemeEditorDialog extends DialogComponentProvider {
updateButtons();
}
private void resetSelectedLookAndFeel() {
Swing.runLater(() -> {
try {
combo.removeItemListener(comboListener);
combo.setSelectedItem(themeManager.getActiveTheme().getLookAndFeelType());
}
finally {
combo.addItemListener(comboListener);
}
});
}
private void themeComboChanged(ItemEvent e) {
if (e.getStateChange() != ItemEvent.SELECTED) {
@@ -181,24 +193,43 @@ public class ThemeEditorDialog extends DialogComponentProvider {
}
LafType lafType = (LafType) e.getItem();
Swing.runLater(() -> {
if (!themeManager.hasThemeValueChanges()) {
setLookAndFeel(lafType);
return;
}
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.ERROR);
}
else {
setStatusText("");
}
colorTree.rebuild();
colorTable.reloadAll();
paletteTable.reloadAll();
fontTable.reloadAll();
iconTable.reloadAll();
});
//@formatter:off
int result = OptionDialog.showOptionDialog(null, "Discard Changes?",
"Changing the Look and Feel type will cause you to lose your changes.\n" +
"If you would like to keep your changes, cancel this dialog and then save the theme\n" +
"Would you like to continue?",
"Lose Changes");
//@formatter:on
if (result == OptionDialog.CANCEL_OPTION) {
resetSelectedLookAndFeel();
return;
}
setLookAndFeel(lafType);
}
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() {
+33 -3
View File
@@ -4,9 +4,6 @@
color.bg = [color]system.color.bg.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.disabled = color.palette.lightgray
color.bg.uneditable = [color]system.color.bg.control
@@ -40,6 +37,27 @@ font.standard = [font]system.font.control
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
icon.flag = flag.png
icon.lock = kgpg.png
@@ -108,3 +126,15 @@ color.cursor.unfocused = color.palette.darkgray
icon.make.selection = stack.png
[Flat Dark]
[laf.boolean]ToolBar.focusableButtons = true
[Flat Light]
[laf.boolean]ToolBar.focusableButtons = true
@@ -94,6 +94,18 @@ public abstract class AbstractThemeReader {
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 {
error(lineNumber, "Can't process property: " + key + " = " + value);
}
@@ -143,6 +155,22 @@ public abstract class AbstractThemeReader {
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 {
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]"
*/
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.
* @param line the line to be added/parsed
* @param lineNumber the line number in the file for this line
@@ -73,7 +73,7 @@ public class ApplicationThemeManager extends ThemeManager {
@Override
public void restoreThemeValues() {
applicationDefaults = getApplicationDefaults();
applicationDefaults = loadApplicationDefaults();
buildCurrentValues();
lookAndFeelManager.resetAll(javaDefaults);
notifyThemeChanged(new AllValuesChangedThemeEvent(false));
@@ -288,6 +288,11 @@ public class ApplicationThemeManager extends ThemeManager {
return false;
}
@Override
public boolean hasThemeValueChanges() {
return !changedValuesMap.isEmpty();
}
@Override
public void registerFont(Component component, String fontId) {
lookAndFeelManager.registerFont(component, fontId);
@@ -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);
}
}
@@ -17,6 +17,8 @@ package generic.theme;
import java.awt.Color;
import org.apache.commons.lang3.StringUtils;
import ghidra.util.Msg;
import ghidra.util.WebColors;
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.
*/
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 EXTERNAL_PREFIX = "[color]";
@@ -65,13 +71,14 @@ public class ColorValue extends ThemeValue<Color> {
return !id.startsWith(COLOR_ID_PREFIX);
}
/**
/**
* Returns true if the given key string is a valid external key for a color value
* @param key the key string to test
* @return true if the given key string is a valid external key for a color value
*/
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)) {
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;
}
@@ -122,6 +135,9 @@ public class ColorValue extends ThemeValue<Color> {
if (externalId.startsWith(EXTERNAL_PREFIX)) {
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;
}
@@ -18,6 +18,8 @@ package generic.theme;
import java.awt.Font;
import java.text.ParseException;
import org.apache.commons.lang3.StringUtils;
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.
*/
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.";
public static final Font LAST_RESORT_DEFAULT = new Font("monospaced", Font.PLAIN, 12);
private static final String EXTERNAL_PREFIX = "[font]";
public static final Font LAST_RESORT_DEFAULT = new Font("monospaced", Font.PLAIN, 12);
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());
}
/**
/**
* Returns true if the given key string is a valid external key for a font value
* @param key the key string to test
* @return true if the given key string is a valid external key for a font value
*/
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 value the font value to parse
* @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 {
String id = fromExternalId(key);
@@ -164,6 +172,12 @@ public class FontValue extends ThemeValue<Font> {
if (internalId.startsWith(FONT_ID_PREFIX)) {
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;
}
@@ -171,6 +185,9 @@ public class FontValue extends ThemeValue<Font> {
if (externalId.startsWith(EXTERNAL_PREFIX)) {
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;
}
@@ -198,9 +215,7 @@ public class FontValue extends ThemeValue<Font> {
}
private static FontValue getRefFontValue(String id, String value) throws ParseException {
if (value.startsWith(EXTERNAL_PREFIX)) {
value = value.substring(EXTERNAL_PREFIX.length());
}
value = fromExternalId(value);
int modIndex = value.indexOf("[");
if (modIndex < 0) {
return new FontValue(id, fromExternalId(value));
@@ -33,6 +33,7 @@ public class GThemeValueMap {
protected Map<String, ColorValue> colorMap = new HashMap<>();
protected Map<String, FontValue> fontMap = new HashMap<>();
protected Map<String, IconValue> iconMap = new HashMap<>();
protected Map<String, JavaPropertyValue> propertyMap = new HashMap<>();
/**
* Constructs a new empty map.
@@ -88,6 +89,19 @@ public class GThemeValueMap {
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.
* @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.
* @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.fontMap.values().forEach(v -> addFont(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());
}
/**
* 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.
* @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
* @return 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.
* @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() {
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();
fontMap.clear();
iconMap.clear();
propertyMap.clear();
}
/**
* Returns true if there are not color, font, or icon values in this map
* @return 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, icon or property values in this map
*/
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);
}
/**
* 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
* 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.
* @return a new {@link GThemeValueMap} that is only populated by values that don't exist
* in the give map
@@ -254,13 +305,18 @@ public class GThemeValueMap {
map.addIcon(icon);
}
}
for (JavaPropertyValue property : propertyMap.values()) {
if (!property.equals(base.getProperty(property.getId()))) {
map.addProperty(property);
}
}
return map;
}
/**
* 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
* when exporting this set of values to a zip file.
* versus resources in the classpath. These are the icon files that need to be included when
* 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
* versus resources in the classpath
*/
@@ -268,18 +324,24 @@ public class GThemeValueMap {
Set<File> files = new HashSet<>();
for (IconValue iconValue : iconMap.values()) {
Icon icon = iconValue.getRawValue();
if (icon instanceof UrlImageIcon urlIcon) {
String originalPath = urlIcon.getOriginalPath();
if (originalPath.startsWith(ResourceManager.EXTERNAL_ICON_PREFIX)) {
URL url = urlIcon.getUrl();
String filePath = url.getFile();
if (filePath != null) {
File iconFile = new File(filePath);
if (iconFile.exists()) {
files.add(iconFile);
}
}
}
if (!(icon instanceof UrlImageIcon urlIcon)) {
continue;
}
String originalPath = urlIcon.getOriginalPath();
if (!originalPath.startsWith(ResourceManager.EXTERNAL_ICON_PREFIX)) {
continue;
}
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;
@@ -287,7 +349,7 @@ public class GThemeValueMap {
@Override
public int hashCode() {
return Objects.hash(colorMap, fontMap, iconMap);
return Objects.hash(colorMap, fontMap, iconMap, propertyMap);
}
@Override
@@ -302,8 +364,10 @@ public class GThemeValueMap {
return false;
}
GThemeValueMap other = (GThemeValueMap) obj;
return Objects.equals(colorMap, other.colorMap) && Objects.equals(fontMap, other.fontMap) &&
Objects.equals(iconMap, other.iconMap);
return Objects.equals(colorMap, other.colorMap) &&
Objects.equals(fontMap, other.fontMap) &&
Objects.equals(iconMap, other.iconMap) &&
Objects.equals(propertyMap, other.propertyMap);
}
public void checkForUnresolvedReferences() {
@@ -317,6 +381,9 @@ public class GThemeValueMap {
for (IconValue iconValue : iconMap.values()) {
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.
* @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.
*/
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.
* @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
*/
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.
* @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
*/
public Icon getResolvedIcon(String id) {
@@ -388,4 +463,18 @@ public class GThemeValueMap {
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;
}
}
@@ -19,6 +19,8 @@ import java.text.ParseException;
import javax.swing.Icon;
import org.apache.commons.lang3.StringUtils;
import ghidra.util.Msg;
import resources.ResourceManager;
import resources.icons.EmptyIcon;
@@ -33,14 +35,14 @@ import resources.icons.UrlImageIcon;
public class IconValue extends ThemeValue<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.";
public static final Icon LAST_RESORT_DEFAULT = ResourceManager.getDefaultIcon();
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 IconModifier modifier;
/**
@@ -94,13 +96,14 @@ public class IconValue extends ThemeValue<Icon> {
return icon;
}
/**
/**
* Returns true if the given key string is a valid external key for an icon value
* @param key the key string to test
* @return true if the given key string is a valid external key for an icon value
*/
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 {
if (value.startsWith(EXTERNAL_PREFIX)) {
value = value.substring(EXTERNAL_PREFIX.length());
}
value = fromExternalId(value);
int modifierIndex = getModifierIndex(value);
if (modifierIndex < 0) {
return new IconValue(id, value);
@@ -213,6 +214,12 @@ public class IconValue extends ThemeValue<Icon> {
if (internalId.startsWith(ICON_ID_PREFIX)) {
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;
}
@@ -220,6 +227,9 @@ public class IconValue extends ThemeValue<Icon> {
if (externalId.startsWith(EXTERNAL_PREFIX)) {
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;
}
@@ -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();
}
}
@@ -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);
}
}
@@ -211,7 +211,7 @@ public class StubThemeManager extends ThemeManager {
}
@Override
protected ApplicationThemeDefaults getApplicationDefaults() {
protected ApplicationThemeDefaults loadApplicationDefaults() {
return new ApplicationThemeDefaults() {
@Override
@@ -22,7 +22,7 @@ import generic.theme.laf.UiDefaultsMapper;
* and Feel (LaF) is being used.
* <P>
* 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
* 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
@@ -70,6 +70,7 @@ public abstract class ThemeManager {
protected LafType activeLafType = activeTheme.getLookAndFeelType();
protected boolean useDarkDefaults = activeTheme.useDarkDefaults();
// this use our normalized ids (e.g., 'laf.')
protected GThemeValueMap javaDefaults = 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
INSTANCE = this;
}
applicationDefaults = getApplicationDefaults();
applicationDefaults = loadApplicationDefaults();
}
protected ApplicationThemeDefaults getApplicationDefaults() {
protected ApplicationThemeDefaults loadApplicationDefaults() {
return new PropertyFileThemeDefaults();
}
@@ -100,9 +102,35 @@ public abstract class ThemeManager {
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() {
GThemeValueMap map = new GThemeValueMap();
map.load(javaDefaults);
map.load(applicationDefaults.getLightValues());
if (useDarkDefaults) {
@@ -500,6 +528,16 @@ public abstract class ThemeManager {
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
* @param id the id to check for an existing color.
@@ -28,6 +28,7 @@ import ghidra.util.Msg;
* @param <T> the base type this ThemeValue works on (i.e., Colors, Fonts, Icons)
*/
public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> {
protected final String id;
protected final T value;
protected final String referenceId;
@@ -36,13 +37,19 @@ public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> {
if (id.equals(referenceId)) {
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.referenceId = referenceId;
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.
* @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
* be returned. In rare situations where it is acceptable for the value to not be resolvable,
* 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.
* @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)
* from the given set of theme values.
* @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)
* 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.
* @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.
*/
protected abstract T getUnresolvedReferenceValue(String primaryId, String unresolvedId);
@@ -64,7 +64,7 @@ public class CustomNimbusLookAndFeel extends NimbusLookAndFeel {
}
protected void installJavaDefaultsIntoThemeManager(UiDefaultsMapper uiDefaultsMapper) {
GThemeValueMap javaDefaults = uiDefaultsMapper.getJavaDefaults();
GThemeValueMap javaDefaults = uiDefaultsMapper.getNormalizedJavaDefaults();
themeManager.setJavaDefaults(javaDefaults);
}
@@ -30,13 +30,13 @@ public class FlatDarkUiDefaultsMapper extends FlatUiDefaultsMapper {
}
@Override
protected void assignSystemColorValues() {
super.assignSystemColorValues();
protected void pickRepresentativeValueForColorGroups() {
super.pickRepresentativeValueForColorGroups();
// 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
assignSystemColorDirect(BG_VIEW_ID, new Color(0x1c1d1e));
assignSystemColorDirect(FG_VIEW_ID, WebColors.LIGHT_GRAY);
setGroupColor(BG_VIEW_ID, new Color(0x1c1d1e));
setGroupColor(FG_VIEW_ID, WebColors.LIGHT_GRAY);
}
@Override
@@ -16,7 +16,6 @@
package generic.theme.laf;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import generic.theme.ApplicationThemeManager;
import generic.theme.LafType;
@@ -28,16 +27,7 @@ public class FlatLookAndFeelManager extends LookAndFeelManager {
}
@Override
protected void fixupLookAndFeelIssues() {
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) {
protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
if (getLookAndFeelType() == LafType.FLAT_DARK) {
return new FlatDarkUiDefaultsMapper(defaults);
}
@@ -24,42 +24,42 @@ public class FlatUiDefaultsMapper extends UiDefaultsMapper {
}
@Override
protected void registerIgnoredLafIds() {
super.registerIgnoredLafIds();
ignoredLafIds.add("Actions.Blue");
ignoredLafIds.add("Actions.Green");
ignoredLafIds.add("Actions.Grey");
ignoredLafIds.add("Actions.Greyinline");
ignoredLafIds.add("Actions.Red");
ignoredLafIds.add("Actions.Yellow");
protected void registerIgnoredJavaIds() {
super.registerIgnoredJavaIds();
ignoredJavaIds.add("Actions.Blue");
ignoredJavaIds.add("Actions.Green");
ignoredJavaIds.add("Actions.Grey");
ignoredJavaIds.add("Actions.Greyinline");
ignoredJavaIds.add("Actions.Red");
ignoredJavaIds.add("Actions.Yellow");
ignoredLafIds.add("Objects.BlackText");
ignoredLafIds.add("Objects.Blue");
ignoredLafIds.add("Objects.Green");
ignoredLafIds.add("Objects.GreenAndroid");
ignoredLafIds.add("Objects.Grey");
ignoredLafIds.add("Objects.Pink");
ignoredLafIds.add("Objects.Purple");
ignoredLafIds.add("Objects.Red");
ignoredLafIds.add("Objects.RedStatus");
ignoredLafIds.add("Objects.Yellow");
ignoredLafIds.add("Objects.YellowDark");
ignoredJavaIds.add("Objects.BlackText");
ignoredJavaIds.add("Objects.Blue");
ignoredJavaIds.add("Objects.Green");
ignoredJavaIds.add("Objects.GreenAndroid");
ignoredJavaIds.add("Objects.Grey");
ignoredJavaIds.add("Objects.Pink");
ignoredJavaIds.add("Objects.Purple");
ignoredJavaIds.add("Objects.Red");
ignoredJavaIds.add("Objects.RedStatus");
ignoredJavaIds.add("Objects.Yellow");
ignoredJavaIds.add("Objects.YellowDark");
ignoredLafIds.add("h0.font");
ignoredLafIds.add("h00.font");
ignoredLafIds.add("h1.font");
ignoredLafIds.add("h1.regular.font");
ignoredLafIds.add("h2.font");
ignoredLafIds.add("h2.regular.font");
ignoredLafIds.add("h3.font");
ignoredLafIds.add("h3.regular.font");
ignoredLafIds.add("h4.font");
ignoredLafIds.add("large.font");
ignoredLafIds.add("light.font");
ignoredLafIds.add("medium.font");
ignoredLafIds.add("mini.font");
ignoredLafIds.add("monospaced.font");
ignoredLafIds.add("small.font");
ignoredJavaIds.add("h0.font");
ignoredJavaIds.add("h00.font");
ignoredJavaIds.add("h1.font");
ignoredJavaIds.add("h1.regular.font");
ignoredJavaIds.add("h2.font");
ignoredJavaIds.add("h2.regular.font");
ignoredJavaIds.add("h3.font");
ignoredJavaIds.add("h3.regular.font");
ignoredJavaIds.add("h4.font");
ignoredJavaIds.add("large.font");
ignoredJavaIds.add("light.font");
ignoredJavaIds.add("medium.font");
ignoredJavaIds.add("mini.font");
ignoredJavaIds.add("monospaced.font");
ignoredJavaIds.add("small.font");
}
}
@@ -30,7 +30,7 @@ public class GtkLookAndFeelManager extends LookAndFeelManager {
}
@Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) {
protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
return new UiDefaultsMapper(defaults);
}
}
@@ -70,7 +70,6 @@ public abstract class LookAndFeelManager {
doInstallLookAndFeel();
processJavaDefaults();
fixupLookAndFeelIssues();
installGlobalProperties();
installCustomLookAndFeelActions();
updateComponentUis();
}
@@ -157,22 +156,26 @@ public abstract class LookAndFeelManager {
/**
* 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();
for (String changedFontId : changedJavaFontIds) {
for (String changedFontId : changedFontIds) {
// even though all these derive from the new font, they might be different
// because of FontModifiers.
Font font = Gui.getFont(changedFontId);
String lafFontId = normalizedIdToLafIdMap.get(changedFontId);
if (lafFontId != null) {
String javaFontId = normalizedIdToLafIdMap.get(changedFontId);
if (javaFontId != null) {
// 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();
}
@@ -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() {
// no generic fix-ups at this time.
installGlobalFontSizeOverride();
}
/**
@@ -244,15 +260,14 @@ public abstract class LookAndFeelManager {
*/
protected void processJavaDefaults() {
UIDefaults defaults = UIManager.getDefaults();
UiDefaultsMapper uiDefaultsMapper = getUiDefaultsMapper(defaults);
GThemeValueMap javaDefaults = uiDefaultsMapper.getJavaDefaults();
UiDefaultsMapper uiDefaultsMapper = createUiDefaultsMapper(defaults);
GThemeValueMap javaDefaults = uiDefaultsMapper.getNormalizedJavaDefaults();
themeManager.setJavaDefaults(javaDefaults);
uiDefaultsMapper.installValuesIntoUIDefaults(themeManager.getCurrentValues());
normalizedIdToLafIdMap = uiDefaultsMapper.getNormalizedIdToLafIdMap();
}
protected abstract UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults);
protected abstract UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults);
protected String findLookAndFeelClassName(String lookAndFeelName) {
LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels();
@@ -277,38 +292,20 @@ public abstract class LookAndFeelManager {
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 newKs = KeyStroke.getKeyStroke(newKsText);
UIDefaults uiDefaults = UIManager.getDefaults();
for (String properyPrefix : prefixValues) {
UIDefaults defaults = UIManager.getDefaults();
Object object = defaults.get(properyPrefix + ".focusInputMap");
Object object = uiDefaults.get(properyPrefix + ".focusInputMap");
InputMap inputMap = (InputMap) object;
Object action = inputMap.get(existingKs);
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() {
// 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
* @param defaults the UIDefaults to search
@@ -34,7 +34,7 @@ public class MacLookAndFeelManager extends LookAndFeelManager {
}
@Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) {
protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
return new MacUiDefaultsMapper(defaults);
}
@@ -27,7 +27,7 @@ public class MetalLookAndFeelManager extends LookAndFeelManager {
}
@Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) {
protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
return new UiDefaultsMapper(defaults);
}
}
@@ -30,12 +30,15 @@ public class MotifLookAndFeelManager extends LookAndFeelManager {
}
@Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) {
protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
return new MotifUiDefaultsMapper(defaults);
}
@Override
protected void fixupLookAndFeelIssues() {
super.fixupLookAndFeelIssues();
//
// 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.
@@ -24,9 +24,9 @@ public class MotifUiDefaultsMapper extends UiDefaultsMapper {
}
@Override
protected void registerIgnoredLafIds() {
super.registerIgnoredLafIds();
ignoredLafIds.add("controlLightShadow");
protected void registerIgnoredJavaIds() {
super.registerIgnoredJavaIds();
ignoredJavaIds.add("controlLightShadow");
}
}
@@ -103,14 +103,14 @@ public class NimbusLookAndFeelManager extends LookAndFeelManager {
// fix scroll bar grabber disappearing. See
// 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.
UIDefaults defaults = UIManager.getDefaults();
defaults.put("ScrollBar.minimumThumbSize", new Dimension(30, 30));
UIDefaults uiDefaults = UIManager.getDefaults();
uiDefaults.put("ScrollBar.minimumThumbSize", new Dimension(30, 30));
// (see NimbusDefaults for key values that can be changed here)
}
@Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) {
protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
return new NimbusUiDefaultsMapper(defaults);
}
}
@@ -28,45 +28,45 @@ public class NimbusUiDefaultsMapper extends UiDefaultsMapper {
}
@Override
protected void registerIgnoredLafIds() {
super.registerIgnoredLafIds();
ignoredLafIds.add("background");
protected void registerIgnoredJavaIds() {
super.registerIgnoredJavaIds();
ignoredJavaIds.add("background");
ignoredLafIds.add("controlLHighlight");
ignoredJavaIds.add("controlLHighlight");
ignoredLafIds.add("nimbusAlertYellow");
ignoredLafIds.add("nimbusBase");
ignoredLafIds.add("nimbusBlueGrey");
ignoredLafIds.add("nimbusDisabledText");
ignoredLafIds.add("nimbusFocus");
ignoredLafIds.add("nimbusGreen");
ignoredLafIds.add("nimbusInfoBlue");
ignoredLafIds.add("nimbusOrange");
ignoredLafIds.add("nimbusRed");
ignoredLafIds.add("nimbusSelectedText");
ignoredLafIds.add("nimbusSelection");
ignoredLafIds.add("nimbusSelectionBackground");
ignoredJavaIds.add("nimbusAlertYellow");
ignoredJavaIds.add("nimbusBase");
ignoredJavaIds.add("nimbusBlueGrey");
ignoredJavaIds.add("nimbusDisabledText");
ignoredJavaIds.add("nimbusFocus");
ignoredJavaIds.add("nimbusGreen");
ignoredJavaIds.add("nimbusInfoBlue");
ignoredJavaIds.add("nimbusOrange");
ignoredJavaIds.add("nimbusRed");
ignoredJavaIds.add("nimbusSelectedText");
ignoredJavaIds.add("nimbusSelection");
ignoredJavaIds.add("nimbusSelectionBackground");
}
@Override
protected void assignSystemColorValues() {
protected void pickRepresentativeValueForColorGroups() {
// different from base class
assignSystemColorFromLafId(BG_CONTROL_ID, "Button.background");
assignSystemColorFromLafId(FG_CONTROL_ID, "Button.foreground");
assignSystemColorFromLafId(BG_BORDER_ID, "nimbusBorder");
assignSystemColorFromLafId(BG_VIEW_ID, "nimbusLightBackground");
assignSystemColorFromLafId(FG_VIEW_ID, "controlText");
setGroupColorUsingJavaRepresentative(BG_CONTROL_ID, "Button.background");
setGroupColorUsingJavaRepresentative(FG_CONTROL_ID, "Button.foreground");
setGroupColorUsingJavaRepresentative(BG_BORDER_ID, "nimbusBorder");
setGroupColorUsingJavaRepresentative(BG_VIEW_ID, "nimbusLightBackground");
setGroupColorUsingJavaRepresentative(FG_VIEW_ID, "controlText");
// 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"
assignSystemColorFromLafId(BG_VIEW_SELECTED_ID, "textHighlight");
assignSystemColorFromLafId(FG_VIEW_SELECTED_ID, "textHighlightText");
assignSystemColorFromLafId(FG_DISABLED_ID, "textInactiveText");
assignSystemColorFromLafId(BG_TOOLTIP_ID, "info");
assignSystemColorFromLafId(FG_TOOLTIP_ID, "infoText");
setGroupColorUsingJavaRepresentative(BG_VIEW_SELECTED_ID, "textHighlight");
setGroupColorUsingJavaRepresentative(FG_VIEW_SELECTED_ID, "textHighlightText");
setGroupColorUsingJavaRepresentative(FG_DISABLED_ID, "textInactiveText");
setGroupColorUsingJavaRepresentative(BG_TOOLTIP_ID, "info");
setGroupColorUsingJavaRepresentative(FG_TOOLTIP_ID, "infoText");
}
@Override
@@ -83,8 +83,8 @@ public class NimbusUiDefaultsMapper extends UiDefaultsMapper {
super.installGColorsIntoUIDefaults();
// 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
// attribute was being ignored. We set it here to be a GColor, which causes Nimbus
// 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
// to honor the value. We may need to add more entries here as they are discovered.
defaults.put("TextField.selectionForeground",
File diff suppressed because it is too large Load Diff
@@ -27,7 +27,7 @@ public class WindowsClassicLookAndFeelManager extends LookAndFeelManager {
}
@Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) {
protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
return new UiDefaultsMapper(defaults);
}
}
@@ -27,7 +27,7 @@ public class WindowsLookAndFeelManager extends LookAndFeelManager {
}
@Override
protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) {
protected UiDefaultsMapper createUiDefaultsMapper(UIDefaults defaults) {
return new UiDefaultsMapper(defaults);
}
@@ -352,7 +352,7 @@ public class ApplicationThemeManagerTest {
}
@Override
protected ApplicationThemeDefaults getApplicationDefaults() {
protected ApplicationThemeDefaults loadApplicationDefaults() {
return new ApplicationThemeDefaults() {
@Override
@@ -22,7 +22,7 @@ import java.awt.Color;
import org.junit.Before;
import org.junit.Test;
import ghidra.util.WebColors;
import ghidra.util.*;
public class ColorValueTest {
@@ -30,6 +30,10 @@ public class ColorValueTest {
@Before
public void setup() {
// disable warning messages when some test values cannot be found
Msg.setErrorLogger(new SpyErrorLogger());
values = new GThemeValueMap();
}
@@ -150,4 +154,34 @@ public class ColorValueTest {
assertEquals("color.parent", value.getReferenceId());
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));
}
}
@@ -23,12 +23,19 @@ import java.text.ParseException;
import org.junit.Before;
import org.junit.Test;
import ghidra.util.Msg;
import ghidra.util.SpyErrorLogger;
public class FontValueTest {
private static Font FONT = new Font("Dialog", Font.PLAIN, 12);
private GThemeValueMap values;
@Before
public void setup() {
// disable warning messages when some test values cannot be found
Msg.setErrorLogger(new SpyErrorLogger());
values = new GThemeValueMap();
}
@@ -140,4 +147,33 @@ public class FontValueTest {
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));
}
}
@@ -83,6 +83,8 @@ public class GThemeTest extends AbstractGenericTest {
theme.setColor("foo.bar", Color.GREEN);
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.2", DIALOG);
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.GREEN, theme.getColor("foo.bar").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(DIALOG, theme.getFont("font.a.2").get(theme));
@@ -24,6 +24,8 @@ import javax.swing.Icon;
import org.junit.Before;
import org.junit.Test;
import ghidra.util.Msg;
import ghidra.util.SpyErrorLogger;
import resources.MultiIcon;
import resources.ResourceManager;
import resources.icons.EmptyIcon;
@@ -35,6 +37,10 @@ public class IconValueTest {
@Before
public void setup() {
// disable warning messages when some test values cannot be found
Msg.setErrorLogger(new SpyErrorLogger());
values = new GThemeValueMap();
}
@@ -225,4 +231,37 @@ public class IconValueTest {
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));
}
}
@@ -36,8 +36,8 @@ public class ThemePropertyFileReaderTest {
@Test
public void testDefaults() throws IOException {
//@formatter:off
ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
" color.b.1 = white", // WHITE
" color.b.2 = #ff0000", // RED
" color.b.3 = 0x008000", // GREEN
@@ -53,12 +53,18 @@ public class ThemePropertyFileReaderTest {
" icon.a.12 = icon.a.10[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)]}",
" [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
Color halfAlphaRed = new Color(0x80ff0000, true);
GThemeValueMap values = reader.getDefaultValues();
assertEquals(15, values.size());
assertEquals(21, values.size());
assertEquals(WHITE, getColor(values, "color.b.1"));
assertEquals(RED, getColor(values, "color.b.2"));
@@ -85,21 +91,27 @@ public class ThemePropertyFileReaderTest {
icon = getIcon(values, "icon.a.14");
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
public void testDarkDefaults() throws IOException {
//@formatter:off
ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
" color.b.1 = red",
" color.b.2 = red",
" color.b.3 = red",
" color.b.4 = red",
" color.b.5 = red",
" color.b.6 = red",
" color.b.7 = red",
"[Dark Defaults]",
ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
" color.b.1 = red",
" color.b.2 = red",
" color.b.3 = red",
" color.b.4 = red",
" color.b.5 = red",
" color.b.6 = red",
" color.b.7 = red",
"[Dark Defaults]",
" color.b.1 = white", // WHITE
" color.b.2 = #ff0000", // RED
" color.b.3 = 0x008000", // GREEN
@@ -126,11 +138,11 @@ public class ThemePropertyFileReaderTest {
@Test
public void testBothDefaultsAndDarkDefaultsInSameFile() throws IOException {
//@formatter:off
ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
" color.b.1 = white", // WHITE
" color.b.2 = #ff0000", // RED
"[Dark Defaults]",
"[Dark Defaults]",
" color.b.1 = black", // BLACK
" color.b.2 = #0000ff", // BLUE
"")));
@@ -151,14 +163,14 @@ public class ThemePropertyFileReaderTest {
@Test
public void testLookAndFeelValues() throws IOException {
//@formatter:off
ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
" color.b.1 = white",
"[Dark Defaults]",
" color.b.1 = black",
ThemePropertyFileReader reader = new ThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
" color.b.1 = white",
"[Dark Defaults]",
" color.b.1 = black",
"[Metal]",
" color.b.1 = red",
"[Nimbus]",
"[Nimbus]",
" color.b.1 = green",
"")));
//@formatter:on
@@ -188,8 +200,8 @@ public class ThemePropertyFileReaderTest {
@Test
public void testParseColorError() throws IOException {
//@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
" color.b.1 = white", // WHITE
" color.b.2 = sdfsdf", // RED
"")));
@@ -204,11 +216,11 @@ public class ThemePropertyFileReaderTest {
@Test
public void testParseFontError() throws IOException {
//@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
" font.b.1 = Dialog-PLAIN-14",
" font.b.2 = Dialog-PLANE-13",
" font.b.3 = Dialog-BOLD-ITALIC",
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
" font.b.1 = Dialog-PLAIN-14",
" font.b.2 = Dialog-PLANE-13",
" font.b.3 = Dialog-BOLD-ITALIC",
"")));
//@formatter:on
List<String> errors = reader.getErrors();
@@ -219,10 +231,10 @@ public class ThemePropertyFileReaderTest {
@Test
public void testParseFontModiferError() throws IOException {
//@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
" font.b.1 = Dialog-PLAIN-14",
" font.b.2 = (font.b.1[)",
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
" font.b.1 = Dialog-PLAIN-14",
" font.b.2 = (font.b.1[)",
"")));
//@formatter:on
List<String> errors = reader.getErrors();
@@ -233,10 +245,10 @@ public class ThemePropertyFileReaderTest {
@Test
public void testIconNoRightHandValueError() throws IOException {
//@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
" icon.b.1 = core.png",
" icon.b.2 = ",
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
" icon.b.1 = core.png",
" icon.b.2 = ",
"")));
//@formatter:on
List<String> errors = reader.getErrors();
@@ -247,8 +259,8 @@ public class ThemePropertyFileReaderTest {
@Test
public void testColorIdDefinedInNonDefaultsSectionOnly() throws IOException {
//@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
" color.foo = red",
"[Dark Defaults]",
" color.bar = blue",
@@ -264,7 +276,7 @@ public class ThemePropertyFileReaderTest {
@Test
public void testFontIdDefinedInNonDefaultsSectionOnly() throws IOException {
//@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
"[Dark Defaults]",
" font.bar = dialog-PLAIN-14",
@@ -280,7 +292,7 @@ public class ThemePropertyFileReaderTest {
@Test
public void testIconIdDefinedInNonDefaultsSectionOnly() throws IOException {
//@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Defaults]",
"[Dark Defaults]",
" icon.bar = core.png",
@@ -296,8 +308,8 @@ public class ThemePropertyFileReaderTest {
@Test
public void testDefaultSectionMustBeFirst() throws Exception {
//@formatter:off
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Dark Defaults]",
ThemePropertyFileReader reader = new SilentThemePropertyFileReader("test", new StringReader(String.join("\n",
"[Dark Defaults]",
" color.foo = red",
"[Defaults]",
" color.bar = blue",
@@ -326,6 +338,16 @@ public class ThemePropertyFileReaderTest {
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 {
protected SilentThemePropertyFileReader(String source, Reader reader) throws IOException {
@@ -28,6 +28,8 @@ import org.junit.Before;
import org.junit.Test;
import generic.theme.*;
import ghidra.util.Msg;
import ghidra.util.SpyErrorLogger;
import resources.ResourceManager;
public class UIDefaultsMapperTest {
@@ -40,6 +42,10 @@ public class UIDefaultsMapperTest {
@Before
public void setup() {
// disable warning messages when some default UI values cannot be found
Msg.setErrorLogger(new SpyErrorLogger());
defaults = createDefaults();
defaults.put("control", Color.RED);
defaults.put("Button.background", Color.RED);
@@ -51,7 +57,7 @@ public class UIDefaultsMapperTest {
@Test
public void testGetJavaDefaults() {
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("laf.color.Button.background"));
@@ -71,7 +77,7 @@ public class UIDefaultsMapperTest {
defaults.put("RadioButton.background", Color.BLUE); // Blue not defined in a color group
mapper = new UiDefaultsMapper(defaults);
GThemeValueMap javaDefaults = mapper.getJavaDefaults();
GThemeValueMap javaDefaults = mapper.getNormalizedJavaDefaults();
// expecting two palette groups to be created
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
mapper = new UiDefaultsMapper(defaults);
GThemeValueMap javaDefaults = mapper.getJavaDefaults();
GThemeValueMap javaDefaults = mapper.getNormalizedJavaDefaults();
assertDirectFont(javaDefaults, "laf.palette.font.01", SMALL_FONT);
assertIndirectFont(javaDefaults, "laf.font.ToggleButton.font", "laf.palette.font.01");
@@ -210,7 +210,7 @@
<target name="___chkstk_ms"/>
<pcode>
<body><![CDATA[
RSP = RSP + 0;
RSP = RSP + 8;
]]></body>
</pcode>
</callfixup>