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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

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 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]";
@ -71,7 +77,8 @@ public class ColorValue extends ThemeValue<Color> {
* @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;
}

View file

@ -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;
/**
@ -103,7 +110,8 @@ public class FontValue extends ThemeValue<Font> {
* @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);
}
/**
@ -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));

View file

@ -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
@ -115,6 +129,15 @@ public class GThemeValueMap {
return iconMap.get(id);
}
/**
* 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.
@ -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,6 +272,14 @@ 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.
@ -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;
}
}

View file

@ -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;
/**
@ -100,7 +102,8 @@ public class IconValue extends ThemeValue<Icon> {
* @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;
}

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
protected ApplicationThemeDefaults getApplicationDefaults() {
protected ApplicationThemeDefaults loadApplicationDefaults() {
return new ApplicationThemeDefaults() {
@Override

View file

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

View file

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

View file

@ -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,6 +37,12 @@ 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;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -71,9 +71,13 @@ import ghidra.util.Msg;
*
*/
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_ICON_ID_PREFIX = "laf.icon.";
public static final String LAF_COLOR_ID_PREFIX = ColorValue.LAF_ID_PREFIX;
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_FONT_PALETTE_PREFIX = "laf.palette.font.";
@ -86,22 +90,25 @@ public class UiDefaultsMapper {
protected UIDefaults defaults;
private GThemeValueMap extractedValues;
/** 'normalized' values have keys that start with 'laf.' */
private GThemeValueMap normalizedValues = new GThemeValueMap();
private Map<String, String> lafIdToNormalizedIdMap = new HashMap<>();
protected Set<String> ignoredLafIds = new HashSet<>();
/** Maps Look and Feel keys to standardized keys that start with 'laf.' */
private Map<String, String> javaIdToNormalizedId = new HashMap<>();
protected Set<String> ignoredJavaIds = new HashSet<>();
private Map<String, ColorMatcher> componentToColorMatcherMap = new HashMap<>();
private Map<String, FontMatcher> componentToFontMatcherMap = new HashMap<>();
private Map<String, ColorGrouper> componentToColorGrouper = new HashMap<>();
private Map<String, FontGrouper> componentToFontGrouper = new HashMap<>();
// @formatter:off
protected ColorMatcher viewColorMatcher = new ColorMatcher(BG_VIEW_ID,
protected ColorGrouper viewColorGrouper = new ColorGrouper(BG_VIEW_ID,
FG_VIEW_ID,
BG_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);
protected ColorMatcher defaultColorMatcher = new ColorMatcher(BG_CONTROL_ID,
protected ColorGrouper defaultColorMatcher = new ColorGrouper(BG_CONTROL_ID,
FG_CONTROL_ID,
BG_VIEW_ID,
FG_VIEW_ID,
@ -111,9 +118,9 @@ public class UiDefaultsMapper {
BG_TOOLTIP_ID,
BG_BORDER_ID);
protected FontMatcher menuFontMatcher = new FontMatcher(FONT_MENU_ID);
protected FontMatcher viewFontMatcher = new FontMatcher(FONT_VIEW_ID);
protected FontMatcher defaultFontMatcher = new FontMatcher(FONT_CONTROL_ID,
protected FontGrouper menuFontGrouper = new FontGrouper(FONT_MENU_ID);
protected FontGrouper viewFontGrouper = new FontGrouper(FONT_VIEW_ID);
protected FontGrouper defaultFontMatcher = new FontGrouper(FONT_CONTROL_ID,
FONT_VIEW_ID,
FONT_MENU_ID);
@ -128,13 +135,13 @@ public class UiDefaultsMapper {
this.defaults = defaults;
this.extractedValues = extractColorFontAndIconValuesFromDefaults();
assignSystemColorValues();
assignSystemFontValues();
pickRepresentativeValueForColorGroups();
pickRepresentativeValueForFontGroups();
registerIgnoredLafIds();
registerIgnoredJavaIds();
assignColorMatchersToComponentIds();
assignFontMatchersToComponentIds();
buildComponentToColorGrouperMap();
buildComponentToFontGrouperMap();
assignNormalizedColorValues();
assignNormalizedFontValues();
@ -142,12 +149,15 @@ public class UiDefaultsMapper {
}
/**
* Returns the normalized id to value map that will be installed into the
* ApplicationThemeManager to be the user changeable values for affecting the Java
* LookAndFeel colors, fonts, and icons
* Returns the normalized id to value map that will be installed into the theme manager to be
* the user changeable values for affecting the Java 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
*/
public GThemeValueMap getJavaDefaults() {
public GThemeValueMap getNormalizedJavaDefaults() {
return normalizedValues;
}
@ -161,22 +171,25 @@ public class UiDefaultsMapper {
//
// 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
// values that are different than the defaults.
// values that are different than the defaults. Finally, we apply any overridden Java
// properties.
//
installGColorsIntoUIDefaults();
installOverriddenFontsIntoUIDefaults(currentValues);
installOverriddenIconsIntoUIDefaults(currentValues);
installOverriddenPropertiesIntoUIDefaults(currentValues);
}
/**
* 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
* the UiDefaults so that the LookAndFeel will pick up and use the changes.
*
* @return a mapping of normalized LaF ids to original LaF ids.
*/
public Map<String, String> getNormalizedIdToLafIdMap() {
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 standardId = entry.getValue();
map.put(standardId, lafId);
@ -191,41 +204,41 @@ public class UiDefaultsMapper {
* 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.
*/
protected void registerIgnoredLafIds() {
protected void registerIgnoredJavaIds() {
ignoredLafIds.add("desktop");
ignoredLafIds.add("activeCaption");
ignoredLafIds.add("activeCaptionText");
ignoredLafIds.add("activeCaptionBorder");
ignoredLafIds.add("inactiveCaption");
ignoredLafIds.add("inactiveCaptionText");
ignoredLafIds.add("inactiveCaptionBorder");
ignoredLafIds.add("window");
ignoredLafIds.add("windowBorder");
ignoredLafIds.add("windowText");
ignoredLafIds.add("menu");
ignoredLafIds.add("menuText");
ignoredLafIds.add("text");
ignoredLafIds.add("textText");
ignoredLafIds.add("textHighlight");
ignoredLafIds.add("textHighightText");
ignoredLafIds.add("textInactiveText");
ignoredLafIds.add("control");
ignoredLafIds.add("controlText");
ignoredLafIds.add("controlHighlight");
ignoredLafIds.add("controlLtHighlight");
ignoredLafIds.add("controlShadow");
ignoredLafIds.add("controlDkShadow");
ignoredLafIds.add("info");
ignoredLafIds.add("infoText");
ignoredLafIds.add("scrollbar");
ignoredJavaIds.add("desktop");
ignoredJavaIds.add("activeCaption");
ignoredJavaIds.add("activeCaptionText");
ignoredJavaIds.add("activeCaptionBorder");
ignoredJavaIds.add("inactiveCaption");
ignoredJavaIds.add("inactiveCaptionText");
ignoredJavaIds.add("inactiveCaptionBorder");
ignoredJavaIds.add("window");
ignoredJavaIds.add("windowBorder");
ignoredJavaIds.add("windowText");
ignoredJavaIds.add("menu");
ignoredJavaIds.add("menuText");
ignoredJavaIds.add("text");
ignoredJavaIds.add("textText");
ignoredJavaIds.add("textHighlight");
ignoredJavaIds.add("textHighightText");
ignoredJavaIds.add("textInactiveText");
ignoredJavaIds.add("control");
ignoredJavaIds.add("controlText");
ignoredJavaIds.add("controlHighlight");
ignoredJavaIds.add("controlLtHighlight");
ignoredJavaIds.add("controlShadow");
ignoredJavaIds.add("controlDkShadow");
ignoredJavaIds.add("info");
ignoredJavaIds.add("infoText");
ignoredJavaIds.add("scrollbar");
}
/**
* Defines the values to assign to all the system color ids based on the best representative
* value defined in the {@link BasicLookAndFeel}
*/
protected void assignSystemColorValues() {
protected void pickRepresentativeValueForColorGroups() {
// Originally, these values were assigned to the corresponding concepts as defined
// 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
@ -235,114 +248,118 @@ public class UiDefaultsMapper {
// subclassed where the values can be overridden. See the NimbusUiDefaultsMapper as an
// example.
assignSystemColorFromLafId(BG_CONTROL_ID, "Button.background");
assignSystemColorFromLafId(FG_CONTROL_ID, "Button.foreground");
assignSystemColorFromLafId(BG_BORDER_ID, "InternalFrame.borderColor");
setGroupColorUsingJavaRepresentative(BG_CONTROL_ID, "Button.background");
setGroupColorUsingJavaRepresentative(FG_CONTROL_ID, "Button.foreground");
setGroupColorUsingJavaRepresentative(BG_BORDER_ID, "InternalFrame.borderColor");
assignSystemColorFromLafId(BG_VIEW_ID, "TextArea.background");
assignSystemColorFromLafId(FG_VIEW_ID, "TextArea.foreground");
assignSystemColorFromLafId(BG_VIEW_SELECTED_ID, "TextArea.selectionBackground");
assignSystemColorFromLafId(FG_VIEW_SELECTED_ID, "TextArea.selectionForeground");
setGroupColorUsingJavaRepresentative(BG_VIEW_ID, "TextArea.background");
setGroupColorUsingJavaRepresentative(FG_VIEW_ID, "TextArea.foreground");
setGroupColorUsingJavaRepresentative(BG_VIEW_SELECTED_ID, "TextArea.selectionBackground");
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");
assignSystemColorFromLafId(FG_TOOLTIP_ID, "ToolTip.foreground");
setGroupColorUsingJavaRepresentative(BG_TOOLTIP_ID, "ToolTip.background");
setGroupColorUsingJavaRepresentative(FG_TOOLTIP_ID, "ToolTip.foreground");
}
/**
* 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 lafId the LaF key to use to retrieve a color from the UiDefaults
* @param group the system color id to get a value for
* @param javaId the LaF key to use to retrieve a color from the UiDefaults
*/
protected void assignSystemColorFromLafId(String systemColorId, String lafId) {
Color lafColor = defaults.getColor(lafId);
if (lafColor == null) {
Msg.debug(this, "Missing value for system color: \"" + systemColorId +
"\". No value for laf id: \"" + lafId + "\".");
protected void setGroupColorUsingJavaRepresentative(String group, String javaId) {
Color javaColor = defaults.getColor(javaId);
if (javaColor == null) {
Msg.debug(this, "Missing value for system color: \"" + group +
"\". No value for java id: \"" + javaId + "\".");
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
* the system color.
* @param systemColorId the system color id to assign the given color
* This allows clients to hard-code a chosen color for a group
*
* @param group the system color id to assign the given color
* @param color the color to be assigned to the system color id
*/
protected void assignSystemColorDirect(String systemColorId, Color color) {
normalizedValues.addColor(new ColorValue(systemColorId, color));
protected void setGroupColor(String group, Color color) {
normalizedValues.addColor(new ColorValue(group, color));
}
/**
* Assigns the system font id a directly specified font and does not use the LaF to populate
* the system font.
* @param systemFontId the system font id to assign the given font
* This allows clients to hard-code a chosen font for a group
*
* @param group the system font id to assign the given font
* @param font the font to be assigned to the system font id
*/
protected void assignSystemFontDirect(String systemFontId, Font font) {
normalizedValues.addFont(new FontValue(systemFontId, font));
protected void setGroupFont(String group, Font 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() {
assignSystemFontFromLafId(FONT_CONTROL_ID, "Button.font");
assignSystemFontFromLafId(FONT_VIEW_ID, "Table.font");
assignSystemFontFromLafId(FONT_MENU_ID, "Menu.font");
protected void pickRepresentativeValueForFontGroups() {
setGroupFontUsingRepresentative(FONT_CONTROL_ID, "Button.font");
setGroupFontUsingRepresentative(FONT_VIEW_ID, "Table.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);
if (lafFont == null) {
Msg.debug(this, "Missing value for system font: \"" + systemFontId +
"\". No value for laf id: \"" + lafId + "\".");
Font representativeFont = extractedValues.getResolvedFont(javaId);
if (representativeFont == null) {
Msg.debug(this, "Missing value for system font: \"" + fontGroup +
"\". No value for java id: \"" + javaId + "\".");
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() {
defineComponentFontMatcher(MENU_COMPONENTS, menuFontMatcher);
defineComponentFontMatcher(VIEW_COMPONENTS, viewFontMatcher);
protected void buildComponentToFontGrouperMap() {
mapComponentsToFontGrouper(menuFontGrouper, MENU_COMPONENTS);
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() {
defineComponentColorMatcher(VIEW_COMPONENTS, viewColorMatcher);
defineComponentColorMatcher(TOOLTIP_COMPONENTS, tooltipColorMatcher);
protected void buildComponentToColorGrouperMap() {
mapComponentsToColorGrouper(viewColorGrouper, VIEW_COMPONENTS);
mapComponentsToColorGrouper(tooltipColorGrouper, TOOLTIP_COMPONENTS);
}
/**
* Assigns every component name in the component group to the given ColorValueMatcher
* @param componentGroups a list of component names
* @param matcher the ColorMatcher that will provide the precedence of system ids to
* @param grouper the ColorMatcher that will provide the precedence of system ids to
* search when replacing LaF component specific values
* @param componentGroup a list of component names
*/
private void defineComponentColorMatcher(String[] componentGroups, ColorMatcher matcher) {
for (String componentGroup : componentGroups) {
componentToColorMatcherMap.put(componentGroup, matcher);
private void mapComponentsToColorGrouper(ColorGrouper grouper, String... componentGroup) {
for (String name : componentGroup) {
componentToColorGrouper.put(name, grouper);
}
}
/**
* Assigns every component name in a component group to the given FontValueMapper
* @param componentGroups a list of component names
* @param matcher the FontValueMatcher that will provide the precedence of ststem font ids to
* @param grouper the FontValueMatcher that will provide the precedence of system font ids to
* 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) {
for (String componentGroup : componentGroups) {
componentToFontMatcherMap.put(componentGroup, matcher);
private void mapComponentsToFontGrouper(FontGrouper grouper, String... componentGroup) {
for (String name : componentGroup) {
componentToFontGrouper.put(name, grouper);
}
}
@ -356,12 +373,12 @@ public class UiDefaultsMapper {
for (String lafId : list) {
// we don't want to create java defaults for laf system ids since changing them would
// have no effect
if (ignoredLafIds.contains(lafId)) {
if (ignoredJavaIds.contains(lafId)) {
continue;
}
String createdId = LAF_FONT_ID_PREFIX + lafId;
lafIdToNormalizedIdMap.put(lafId, createdId);
javaIdToNormalizedId.put(lafId, createdId);
Font lafFont = extractedValues.getResolvedFont(lafId);
FontValue fontValue = getFontValue(createdId, lafId, lafFont);
@ -380,7 +397,7 @@ public class UiDefaultsMapper {
Icon icon = extractedValues.getResolvedIcon(lafId);
if (icon != null) {
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());
Collections.sort(list);
for (String lafId : list) {
if (ignoredLafIds.contains(lafId)) {
if (ignoredJavaIds.contains(lafId)) {
continue;
}
String createdId = LAF_COLOR_ID_PREFIX + lafId;
lafIdToNormalizedIdMap.put(lafId, createdId);
javaIdToNormalizedId.put(lafId, createdId);
Color lafColor = extractedValues.getResolvedColor(lafId);
ColorValue colorValue = getColorValue(createdId, lafId, lafColor);
@ -486,16 +503,16 @@ public class UiDefaultsMapper {
*/
private String findSystemColorId(String lafId, Color lafColor) {
String componentName = getComponentName(lafId);
ColorMatcher colorMatcher = componentToColorMatcherMap.get(componentName);
ColorGrouper colorMatcher = componentToColorGrouper.get(componentName);
// check in widget specific group first
if (colorMatcher != null) {
String systemId = colorMatcher.getSystemId(lafColor);
String systemId = colorMatcher.getGroupId(lafColor);
if (systemId != null) {
return systemId;
}
}
// 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) {
String componentName = getComponentName(lafId);
FontMatcher fontMatcher = componentToFontMatcherMap.get(componentName);
FontGrouper fontMatcher = componentToFontGrouper.get(componentName);
// check in widget specific group first
if (fontMatcher != null) {
String systemId = fontMatcher.getSystemId(lafFont);
String systemId = fontMatcher.getGroupId(lafFont);
if (systemId != null) {
return systemId;
}
}
// 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<>();
for (String lafId : extractedValues.getColorIds()) {
String standardColorId = lafIdToNormalizedIdMap.get(lafId);
String standardColorId = javaIdToNormalizedId.get(lafId);
if (standardColorId != null) {
GColorUIResource sharedGColor = getSharedGColor(cachedColors, standardColorId);
defaults.put(lafId, sharedGColor);
@ -556,7 +573,7 @@ public class UiDefaultsMapper {
private void installOverriddenIconsIntoUIDefaults(GThemeValueMap currentValues) {
for (String lafId : extractedValues.getIconIds()) {
Icon currentIcon = extractedValues.getResolvedIcon(lafId);
String standardId = lafIdToNormalizedIdMap.get(lafId);
String standardId = javaIdToNormalizedId.get(lafId);
Icon overriddenIcon = currentValues.getResolvedIcon(standardId);
if (overriddenIcon != null && currentIcon != overriddenIcon) {
defaults.put(lafId, overriddenIcon);
@ -572,7 +589,7 @@ public class UiDefaultsMapper {
private void installOverriddenFontsIntoUIDefaults(GThemeValueMap currentValues) {
for (String lafId : extractedValues.getFontIds()) {
Font currentFont = extractedValues.getResolvedFont(lafId);
String standardId = lafIdToNormalizedIdMap.get(lafId);
String standardId = javaIdToNormalizedId.get(lafId);
Font overriddenFont = currentValues.getResolvedFont(standardId);
if (overriddenFont != null && overriddenFont != currentFont) {
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
* 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) {
String normalizedId = lafIdToNormalizedIdMap.get(lafId);
String normalizedId = javaIdToNormalizedId.get(lafId);
if (normalizedId == null) {
Msg.debug(this, "Missing value for laf id: \"" + lafId);
return;
@ -644,54 +673,59 @@ public class UiDefaultsMapper {
* @return a list of all ids that have the given value type
*/
private List<String> getLookAndFeelIdsForType(Class<?> clazz) {
List<String> colorKeys = new ArrayList<>();
List<String> ids = new ArrayList<>();
List<Object> keyList = IteratorUtils.toList(defaults.keys().asIterator());
for (Object key : keyList) {
if (key instanceof String) {
Object value = defaults.get(key);
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
* in the order the system ids are given in the constructor.
* Used to match values (Colors or Fonts) into appropriate system groups. System group are
* 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)
*/
private abstract class ValueMatcher<T> {
private Map<T, String> map = new HashMap<>();
private List<String> systemIdList;
private abstract class ValueGrouper<T> {
private Map<T, String> idsByFont = new HashMap<>();
private List<String> groupIds;
private boolean initialized;
ValueMatcher(String... systemIds) {
systemIdList = new ArrayList<>(Arrays.asList(systemIds));
ValueGrouper(String... ids) {
groupIds = new ArrayList<>(Arrays.asList(ids));
}
private void initialize() {
initialized = true;
// process in reverse order so that earlier items in the list can overwrite later
// items if they have the same font
for (int i = systemIdList.size() - 1; i >= 0; i--) {
String systemId = systemIdList.get(i);
T value = getValueFromJavaDefaults(systemId);
// items if they have the same value
for (int i = groupIds.size() - 1; i >= 0; i--) {
String groupId = groupIds.get(i);
T value = getValueFromJavaDefaults(groupId);
if (value != null) {
map.put(value, systemId);
idsByFont.put(value, groupId);
}
}
}
protected abstract T getValueFromJavaDefaults(String systemId);
String getSystemId(T value) {
String getGroupId(T value) {
if (!initialized) {
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
* 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);
}
@ -718,8 +752,8 @@ public class UiDefaultsMapper {
* 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.
*/
private class FontMatcher extends ValueMatcher<Font> {
FontMatcher(String... systemIds) {
private class FontGrouper extends ValueGrouper<Font> {
FontGrouper(String... systemIds) {
super(systemIds);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,6 +91,12 @@ 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
@ -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 {

View file

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

View file

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