GP-1981 Created font modifiers for referred fonts

and added property grouping for UIDefault colors and fonts. Also added a
lot of javadocs and unit tests. Also introduced concept of "system"
values so we can map to java LookAndFeel values

GP-1981 added "system" properties
This commit is contained in:
ghidragon 2022-08-22 17:39:58 -04:00
parent 15e633c239
commit 780d4b7671
55 changed files with 2719 additions and 1485 deletions

View file

@ -15,17 +15,10 @@
*/
package generic.theme;
import java.awt.Color;
import java.awt.Font;
import java.io.*;
import java.util.*;
import javax.swing.Icon;
import javax.swing.plaf.FontUIResource;
import ghidra.util.Msg;
import ghidra.util.WebColors;
import resources.ResourceManager;
/**
* Abstract base class for reading theme values either in sections (theme property files) or no
@ -101,33 +94,29 @@ public abstract class AbstractThemeReader {
}
private IconValue parseIconProperty(String key, String value) {
if (IconValue.isIconKey(value)) {
return new IconValue(key, value);
}
Icon icon = ResourceManager.loadImage(value);
return new IconValue(key, icon);
return IconValue.parse(key, value);
}
private FontValue parseFontProperty(String key, String value, int lineNumber) {
if (FontValue.isFontKey(value)) {
return new FontValue(key, value);
try {
FontValue parsedValue = FontValue.parse(key, value);
if (parsedValue == null) {
error(lineNumber, "Could not parse Font value: " + value);
}
return parsedValue;
}
Font font = Font.decode(value);
if (font == null) {
error(lineNumber, "Could not parse Color: " + value);
catch (Exception e) {
error(lineNumber, "Could not parse Font value: " + value + "because " + e.getMessage());
}
return font == null ? null : new FontValue(key, new FontUIResource(font));
return null;
}
private ColorValue parseColorProperty(String key, String value, int lineNumber) {
if (ColorValue.isColorKey(value)) {
return new ColorValue(key, value);
ColorValue parsedValue = ColorValue.parse(key, value);
if (parsedValue == null) {
error(lineNumber, "Could not parse Color value: " + value);
}
Color color = WebColors.getColor(value);
if (color == null) {
error(lineNumber, "Could not parse Color: " + value);
}
return color == null ? null : new ColorValue(key, color);
return parsedValue;
}
private List<Section> readSections(LineNumberReader reader) throws IOException {

View file

@ -18,6 +18,7 @@ package generic.theme;
import java.awt.Color;
import ghidra.util.Msg;
import ghidra.util.WebColors;
import utilities.util.reflection.ReflectionUtilities;
/**
@ -27,8 +28,9 @@ 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> {
static final String COLOR_ID_PREFIX = "color.";
static final String EXTERNAL_PREFIX = "[color]";
private static final String COLOR_ID_PREFIX = "color.";
private static final String EXTERNAL_PREFIX = "[color]";
private static final String SYSTEM_COLOR_PREFIX = "system.color";
public static final Color LAST_RESORT_DEFAULT = Color.GRAY;
@ -53,13 +55,45 @@ public class ColorValue extends ThemeValue<Color> {
super(id, refId, null);
}
@Override
public String getSerializationString() {
String outputId = toExternalId(id);
return outputId + " = " + getSerializedValue();
}
/**
* 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) ||
key.startsWith(SYSTEM_COLOR_PREFIX);
}
/**
* Parses the value string into a color or reference and creates a new ColorValue using
* the given key and the parse results.
* @param key the key to associate the parsed value with
* @param value the color value to parse
* @return a ColorValue with the given key and the parsed value
*/
public static ColorValue parse(String key, String value) {
String id = fromExternalId(key);
if (isColorKey(value)) {
return new ColorValue(id, fromExternalId(value));
}
Color color = WebColors.getColor(value);
return color == null ? null : new ColorValue(id, color);
}
@Override
protected ColorValue getReferredValue(GThemeValueMap values, String refId) {
return values.getColor(refId);
}
@Override
protected Color getUnresolvedReferenceValue(String id) {
protected Color getUnresolvedReferenceValue(String unresolvedId) {
Throwable t = ReflectionUtilities.createThrowableWithStackOlderThan();
StackTraceElement[] trace = t.getStackTrace();
@ -69,35 +103,26 @@ public class ColorValue extends ThemeValue<Color> {
t.setStackTrace(filtered);
Msg.error(this,
"Could not resolve indirect color for \"" + id + "\", using last resort default!", t);
"Could not resolve indirect color for \"" + unresolvedId +
"\", using last resort default!",
t);
return LAST_RESORT_DEFAULT;
}
@Override
public String toExternalId(String internalId) {
if (internalId.startsWith(COLOR_ID_PREFIX)) {
private static String toExternalId(String internalId) {
if (internalId.startsWith(COLOR_ID_PREFIX) || internalId.startsWith(SYSTEM_COLOR_PREFIX)) {
return internalId;
}
return EXTERNAL_PREFIX + internalId;
}
@Override
public String fromExternalId(String externalId) {
private static String fromExternalId(String externalId) {
if (externalId.startsWith(EXTERNAL_PREFIX)) {
return externalId.substring(EXTERNAL_PREFIX.length());
}
return externalId;
}
/**
* 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);
}
private static Color getRawColor(Color value) {
if (value instanceof GColor) {
return null;
@ -112,4 +137,16 @@ public class ColorValue extends ThemeValue<Color> {
return null;
}
private String getSerializedValue() {
if (referenceId != null) {
return toExternalId(referenceId);
}
String outputString = WebColors.toString(value, false);
String colorName = WebColors.toWebColorName(value);
if (colorName != null) {
outputString += " // " + colorName;
}
return outputString;
}
}

View file

@ -0,0 +1,166 @@
/* ###
* 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 java.awt.Font;
import java.util.*;
import java.util.regex.Pattern;
public class FontModifier {
private static final Pattern MODIFIER_PATTERN = Pattern.compile("(\\[([a-zA-Z]+|[0-9]+)\\])*");
private String family;
private Integer style;
private Integer size;
public FontModifier() {
}
public FontModifier(String family, Integer style, Integer size) {
this.family = family;
this.style = style;
this.size = size;
}
public void addFamilyModifier(String newFamily) {
if (family != null) {
throw new IllegalStateException("Multiple font family names specified");
}
this.family = newFamily;
}
public void addSizeModfier(int newSize) {
if (size != null) {
throw new IllegalStateException("Multiple font sizes specified");
}
this.size = newSize;
}
public void addStyleModifier(int newStyle) {
if (style == null) {
style = newStyle;
return;
}
if (style == Font.PLAIN || newStyle == Font.PLAIN) {
throw new IllegalStateException("Attempted to set incompable styles");
}
style = style | newStyle;
}
public Font modify(Font font) {
if (family == null) {
if (style != null && size != null) {
return font.deriveFont(style, size);
}
else if (style != null) {
return font.deriveFont(style);
}
return font.deriveFont((float) size);
}
int newStyle = style != null ? style : font.getStyle();
int newSize = size != null ? size : font.getSize();
return new Font(family, newStyle, newSize);
}
public static FontModifier parse(String value) {
List<String> modifierValues = getModifierPieces(value);
if (modifierValues.isEmpty()) {
return null;
}
FontModifier modifier = new FontModifier();
for (String modifierString : modifierValues) {
if (setSize(modifier, modifierString)) {
continue;
}
if (setStyle(modifier, modifierString)) {
continue;
}
modifier.addFamilyModifier(modifierString);
}
if (modifier.hadModifications()) {
return modifier;
}
return null;
}
public String getSerializationString() {
StringBuilder builder = new StringBuilder();
if (family != null) {
builder.append("[" + family + "]");
}
if (size != null) {
builder.append("[" + size + "]");
}
if (style != null) {
switch (style.intValue()) {
case Font.PLAIN:
builder.append("[plain]");
break;
case Font.BOLD:
builder.append("[bold]");
break;
case Font.ITALIC:
builder.append("[italic]");
break;
case Font.BOLD | Font.ITALIC:
builder.append("[bold][italic]");
break;
}
}
return builder.toString();
}
private boolean hadModifications() {
return family != null || size != null || style != null;
}
private static boolean setStyle(FontModifier modifier, String modifierString) {
int style = FontValue.getStyle(modifierString);
if (style >= 0) {
modifier.addStyleModifier(style);
return true;
}
return false;
}
private static boolean setSize(FontModifier modifier, String modifierString) {
try {
int size = Integer.parseInt(modifierString);
modifier.addSizeModfier(size);
return true;
}
catch (NumberFormatException e) {
return false;
}
}
private static List<String> getModifierPieces(String value) {
if (!MODIFIER_PATTERN.matcher(value).matches()) {
throw new IllegalArgumentException("Invalid font modifier string");
}
StringTokenizer tokenizer = new StringTokenizer(value, "[]");
List<String> list = new ArrayList<>();
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken().trim();
if (!token.isBlank()) {
list.add(token);
}
}
return list;
}
}

View file

@ -29,6 +29,7 @@ public class FontValue extends ThemeValue<Font> {
static final String FONT_ID_PREFIX = "font.";
public static final Font LAST_RESORT_DEFAULT = new Font("monospaced", Font.PLAIN, 12);
private static final String EXTERNAL_PREFIX = "[font]";
private FontModifier modifier;
/**
* Constructor used when the FontValue will have a direct {@link Font} value. The refId
@ -50,31 +51,44 @@ public class FontValue extends ThemeValue<Font> {
super(id, refId, null);
}
@Override
protected FontValue getReferredValue(GThemeValueMap values, String refId) {
return values.getFont(refId);
private FontValue(String id, String refId, FontModifier modifier) {
super(id, refId, null);
this.modifier = modifier;
}
@Override
protected Font getUnresolvedReferenceValue(String id) {
Msg.warn(this, "Could not resolve indirect font for" + id + ", using last resort default");
return LAST_RESORT_DEFAULT;
}
@Override
public String toExternalId(String internalId) {
if (internalId.startsWith(FONT_ID_PREFIX)) {
return internalId;
public Font get(GThemeValueMap values) {
Font font = super.get(values);
if (modifier != null) {
return modifier.modify(font);
}
return EXTERNAL_PREFIX + internalId;
return font;
}
@Override
public String fromExternalId(String externalId) {
if (externalId.startsWith(EXTERNAL_PREFIX)) {
return externalId.substring(EXTERNAL_PREFIX.length());
public String getSerializationString() {
String outputId = toExternalId(id);
return outputId + " = " + getValueOutput();
}
private String getValueOutput() {
if (referenceId != null) {
String refId = toExternalId(referenceId);
if (modifier != null) {
return "(" + refId + modifier.getSerializationString() + ")";
}
return refId;
}
return externalId;
return fontToString(value);
}
/**
* Converts a file to a string.
* @param font the font to convert to a String
* @return a String that represents the font
*/
public static String fontToString(Font font) {
return String.format("%s-%s-%s", font.getName(), getStyleString(font), font.getSize());
}
/**
@ -86,4 +100,127 @@ public class FontValue extends ThemeValue<Font> {
return key.startsWith(FONT_ID_PREFIX) || key.startsWith(EXTERNAL_PREFIX);
}
/**
* Parses the value string into a font or reference and creates a new FontValue using
* the given key and the parse results.
* @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
*/
public static FontValue parse(String key, String value) {
String id = fromExternalId(key);
value = clean(value);
if (isFontKey(value)) {
return getRefFontValue(id, value);
}
Font font = parseFont(value);
return font == null ? null : new FontValue(id, font);
}
public static int getStyle(String styleString) {
if ("plain".equalsIgnoreCase(styleString)) {
return Font.PLAIN;
}
if ("bold".equalsIgnoreCase(styleString)) {
return Font.BOLD;
}
if ("italic".equalsIgnoreCase(styleString)) {
return Font.ITALIC;
}
if ("bolditalic".equalsIgnoreCase(styleString)) {
return Font.BOLD | Font.ITALIC;
}
return -1;
}
@Override
protected FontValue getReferredValue(GThemeValueMap values, String refId) {
return values.getFont(refId);
}
@Override
protected Font getUnresolvedReferenceValue(String unresolvedId) {
Msg.warn(this, "Could not resolve indirect font for" + unresolvedId +
", using last resort default");
return LAST_RESORT_DEFAULT;
}
private static String toExternalId(String internalId) {
if (internalId.startsWith(FONT_ID_PREFIX)) {
return internalId;
}
return EXTERNAL_PREFIX + internalId;
}
private static String fromExternalId(String externalId) {
if (externalId.startsWith(EXTERNAL_PREFIX)) {
return externalId.substring(EXTERNAL_PREFIX.length());
}
return externalId;
}
private static Font parseFont(String value) {
int sizeIndex = value.lastIndexOf("-");
int styleIndex = value.lastIndexOf("-", sizeIndex - 1);
if (sizeIndex <= 0 || styleIndex <= 0) {
return null;
}
String sizeString = value.substring(sizeIndex + 1);
String styleString = value.substring(styleIndex + 1, sizeIndex);
String familyName = value.substring(0, styleIndex);
try {
int size = Integer.parseInt(sizeString);
int style = getStyle(styleString);
if (style >= 0) {
return new Font(familyName, style, size);
}
}
catch (NumberFormatException e) {
// parse failed, return null
}
return null;
}
private static FontValue getRefFontValue(String id, String value) {
if (value.startsWith(EXTERNAL_PREFIX)) {
value = value.substring(EXTERNAL_PREFIX.length());
}
int modIndex = value.indexOf("[");
if (modIndex < 0) {
return new FontValue(id, fromExternalId(value));
}
String refId = value.substring(0, modIndex).trim();
FontModifier modifier = FontModifier.parse(value.substring(modIndex));
return new FontValue(id, refId, modifier);
}
private static String clean(String value) {
value = value.trim();
if (value.startsWith("(")) {
value = value.substring(1);
}
if (value.endsWith(")")) {
value = value.substring(0, value.length() - 1);
}
return value;
}
private static String getStyleString(Font font) {
boolean bold = font.isBold();
boolean italic = font.isItalic();
if (bold && italic) {
return "BOLDITALIC";
}
if (bold) {
return "BOLD";
}
if (italic) {
return "ITALIC";
}
return "PLAIN";
}
}

View file

@ -63,7 +63,7 @@ public class GColor extends Color {
public GColor(String id, boolean validate) {
super(0x808080);
this.id = id;
delegate = Gui.getRawColor(id, validate);
delegate = Gui.getColor(id, validate);
inUseColors.add(this);
}
@ -214,7 +214,7 @@ public class GColor extends Color {
* Reloads the delegate.
*/
public void refresh() {
Color color = Gui.getRawColor(id, false);
Color color = Gui.getColor(id, false);
if (color != null) {
if (alpha != null) {
delegate = new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);

View file

@ -71,7 +71,7 @@ public class GIcon implements Icon {
*/
public GIcon(String id, boolean validate) {
this.id = id;
delegate = Gui.getRawIcon(id, validate);
delegate = Gui.getIcon(id, validate);
inUseIcons.add(this);
}
@ -122,7 +122,7 @@ public class GIcon implements Icon {
* Reloads the delegate.
*/
public void refresh() {
Icon icon = Gui.getRawIcon(id, false);
Icon icon = Gui.getIcon(id, false);
if (icon != null) {
delegate = icon;
}

View file

@ -32,7 +32,7 @@ public class GThemeDefaults {
public static final String COLOR_BG = "color.bg"; // TODO do we need this?; rename to use 'background'?
public static class Java {
public static final String BORDER = "Component.borderColor"; // TODO
public static final String BORDER = "system.color.border"; // TODO
}
}

View file

@ -275,4 +275,25 @@ public class GThemeValueMap {
return files;
}
@Override
public int hashCode() {
return Objects.hash(colorMap, fontMap, iconMap);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
GThemeValueMap other = (GThemeValueMap) obj;
return Objects.equals(colorMap, other.colorMap) && Objects.equals(fontMap, other.fontMap) &&
Objects.equals(iconMap, other.iconMap);
}
}

View file

@ -16,20 +16,19 @@
package generic.theme;
import java.awt.*;
import java.io.*;
import java.io.File;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicLookAndFeel;
import com.formdev.flatlaf.*;
import generic.theme.builtin.*;
import generic.theme.laf.LookAndFeelManager;
import ghidra.framework.*;
import ghidra.framework.preferences.Preferences;
import ghidra.framework.OperatingSystem;
import ghidra.framework.Platform;
import ghidra.util.Msg;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.datastruct.WeakDataStructureFactory;
@ -53,20 +52,18 @@ import utilities.util.reflection.ReflectionUtilities;
*
*/
public class Gui {
public static final String THEME_DIR = "themes";
public static final String BACKGROUND_KEY = "color.bg.text";
private static final String THEME_PREFFERENCE_KEY = "Theme";
private static GTheme activeTheme = getDefaultTheme();
private static Set<GTheme> allThemes = null;
private static GThemeValueMap ghidraLightDefaults = new GThemeValueMap();
private static GThemeValueMap ghidraDarkDefaults = new GThemeValueMap();
private static GThemeValueMap applicationDefaults = new GThemeValueMap();
private static GThemeValueMap applicationDarkDefaults = new GThemeValueMap();
private static GThemeValueMap javaDefaults = new GThemeValueMap();
private static GThemeValueMap currentValues = new GThemeValueMap();
private static ThemePropertiesLoader themePropertiesLoader = new ThemePropertiesLoader();
private static ThemeFileLoader themeFileLoader = new ThemeFileLoader();
private static ThemePreferenceManager themePreferenceManager = new ThemePreferenceManager();
private static Map<String, GColorUIResource> gColorMap = new HashMap<>();
private static boolean isInitialized;
@ -80,6 +77,7 @@ public class Gui {
// stores the original value for ids whose value has changed from the current theme
private static GThemeValueMap changedValuesMap = new GThemeValueMap();
private static LookAndFeelManager lookAndFeelManager;
static Font DEFAULT_FONT = new Font("Dialog", Font.PLAIN, 12);
private Gui() {
// static utils class, can't construct
@ -92,14 +90,14 @@ public class Gui {
isInitialized = true;
installFlatLookAndFeels();
loadThemeDefaults();
setTheme(getThemeFromPreferences());
setTheme(themePreferenceManager.getTheme());
// LookAndFeelUtils.installGlobalOverrides();
}
/**
* Reloads the defaults from all the discoverable theme.property files.
*/
public static void reloadGhidraDefaults() {
public static void reloadApplicationDefaults() {
loadThemeDefaults();
buildCurrentValues();
lookAndFeelManager.resetAll(javaDefaults);
@ -128,10 +126,11 @@ public class Gui {
try {
lookAndFeelManager.installLookAndFeel();
notifyThemeChanged(new AllValuesChangedThemeEvent(true));
saveThemeToPreferences(theme);
themePreferenceManager.saveThemeToPreferences(theme);
}
catch (Exception e) {
Msg.error(Gui.class, "Error setting LookAndFeel: " + lookAndFeel.getName(), e);
Msg.error(Gui.class,
"Error setting LookAndFeel: " + lookAndFeel.getName(), e);
}
}
}
@ -230,38 +229,53 @@ public class Gui {
}
/**
* Saves the current theme choice to {@link Preferences}.
* @param theme the theme to remember in {@link Preferences}
* Returns the current {@link Font} associated with the given id. A default font will be
* returned if the font can't be resolved and an error message will be printed to the console.
* @param id the id for the desired font
* @return the current {@link Font} associated with the given id.
*/
public static void saveThemeToPreferences(GTheme theme) {
Preferences.setProperty(THEME_PREFFERENCE_KEY, theme.getThemeLocater());
Preferences.store();
public static Font getFont(String id) {
return getFont(id, true);
}
/**
* Returns the current {@link Font} associated with the given id.
* @param id the id for the desired font
* @param validate if true, will print an error message to the console if the id can't be
* resolved
* @return the current {@link Font} associated with the given id.
*/
public static Font getFont(String id) {
public static Font getFont(String id, boolean validate) {
FontValue font = currentValues.getFont(id);
if (font == null) {
Throwable t = getFilteredTrace();
Msg.error(Gui.class, "No font value registered for: " + id, t);
return null;
if (font == null) {
if (validate && isInitialized) {
Throwable t = getFilteredTrace();
Msg.error(Gui.class,
"No color value registered for: '" + id + "'", t);
}
return DEFAULT_FONT;
}
return font.get(currentValues);
}
/**
* Returns the actual direct color for the id, not a GColor. Will output an error message if
* Returns the {@link Color} registered for the given id. Will output an error message if
* the id can't be resolved.
* @param id the id to get the direct color for
* @return the actual direct color for the id, not a GColor
* @return the {@link Color} registered for the given id.
*/
public static Color getRawColor(String id) {
return getRawColor(id, true);
public static Color getColor(String id) {
return getColor(id, true);
}
/**
* Updates the current font for the given id.
* @param id the font id to update to the new color
* @param font the new font for the id
*/
public static void setFont(String id, Font font) {
setFont(new FontValue(id, font));
}
/**
@ -281,8 +295,7 @@ public class Gui {
// update all java LookAndFeel fonts affected by this changed
String id = newValue.getId();
Set<String> changedFontIds = findChangedJavaFontIds(id);
Font newFont = newValue.get(currentValues);
lookAndFeelManager.fontsChanged(changedFontIds, newFont);
lookAndFeelManager.fontsChanged(changedFontIds);
}
/**
@ -380,24 +393,12 @@ public class Gui {
* @param map the default theme values defined by the {@link LookAndFeel}
*/
public static void setJavaDefaults(GThemeValueMap map) {
javaDefaults = fixupJavaDefaultsInheritence(map);
javaDefaults = map;
buildCurrentValues();
GColor.refreshAll();
GIcon.refreshAll();
}
/**
* Attempts to restore the relationships between various theme values that derive from
* other theme values as defined in {@link BasicLookAndFeel}
* @param map the map of value ids to its inherited id
* @return a fixed up version of the given map with relationships restored where possible
*/
public static GThemeValueMap fixupJavaDefaultsInheritence(GThemeValueMap map) {
JavaColorMapping.fixupJavaDefaultsInheritence(map);
JavaFontMapping.fixupJavaDefaultsInheritence(map);
return map;
}
/**
* Returns the {@link GThemeValueMap} containing all the default theme values defined by the
* current {@link LookAndFeel}.
@ -417,9 +418,9 @@ public class Gui {
* @return the {@link GThemeValueMap} containing all the dark values defined in
* theme.properties files
*/
public static GThemeValueMap getGhidraDarkDefaults() {
GThemeValueMap map = new GThemeValueMap(ghidraLightDefaults);
map.load(ghidraDarkDefaults);
public static GThemeValueMap getApplicationDarkDefaults() {
GThemeValueMap map = new GThemeValueMap(applicationDefaults);
map.load(applicationDarkDefaults);
return map;
}
@ -429,8 +430,8 @@ public class Gui {
* @return the {@link GThemeValueMap} containing all the standard values defined in
* theme.properties files
*/
public static GThemeValueMap getGhidraLightDefaults() {
GThemeValueMap map = new GThemeValueMap(ghidraLightDefaults);
public static GThemeValueMap getApplicationLightDefaults() {
GThemeValueMap map = new GThemeValueMap(applicationDefaults);
return map;
}
@ -441,9 +442,9 @@ public class Gui {
*/
public static GThemeValueMap getDefaults() {
GThemeValueMap currentDefaults = new GThemeValueMap(javaDefaults);
currentDefaults.load(ghidraLightDefaults);
currentDefaults.load(applicationDefaults);
if (activeTheme.useDarkDefaults()) {
currentDefaults.load(ghidraDarkDefaults);
currentDefaults.load(applicationDarkDefaults);
}
return currentDefaults;
}
@ -509,18 +510,20 @@ public class Gui {
}
/**
* Returns the actual direct color for the id, not a GColor.
* Returns the color for the id. If there is no color registered for this id, then Color.CYAN
* is returned as the default color.
* @param id the id to get the direct color for
* @param validate if true, will output an error if the id can't be resolved at this time
* @return the actual direct color for the id, not a GColor
*/
public static Color getRawColor(String id, boolean validate) {
public static Color getColor(String id, boolean validate) {
ColorValue color = currentValues.getColor(id);
if (color == null) {
if (validate && isInitialized) {
Throwable t = getFilteredTrace();
Msg.error(Gui.class, "No color value registered for: '" + id + "'", t);
Msg.error(Gui.class,
"No color value registered for: '" + id + "'", t);
}
return Color.CYAN;
}
@ -528,17 +531,29 @@ public class Gui {
}
/**
* Returns the actual direct icon for the id, not a GIcon.
* @param id the id to get the direct icon for
* @param validate if true, will output an error if the id can't be resolved at this time
* @return the actual direct icon for the id, not a GIcon
* Returns the Icon registered for the given id. If no icon is registered for the id,
* the default icon will be returned and an error message will be dumped to the console
* @param id the id to get the registered icon for
* @return the actual icon registered for the given id
*/
public static Icon getRawIcon(String id, boolean validate) {
public static Icon getIcon(String id) {
return getIcon(id, true);
}
/**
* Returns the {@link Icon} registered for the given id. If no icon is registered, returns
* the default icon (bomb).
* @param id the id to get the register icon for
* @param validate if true, will output an error if the id can't be resolved at this time
* @return the Icon registered for the given id
*/
public static Icon getIcon(String id, boolean validate) {
IconValue icon = currentValues.getIcon(id);
if (icon == null) {
if (validate && isInitialized) {
Throwable t = getFilteredTrace();
Msg.error(Gui.class, "No icon value registered for: '" + id + "'", t);
Msg.error(Gui.class,
"No icon value registered for: '" + id + "'", t);
}
return ResourceManager.getDefaultIcon();
}
@ -569,11 +584,6 @@ public class Gui {
return color.brighter();
}
// for testing
public static void setPropertiesLoader(ThemePropertiesLoader loader) {
themePropertiesLoader = loader;
}
/**
* Binds the component to the font identified by the given font id. Whenever the font for
* the font id changes, the component will updated with the new font.
@ -592,9 +602,9 @@ public class Gui {
}
private static void loadThemeDefaults() {
themePropertiesLoader.load();
ghidraLightDefaults = themePropertiesLoader.getDefaults();
ghidraDarkDefaults = themePropertiesLoader.getDarkDefaults();
themeFileLoader.loadThemeDefaultFiles();
applicationDefaults = themeFileLoader.getDefaults();
applicationDarkDefaults = themeFileLoader.getDarkDefaults();
}
private static void notifyThemeChanged(ThemeEvent event) {
@ -616,9 +626,9 @@ public class Gui {
GThemeValueMap map = new GThemeValueMap();
map.load(javaDefaults);
map.load(ghidraLightDefaults);
map.load(applicationDefaults);
if (activeTheme.useDarkDefaults()) {
map.load(ghidraDarkDefaults);
map.load(applicationDarkDefaults);
}
map.load(activeTheme);
currentValues = map;
@ -629,72 +639,15 @@ public class Gui {
if (allThemes == null) {
Set<GTheme> set = new HashSet<>();
set.addAll(findDiscoverableThemes());
set.addAll(loadThemesFromFiles());
set.addAll(themeFileLoader.loadThemeFiles());
allThemes = set;
}
}
private static Collection<GTheme> loadThemesFromFiles() {
List<File> fileList = new ArrayList<>();
FileFilter themeFileFilter = file -> file.getName().endsWith("." + GTheme.FILE_EXTENSION);
File dir = Application.getUserSettingsDirectory();
File themeDir = new File(dir, THEME_DIR);
File[] files = themeDir.listFiles(themeFileFilter);
if (files != null) {
fileList.addAll(Arrays.asList(files));
}
List<GTheme> list = new ArrayList<>();
for (File file : fileList) {
GTheme theme = loadTheme(file);
if (theme != null) {
list.add(theme);
}
}
return list;
}
private static GTheme loadTheme(File file) {
try {
return new ThemeReader(file).readTheme();
}
catch (IOException e) {
Msg.error(Gui.class, "Could not load theme from file: " + file.getAbsolutePath(), e);
}
return null;
}
private static Collection<DiscoverableGTheme> findDiscoverableThemes() {
return ClassSearcher.getInstances(DiscoverableGTheme.class);
}
private static GTheme getThemeFromPreferences() {
String themeId = Preferences.getProperty(THEME_PREFFERENCE_KEY, "Default", true);
if (themeId.startsWith(GTheme.FILE_PREFIX)) {
String filename = themeId.substring(GTheme.FILE_PREFIX.length());
try {
return new ThemeReader(new File(filename)).readTheme();
}
catch (IOException e) {
Msg.showError(GTheme.class, null, "Can't Load Previous Theme",
"Error loading theme file: " + filename, e);
}
}
else if (themeId.startsWith(DiscoverableGTheme.CLASS_PREFIX)) {
String className = themeId.substring(DiscoverableGTheme.CLASS_PREFIX.length());
try {
Class<?> forName = Class.forName(className);
return (GTheme) forName.getDeclaredConstructor().newInstance();
}
catch (Exception e) {
Msg.showError(GTheme.class, null, "Can't Load Previous Theme",
"Can't find or instantiate class: " + className);
}
}
return getDefaultTheme();
}
private static void updateChangedValuesMap(ColorValue currentValue, ColorValue newValue) {
String id = newValue.getId();
ColorValue originalValue = changedValuesMap.getColor(id);
@ -762,4 +715,14 @@ public class Gui {
return affectedIds;
}
// for testing
public static void setPropertiesLoader(ThemeFileLoader loader) {
allThemes = null;
themeFileLoader = loader;
}
public static void setThemePreferenceManager(ThemePreferenceManager manager) {
themePreferenceManager = manager;
}
}

View file

@ -19,6 +19,7 @@ import javax.swing.Icon;
import ghidra.util.Msg;
import resources.ResourceManager;
import resources.icons.UrlImageIcon;
/**
* A class for storing {@link Icon} values that have a String id (e.g. icon.bg.foo) and either
@ -54,43 +55,76 @@ public class IconValue extends ThemeValue<Icon> {
super(id, refId, null);
}
@Override
public String getSerializationString() {
String outputId = toExternalId(id);
return outputId + " = " + getValueOutput();
}
/**
* 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);
}
/**
* Converts an icon to a string.
* @param icon the icon to convert to a String
* @return a String that represents the icon
*/
public static String iconToString(Icon icon) {
if (icon instanceof UrlImageIcon urlIcon) {
return urlIcon.getOriginalPath();
}
return GTheme.JAVA_ICON;
}
/**
* Parses the value string into an icon or reference and creates a new IconValue using
* the given key and the parse results.
* @param key the key to associate the parsed value with
* @param value the color value to parse
* @return an IconValue with the given key and the parsed value
*/
public static IconValue parse(String key, String value) {
String id = fromExternalId(key);
if (isIconKey(value)) {
return new IconValue(id, fromExternalId(value));
}
Icon icon = ResourceManager.loadImage(value);
return new IconValue(id, icon);
}
@Override
protected IconValue getReferredValue(GThemeValueMap values, String refId) {
return values.getIcon(refId);
}
@Override
protected Icon getUnresolvedReferenceValue(String id) {
protected Icon getUnresolvedReferenceValue(String unresolvedId) {
Msg.warn(this,
"Could not resolve indirect icon path for" + id + ", using last resort default");
"Could not resolve indirect icon path for" + unresolvedId +
", using last resort default");
return LAST_RESORT_DEFAULT;
}
@Override
public String toExternalId(String internalId) {
private static String toExternalId(String internalId) {
if (internalId.startsWith(ICON_ID_PREFIX)) {
return internalId;
}
return EXTERNAL_PREFIX + internalId;
}
@Override
public String fromExternalId(String externalId) {
private static String fromExternalId(String externalId) {
if (externalId.startsWith(EXTERNAL_PREFIX)) {
return externalId.substring(EXTERNAL_PREFIX.length());
}
return externalId;
}
/**
* 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);
}
private static Icon getRawIcon(Icon value) {
if (value instanceof GIcon) {
return null;
@ -104,4 +138,12 @@ public class IconValue extends ThemeValue<Icon> {
}
return null;
}
private String getValueOutput() {
if (referenceId != null) {
return toExternalId(referenceId);
}
return iconToString(value);
}
}

View file

@ -96,20 +96,23 @@ public enum LafType {
private static LookAndFeelManager getManager(LafType lookAndFeel) {
switch (lookAndFeel) {
case MAC:
return new MacLookAndFeelManager();
case METAL:
return new MetalLookAndFeelManager();
case WINDOWS:
return new WindowsLookAndFeelManager();
case WINDOWS_CLASSIC:
return new GenericLookAndFeelManager(lookAndFeel);
case FLAT_DARCULA:
case FLAT_DARK:
case FLAT_LIGHT:
return new GenericFlatLookAndFeelManager(lookAndFeel);
return new WindowsClassicLookAndFeelManager();
case GTK:
return new GtkLookAndFeelManager();
case MOTIF:
return new MotifLookAndFeelManager();
case NIMBUS:
return new NimbusLookAndFeelManager();
case FLAT_DARCULA:
case FLAT_DARK:
case FLAT_LIGHT:
return new FlatLookAndFeelManager(lookAndFeel);
default:
throw new AssertException("No lookAndFeelManager defined for " + lookAndFeel);
}

View file

@ -15,8 +15,8 @@
*/
package generic.theme;
import java.io.IOException;
import java.util.List;
import java.io.*;
import java.util.*;
import generic.jar.ResourceFile;
import ghidra.framework.Application;
@ -26,23 +26,23 @@ import ghidra.util.Msg;
* Loads all the system theme.property files that contain all the default color, font, and
* icon values.
*/
public class ThemePropertiesLoader {
GThemeValueMap defaults = new GThemeValueMap();
GThemeValueMap darkDefaults = new GThemeValueMap();
public class ThemeFileLoader {
public static final String THEME_DIR = "themes";
public ThemePropertiesLoader() {
}
private GThemeValueMap defaults = new GThemeValueMap();
private GThemeValueMap darkDefaults = new GThemeValueMap();
/**
* Searches for all the theme.property files and loads them into either the standard
* defaults (light) map or the dark defaults map.
*/
public void load() {
List<ResourceFile> themeDefaultFiles =
Application.findFilesByExtensionInApplication(".theme.properties");
public void loadThemeDefaultFiles() {
defaults.clear();
darkDefaults.clear();
List<ResourceFile> themeDefaultFiles =
Application.findFilesByExtensionInApplication(".theme.properties");
for (ResourceFile resourceFile : themeDefaultFiles) {
Msg.debug(this, "found theme file: " + resourceFile.getAbsolutePath());
try {
@ -57,6 +57,28 @@ public class ThemePropertiesLoader {
}
}
public Collection<GTheme> loadThemeFiles() {
List<File> fileList = new ArrayList<>();
FileFilter themeFileFilter = file -> file.getName().endsWith("." + GTheme.FILE_EXTENSION);
File dir = Application.getUserSettingsDirectory();
File themeDir = new File(dir, THEME_DIR);
File[] files = themeDir.listFiles(themeFileFilter);
if (files != null) {
fileList.addAll(Arrays.asList(files));
}
List<GTheme> list = new ArrayList<>();
for (File file : fileList) {
GTheme theme = loadTheme(file);
if (theme != null) {
list.add(theme);
}
}
return list;
}
/**
* Returns the standard defaults {@link GThemeValueMap}
* @return the standard defaults {@link GThemeValueMap}
@ -73,4 +95,13 @@ public class ThemePropertiesLoader {
return darkDefaults;
}
private static GTheme loadTheme(File file) {
try {
return new ThemeReader(file).readTheme();
}
catch (IOException e) {
Msg.error(Gui.class, "Could not load theme from file: " + file.getAbsolutePath(), e);
}
return null;
}
}

View file

@ -0,0 +1,69 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.theme;
import java.io.File;
import java.io.IOException;
import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg;
/**
* Reads and writes current theme info to preferences
*/
public class ThemePreferenceManager {
private static final String THEME_PREFFERENCE_KEY = "Theme";
/**
* Returns the theme that was stored in preferences or the default theme if none stored.
* @return the last theme used (stored in preferences) or the default theme if not stored
* in preferences
*/
public GTheme getTheme() {
String themeId = Preferences.getProperty(THEME_PREFFERENCE_KEY, "Default", true);
if (themeId.startsWith(GTheme.FILE_PREFIX)) {
String filename = themeId.substring(GTheme.FILE_PREFIX.length());
try {
return new ThemeReader(new File(filename)).readTheme();
}
catch (IOException e) {
Msg.showError(GTheme.class, null, "Can't Load Previous Theme",
"Error loading theme file: " + filename, e);
}
}
else if (themeId.startsWith(DiscoverableGTheme.CLASS_PREFIX)) {
String className = themeId.substring(DiscoverableGTheme.CLASS_PREFIX.length());
try {
Class<?> forName = Class.forName(className);
return (GTheme) forName.getDeclaredConstructor().newInstance();
}
catch (Exception e) {
Msg.showError(GTheme.class, null, "Can't Load Previous Theme",
"Can't find or instantiate class: " + className);
}
}
return Gui.getDefaultTheme();
}
/**
* Saves the current theme choice to {@link Preferences}.
* @param theme the theme to remember in {@link Preferences}
*/
public void saveThemeToPreferences(GTheme theme) {
Preferences.setProperty(THEME_PREFFERENCE_KEY, theme.getThemeLocater());
Preferences.store();
}
}

View file

@ -28,13 +28,13 @@ 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>> {
private final String id;
private final T value;
private final String refId;
protected final String id;
protected final T value;
protected final String referenceId;
protected ThemeValue(String id, String refId, T value) {
this.id = fromExternalId(id);
this.refId = (refId == null) ? null : fromExternalId(refId);
protected ThemeValue(String id, String referenceId, T value) {
this.id = id;
this.referenceId = referenceId;
this.value = value;
}
@ -54,7 +54,7 @@ public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> {
* a value
*/
public String getReferenceId() {
return refId;
return referenceId;
}
/**
@ -82,7 +82,7 @@ public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> {
Set<String> visitedKeys = new HashSet<>();
visitedKeys.add(id);
ThemeValue<T> parent = getReferredValue(values, refId);
ThemeValue<T> parent = getReferredValue(values, referenceId);
// loop resolving indirect references
while (parent != null) {
@ -90,11 +90,11 @@ public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> {
return parent.value;
}
visitedKeys.add(parent.id);
if (visitedKeys.contains(parent.refId)) {
if (visitedKeys.contains(parent.referenceId)) {
Msg.warn(this, "Theme value reference loop detected for key: " + id);
return getUnresolvedReferenceValue(id);
}
parent = getReferredValue(values, parent.refId);
parent = getReferredValue(values, parent.referenceId);
}
return getUnresolvedReferenceValue(id);
}
@ -107,66 +107,64 @@ public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> {
* @return true if this ThemeValue derives its value from the given ancestorId.
*/
public boolean inheritsFrom(String ancestorId, GThemeValueMap values) {
if (refId == null) {
if (referenceId == null) {
return false;
}
if (refId.equals(ancestorId)) {
if (referenceId.equals(ancestorId)) {
return true;
}
Set<String> visitedKeys = new HashSet<>();
visitedKeys.add(id);
ThemeValue<T> parent = getReferredValue(values, refId);
ThemeValue<T> parent = getReferredValue(values, referenceId);
// loop resolving indirect references
while (parent != null) {
if (parent.refId == null) {
if (parent.referenceId == null) {
return false;
}
if (parent.refId.equals(ancestorId)) {
if (parent.referenceId.equals(ancestorId)) {
return true;
}
visitedKeys.add(parent.id);
if (visitedKeys.contains(parent.refId)) {
if (visitedKeys.contains(parent.referenceId)) {
return false;
}
parent = getReferredValue(values, parent.refId);
parent = getReferredValue(values, parent.referenceId);
}
return false;
}
/**
* Returns true if this ColorValue gets its value from some other ColorValue
* @return true if this ColorValue gets its value from some other ColorValue
*/
public boolean isIndirect() {
return referenceId != null;
}
/**
* Returns the "key = value" String for writing this ThemeValue to a file
* @return the "key = value" String for writing this ThemeValue to a file
*/
public abstract String getSerializationString();
/**
* Returns the T to be used if the indirect reference couldn't be resolved.
* @param unresolvedId the id that couldn't be resolved
* @return the default value to be used if the indirect reference couldn't be resolved.
*/
abstract protected T getUnresolvedReferenceValue(String unresolvedId);
/**
* Returns the id to be used when writing to a theme file. For ThemeValues whose id begins
* with the expected prefix (e.g. "color" for ColorValues), it is just the id. Otherwise, the
* id is prepended with an appropriate string to make parsing easier.
* @param internalId the id of this ThemeValue
* @return the id to be used when writing to a theme file
*/
abstract public String toExternalId(String internalId);
/**
* Converts an external id to an internal id (the id stored in this object)
* @param externalId the external form of the id
* @return the id for the ThemeValue being read from a file
*/
abstract public String fromExternalId(String externalId);
protected abstract T getUnresolvedReferenceValue(String unresolvedId);
/**
* Returns the ThemeValue referred to by this ThemeValue. Needs to be overridden by
* concrete classes as they know the correct method to call on the preferredValues map.
* @param preferredValues the {@link GThemeValueMap} to be used to resolve the reference id
* @param referenceId the id of the reference ThemeValue
* @param refId the id of the reference ThemeValue
* @return the ThemeValue referred to by this ThemeValue.
*/
abstract protected ThemeValue<T> getReferredValue(GThemeValueMap preferredValues,
String referenceId);
protected abstract ThemeValue<T> getReferredValue(GThemeValueMap preferredValues,
String refId);
@Override
public int compareTo(ThemeValue<T> o) {
@ -175,7 +173,7 @@ public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> {
@Override
public int hashCode() {
return Objects.hash(id, refId, value);
return Objects.hash(id, referenceId, value);
}
@Override
@ -191,17 +189,17 @@ public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> {
}
ThemeValue<?> other = (ThemeValue<?>) obj;
return Objects.equals(id, other.id) && Objects.equals(refId, other.refId) &&
return Objects.equals(id, other.id) && Objects.equals(referenceId, other.referenceId) &&
Objects.equals(value, other.value);
}
@Override
public String toString() {
String name = getClass().getSimpleName();
if (refId == null) {
if (referenceId == null) {
return name + " (" + id + ", " + value + ")";
}
return name + " (" + id + ", " + refId + ")";
return name + " (" + id + ", " + referenceId + ")";
}
}

View file

@ -15,20 +15,13 @@
*/
package generic.theme;
import java.awt.Color;
import java.awt.Font;
import java.io.*;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.swing.Icon;
import com.google.common.io.Files;
import ghidra.util.WebColors;
import resources.icons.UrlImageIcon;
/**
* Writes a theme to a file either as a single theme file or as a zip file that contains the theme
* file and any external (from the file system, not the classpath) icons used by the theme.
@ -113,87 +106,20 @@ public class ThemeWriter {
writer.newLine();
for (ColorValue colorValue : colors) {
String outputId = colorValue.toExternalId(colorValue.getId());
writer.write(outputId + " = " + getValueOutput(colorValue));
writer.write(colorValue.getSerializationString());
writer.newLine();
}
for (FontValue fontValue : fonts) {
String outputId = fontValue.toExternalId(fontValue.getId());
writer.write(outputId + " = " + getValueOutput(fontValue));
writer.write(fontValue.getSerializationString());
writer.newLine();
}
for (IconValue iconValue : icons) {
String outputId = iconValue.toExternalId(iconValue.getId());
writer.write(outputId + " = " + getValueOutput(iconValue));
writer.write(iconValue.getSerializationString());
writer.newLine();
}
}
private String getValueOutput(ColorValue colorValue) {
if (colorValue.getReferenceId() != null) {
return colorValue.toExternalId(colorValue.getReferenceId());
}
Color color = colorValue.getRawValue();
String outputString = WebColors.toString(color, false);
String colorName = WebColors.toWebColorName(color);
if (colorName != null) {
outputString += " // " + colorName;
}
return outputString;
}
private String getValueOutput(IconValue iconValue) {
if (iconValue.getReferenceId() != null) {
return iconValue.toExternalId(iconValue.getReferenceId());
}
Icon icon = iconValue.getRawValue();
return iconToString(icon);
}
private String getValueOutput(FontValue fontValue) {
if (fontValue.getReferenceId() != null) {
return fontValue.toExternalId(fontValue.getReferenceId());
}
Font font = fontValue.getRawValue();
return fontToString(font);
}
private static String getStyleString(Font font) {
boolean bold = font.isBold();
boolean italic = font.isItalic();
if (bold && italic) {
return "BOLDITALIC";
}
if (bold) {
return "BOLD";
}
if (italic) {
return "ITALIC";
}
return "PLAIN";
}
/**
* Converts a file to a string.
* @param font the font to convert to a String
* @return a String that represents the font
*/
public static String fontToString(Font font) {
return String.format("%s-%s-%s", font.getName(), getStyleString(font), font.getSize());
}
/**
* Converts an icon to a string.
* @param icon the icon to convert to a String
* @return a String that represents the icon
*/
public static String iconToString(Icon icon) {
if (icon instanceof UrlImageIcon urlIcon) {
return urlIcon.getOriginalPath();
}
return GTheme.JAVA_ICON;
}
private void copyToZipFile(String dir, File iconFile, ZipOutputStream zos) throws IOException {

View file

@ -1,253 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.theme.builtin;
import static java.util.Map.*;
import java.awt.Color;
import java.util.List;
import java.util.Map;
import generic.theme.ColorValue;
import generic.theme.GThemeValueMap;
/**
* Maps Java UIDefaults color ids relationships to an id it inherits from
*/
public class JavaColorMapping {
private static Map<String, String> map = Map.ofEntries(
entry("Button.background", "control"),
entry("Button.foreground", "controlText"),
entry("Button.shadow", "controlShadow"),
entry("Button.darkShadow", "controlDkShadow"),
entry("Button.light", "controlHighlight"),
entry("Button.highlight", "controlLtHighlight"),
entry("ToggleButton.background", "control"),
entry("ToggleButton.foreground", "controlText"),
entry("ToggleButton.shadow", "controlShadow"),
entry("ToggleButton.darkShadow", "controlDkShadow"),
entry("ToggleButton.light", "controlHighlight"),
entry("ToggleButton.highlight", "controlLtHighlight"),
entry("RadioButton.background", "control"),
entry("RadioButton.foreground", "controlText"),
entry("RadioButton.shadow", "controlShadow"),
entry("RadioButton.darkShadow", "controlDkShadow"),
entry("RadioButton.light", "controlHighlight"),
entry("RadioButton.highlight", "controlLtHighlight"),
entry("CheckBox.background", "control"),
entry("CheckBox.foreground", "controlText"),
entry("ColorChooser.background", "control"),
entry("ColorChooser.foreground", "controlText"),
entry("ColorChooser.swatchesDefaultRecentColor", "control"),
entry("ComboBox.background", "window"),
entry("ComboBox.foreground", "textText"),
entry("ComboBox.buttonBackground", "control"),
entry("ComboBox.buttonShadow", "controlShadow"),
entry("ComboBox.buttonDarkShadow", "controlDkShadow"),
entry("ComboBox.buttonHighlight", "controlLtHighlight"),
entry("ComboBox.selectionBackground", "textHighlight"),
entry("ComboBox.selectionForeground", "textHighlightText"),
entry("ComboBox.disabledBackground", "control"),
entry("ComboBox.disabledForeground", "textHInactiveText"),
entry("InternalFrame.borderColor", "control"),
entry("InternalFrame.borderShadow", "controlShadow"),
entry("InternalFrame.borderDarkShadow", "controlDkShadow"),
entry("InternalFrame.borderHighlight", "controlLtHighlight"),
entry("InternalFrame.borderLight", "controlHighlight"),
entry("InternalFrame.activeTitleBackground", "activeCaption"),
entry("InternalFrame.activeTitleForeground", "activeCaptionText"),
entry("InternalFrame.inactiveTitleBackground", "inactiveCaption"),
entry("InternalFrame.inactiveTitleForeground", "inactiveCaptionText"),
entry("Label.background", "control"),
entry("Label.foreground", "controlText"),
entry("Label.disabledShadow", "controlShadow"),
entry("List.background", "window"),
entry("List.foreground", "textText"),
entry("List.selectionBackground", "textHighlight"),
entry("List.selectionForeground", "textHighlightText"),
entry("List.dropLineColor", "controlShadow"),
entry("MenuBar.background", "menu"),
entry("MenuBar.foreground", "menuText"),
entry("MenuBar.shadow", "controlShadow"),
entry("MenuBar.highlight", "controlLtHighlight"),
entry("MenuItem.background", "menu"),
entry("MenuItem.foreground", "menuText"),
entry("MenuItem.selectionForeground", "textHighlightText"),
entry("MenuItem.selectionBackground", "textHighlight"),
entry("MenuItem.acceleratorForeground", "menuText"),
entry("MenuItem.acceleratorSelectionForeground", "textHighlightText"),
entry("RadioButtonMenuItem.background", "menu"),
entry("RadioButtonMenuItem.foreground", "menuText"),
entry("RadioButtonMenuItem.selectionForeground", "textHighlightText"),
entry("RadioButtonMenuItem.selectionBackground", "textHighlight"),
entry("RadioButtonMenuItem.acceleratorForeground", "menuText"),
entry("RadioButtonMenuItem.acceleratorSelectionForeground", "textHighlightText"),
entry("CheckBoxMenuItem.background", "menu"),
entry("CheckBoxMenuItem.foreground", "menuText"),
entry("CheckBoxMenuItem.selectionForeground", "textHighlightText"),
entry("CheckBoxMenuItem.selectionBackground", "textHighlight"),
entry("CheckBoxMenuItem.acceleratorForeground", "menuText"),
entry("CheckBoxMenuItem.acceleratorSelectionForeground", "textHighlightText"),
entry("Menu.background", "menu"),
entry("Menu.foreground", "menuText"),
entry("Menu.selectionForeground", "textHighlightText"),
entry("Menu.selectionBackground", "textHighlight"),
entry("Menu.acceleratorForeground", "menuText"),
entry("Menu.acceleratorSelectionForeground", "textHighlightText"),
entry("PopupMenu.background", "menu"),
entry("PopupMenu.foreground", "menuText"),
entry("OptionPane.background", "control"),
entry("OptionPane.foreground", "controlText"),
entry("OptionPane.messageForeground", "controlText"),
entry("Panel.background", "control"),
entry("Panel.foreground", "textText"),
entry("ProgressBar.foreground", "textHighlight"),
entry("ProgressBar.background", "control"),
entry("ProgressBar.selectionForeground", "control"),
entry("ProgressBar.selectionBackground", "textHighlight"),
entry("Separator.background", "controlLtHighlight"),
entry("Separator.foreground", "controlShadow"),
entry("ScrollBar.foreground", "control"),
entry("ScrollBar.track", "scrollbar"),
entry("ScrollBar.trackHighlight", "controlDkShadow"),
entry("ScrollBar.thumb", "control"),
entry("ScrollBar.thumbHighlight", "controlLtHighlight"),
entry("ScrollBar.thumbDarkShadow", "controlDkShadow"),
entry("ScrollBar.thumbShadow", "controlShadow"),
entry("ScrollPane.background", "control"),
entry("ScrollPane.foreground", "controlText"),
entry("Viewport.background", "control"),
entry("Viewport.foreground", "textText"),
entry("Slider.foreground", "control"),
entry("Slider.background", "control"),
entry("Slider.highlight", "controlLtHighlight"),
entry("Slider.shadow", "controlShadow"),
entry("Slider.focus", "controlDkShadow"),
entry("Spinner.background", "control"),
entry("Spinner.foreground", "control"),
entry("SplitPane.background", "control"),
entry("SplitPane.highlight", "controlLtHighlight"),
entry("SplitPane.shadow", "controlShadow"),
entry("SplitPane.darkShadow", "controlDkShadow"),
entry("TabbedPane.background", "control"),
entry("TabbedPane.foreground", "controlText"),
entry("TabbedPane.highlight", "controlLtHighlight"),
entry("TabbedPane.light", "controlHighlight"),
entry("TabbedPane.shadow", "controlShadow"),
entry("TabbedPane.darkShadow", "controlDkShadow"),
entry("TabbedPane.focus", "controlText"),
entry("Table.foreground", "controlText"),
entry("Table.background", "window"),
entry("Table.selectionForeground", "textHighlightText"),
entry("Table.selectionBackground", "textHighlight"),
entry("Table.dropLineColor", "controlShadow"),
entry("Table.focusCellBackground", "window"),
entry("Table.focusCellForeground", "controlText"),
entry("TableHeader.foreground", "controlText"),
entry("TableHeader.background", "control"),
entry("TableHeader.focusCellBackground", "text"),
entry("TextField.background", "window"),
entry("TextField.foreground", "textText"),
entry("TextField.shadow", "controlShadow"),
entry("TextField.darkShadow", "controlDkShadow"),
entry("TextField.light", "controlHighlight"),
entry("TextField.highlight", "controlLtHighlight"),
entry("TextField.inactiveForeground", "textHInactiveText"),
entry("TextField.inactiveBackground", "control"),
entry("TextField.selectionBackground", "textHighlight"),
entry("TextField.selectionForeground", "textHighlightText"),
entry("TextField.caretForeground", "textText"),
entry("FormattedTextField.background", "window"),
entry("FormattedTextField.foreground", "textText"),
entry("FormattedTextField.inactiveForeground", "textHInactiveText"),
entry("FormattedTextField.inactiveBackground", "control"),
entry("FormattedTextField.selectionBackground", "textHighlight"),
entry("FormattedTextField.selectionForeground", "textHighlightText"),
entry("FormattedTextField.caretForeground", "textText"),
entry("PasswordField.background", "window"),
entry("PasswordField.foreground", "textText"),
entry("PasswordField.inactiveForeground", "textHInactiveText"),
entry("PasswordField.inactiveBackground", "control"),
entry("PasswordField.selectionBackground", "textHighlight"),
entry("PasswordField.selectionForeground", "textHighlightText"),
entry("PasswordField.caretForeground", "textText"),
entry("TextArea.background", "window"),
entry("TextArea.foreground", "textText"),
entry("TextArea.inactiveForeground", "textHInactiveText"),
entry("TextArea.selectionBackground", "textHighlight"),
entry("TextArea.selectionForeground", "textHighlightText"),
entry("TextArea.caretForeground", "textText"),
entry("TextPane.foreground", "textText"),
entry("TextPane.selectionBackground", "textHighlight"),
entry("TextPane.selectionForeground", "textHighlightText"),
entry("TextPane.caretForeground", "textText"),
entry("TextPane.inactiveForeground", "textHInactiveText"),
entry("EditorPane.foreground", "textText"),
entry("EditorPane.selectionBackground", "textHighlight"),
entry("EditorPane.selectionForeground", "textHighlightText"),
entry("EditorPane.caretForeground", "textText"),
entry("EditorPane.inactiveForeground", "textHInactiveText"),
entry("TitledBorder.titleColor", "controlText"),
entry("ToolBar.background", "control"),
entry("ToolBar.foreground", "controlText"),
entry("ToolBar.shadow", "controlShadow"),
entry("ToolBar.darkShadow", "controlDkShadow"),
entry("ToolBar.light", "controlHighlight"),
entry("ToolBar.highlight", "controlLtHighlight"),
entry("ToolBar.dockingBackground", "control"),
entry("ToolBar.floatingBackground", "control"),
entry("ToolTip.background", "info"),
entry("ToolTip.foreground", "infoText"),
entry("Tree.background", "window"),
entry("Tree.foreground", "textText"),
entry("Tree.textForeground", "textText"),
entry("Tree.textBackground", "text"),
entry("Tree.selectionForeground", "textHighlightText"),
entry("Tree.selectionBackground", "textHighlight"),
entry("Tree.dropLineColor", "controlShadow"));
public static void fixupJavaDefaultsInheritence(GThemeValueMap values) {
List<ColorValue> colors = values.getColors();
for (ColorValue value : colors) {
ColorValue mapped = map(values, value);
if (mapped != null) {
values.addColor(mapped);
}
}
}
private static ColorValue map(GThemeValueMap values, ColorValue value) {
String id = value.getId();
String refId = map.get(id);
if (refId == null) {
return null;
}
ColorValue refValue = values.getColor(refId);
if (refValue == null) {
return null;
}
Color originalColor = value.get(values);
Color refColor = refValue.get(values);
if (originalColor == null || refColor == null) {
return null;
}
if (originalColor.getRGB() == refColor.getRGB()) {
return new ColorValue(id, refId);
}
return null;
}
}

View file

@ -1,143 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.theme.builtin;
import static java.util.Map.*;
import java.awt.Font;
import java.util.List;
import java.util.Map;
import generic.theme.FontValue;
import generic.theme.GThemeValueMap;
/**
* Maps Java UIDefaults font ids relationships to an id it inherits from
*/
public class JavaFontMapping {
private final static String BUTTON_GROUP = "ButtonComponents.font";
private final static String TEXT_GROUP = "TextComponents.font";
private final static String MENU_GROUP = "MenuComponents.font";
private final static String MENU_ACCELERATOR_GROUP = "MenuComponents.acceleratorFont";
private final static String DIALOG_GROUP = "Dialogs.font";
private final static String WIDGET_GROUP = "Components.font";
private static Map<String, String> map = Map.ofEntries(
entry("ArrowButton.font", BUTTON_GROUP), // nimbus
entry("Button.font", BUTTON_GROUP),
entry("CheckBox.font", BUTTON_GROUP),
entry("RadioButton.font", BUTTON_GROUP),
entry("ToggleButton.font", BUTTON_GROUP),
entry("CheckBoxMenuItem.font", MENU_GROUP),
entry("Menu.font", MENU_GROUP),
entry("MenuBar.font", MENU_GROUP),
entry("MenuItem.font", MENU_GROUP),
entry("PopupMenu.font", MENU_GROUP),
entry("RadioButtonMenuItem.font", MENU_GROUP),
entry("CheckBoxMenuItem.acceleratorFont", MENU_ACCELERATOR_GROUP), // metal, motif
entry("Menu.acceleratorFont", MENU_ACCELERATOR_GROUP), // metal, motif
entry("MenuItem.acceleratorFont", MENU_ACCELERATOR_GROUP), // metal, motfi
entry("RadioButtonMenuItem.acceleratorFont", MENU_ACCELERATOR_GROUP), // metal
entry("EditorPane.font", TEXT_GROUP),
entry("FormattedTextField.font", TEXT_GROUP),
entry("PasswordField.font", TEXT_GROUP),
entry("TextArea.font", TEXT_GROUP),
entry("TextField.font", TEXT_GROUP),
entry("TextPane.font", TEXT_GROUP),
entry("ColorChooser.font", DIALOG_GROUP),
entry("FileChooser.font", DIALOG_GROUP), // nimbus
entry("ComboBox.font", WIDGET_GROUP),
entry("InternalFrame.titleFont", WIDGET_GROUP), // metal, motif, flat
entry("Label.font", WIDGET_GROUP),
entry("List.font", WIDGET_GROUP),
entry("OptionPane.font", DIALOG_GROUP),
entry("Panel.font", WIDGET_GROUP),
entry("ProgressBar.font", WIDGET_GROUP),
entry("RootPane.font", WIDGET_GROUP),
entry("Scrollbar.font", WIDGET_GROUP),
entry("ScrollBarThumb.font", WIDGET_GROUP), // nimbus
entry("ScrollBarTrack.font", WIDGET_GROUP), // nimbus
entry("ScrollPane.font", WIDGET_GROUP),
entry("Separator.font", WIDGET_GROUP), // nimbus
entry("Slider.font", WIDGET_GROUP),
entry("SliderThumb.font", WIDGET_GROUP), // nimbus
entry("SliderTrack.font", WIDGET_GROUP), // nimbus
entry("Spinner.font", WIDGET_GROUP),
entry("SplitPane.font", WIDGET_GROUP), // nimbus
entry("TabbedPane.font", WIDGET_GROUP),
entry("TitledBorder.font", WIDGET_GROUP),
entry("ToolBar.font", WIDGET_GROUP),
entry("ToolTip.font", TEXT_GROUP),
entry("Viewport.font", WIDGET_GROUP),
entry("Tree.font", WIDGET_GROUP),
entry("Table.font", WIDGET_GROUP),
entry("TableHeader.font", "Table.font"));
public static void fixupJavaDefaultsInheritence(GThemeValueMap values) {
createGroupDefaults(values);
List<FontValue> fonts = values.getFonts();
for (FontValue value : fonts) {
FontValue mapped = map(values, value);
if (mapped != null) {
values.addFont(mapped);
}
}
}
private static FontValue map(GThemeValueMap values, FontValue value) {
String id = value.getId();
String refId = map.get(id);
if (refId == null) {
return null;
}
FontValue refValue = values.getFont(refId);
if (refValue == null) {
return null;
}
Font originalFont = value.get(values);
Font refFont = refValue.get(values);
if (originalFont == null || refFont == null) {
return null;
}
if (originalFont.equals(refFont)) {
return new FontValue(id, refId);
}
return null;
}
public static void createGroupDefaults(GThemeValueMap valuesMap) {
addFontValue(valuesMap, BUTTON_GROUP, "Button.font");
addFontValue(valuesMap, TEXT_GROUP, "TextField.font");
addFontValue(valuesMap, MENU_GROUP, "Menu.font");
addFontValue(valuesMap, MENU_ACCELERATOR_GROUP, "Menu.acceleratorFont");
addFontValue(valuesMap, DIALOG_GROUP, "ColorChooser.font");
addFontValue(valuesMap, WIDGET_GROUP, "Label.font");
}
private static void addFontValue(GThemeValueMap valuesMap, String groupId, String exemplarId) {
FontValue font = valuesMap.getFont(exemplarId);
if (font != null) {
valuesMap.addFont(new FontValue(groupId, font.get(valuesMap)));
}
}
}

View file

@ -17,24 +17,30 @@ package generic.theme.laf;
import javax.swing.UIManager;
import generic.theme.ColorValue;
import generic.theme.LafType;
/**
* Common {@link LookAndFeelInstaller} for any of the "Flat" lookAndFeels
*/
public class FlatLookAndFeelInstaller extends LookAndFeelInstaller {
public class FlatLookAndFeelManager extends LookAndFeelManager {
public FlatLookAndFeelInstaller(LafType lookAndFeelType) {
super(lookAndFeelType);
public FlatLookAndFeelManager(LafType laf) {
super(laf);
// establish system color to LookAndFeel colors
systemToLafMap.addColor(new ColorValue(SYSTEM_WIDGET_BACKGROUND_COLOR_ID, "text"));
systemToLafMap.addColor(new ColorValue(SYSTEM_TOOLTIP_BACKGROUND_COLOR_ID, "info"));
}
@Override
protected void fixupLookAndFeelIssues() {
super.fixupLookAndFeelIssues();
// We have historically managed button focusability ourselves. Allow this by default so
// 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 ThemeGrouper getThemeGrouper() {
return new FlatThemeGrouper();
}
}

View file

@ -0,0 +1,64 @@
/* ###
* 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.laf;
import generic.theme.GThemeValueMap;
/**
* Adds specialized groupings unique to the Flat LookAndFeels
*/
public class FlatThemeGrouper extends ThemeGrouper {
@Override
public void group(GThemeValueMap values) {
// we made up a source property for a common Flat color. Picked one of them as
// an exemplar (menu.foreground)
// @formatter:off
defineCustomColorGroup(values, "color.flat.menu.hover.bg", "MenuBar.hoverBackground");
defineCustomColorGroup(values, "color.flat.button.hover.bg", "Button.hoverBackground");
defineCustomColorGroup(values, "color.flat.button.selected.bg","Button.selectedBackground");
defineCustomColorGroup(values, "color.flat.button.toolbar.hover.bg","Button.toolbar.hoverBackground");
defineCustomColorGroup(values, "color.flat.button.toolbar.pressed.bg","Button.toolbar.pressedBackground");
defineCustomColorGroup(values, "color.flat.checkbox.icon.focus.border","CheckBox.icon.focusedBorderColor");
defineCustomColorGroup(values, "color.flat.menu.accelerator.fg","Menu.acceleratorForeground");
defineCustomColorGroup(values, "color.flat.focus.border", "Button.focusedBorderColor");
defineCustomColorGroup(values, "color.flat.focus", "Component.focusColor");
defineCustomColorGroup(values, "color.flat.focus.bg", "Button.focusedBackground");
defineCustomColorGroup(values, "color.flat.checkmark", "CheckBox.icon.checkmarkColor");
defineCustomColorGroup(values, "color.flat.disabled", "Button.disabledBorderColor");
defineCustomColorGroup(values, "color.flat.disabled.selected","Button.disabledSelectedBackground");
defineCustomColorGroup(values, "color.flat.arrow","Spinner.buttonArrowColor");
defineCustomColorGroup(values, "color.flat.arrow.disabled","Spinner.buttonDisabledArrowColor");
defineCustomColorGroup(values, "color.flat.arrow.hover","Spinner.buttonHoverArrowColor");
defineCustomColorGroup(values, "color.flat.arrow.pressed","Spinner.buttonPressedArrowColor");
defineCustomColorGroup(values, "color.flat.dropcell.bg","List.dropCellBackground");
defineCustomColorGroup(values, "color.flat.dropline","List.dropLineColor");
defineCustomColorGroup(values, "color.flat.underline","MenuItem.underlineSelectionColor");
defineCustomColorGroup(values, "color.flat.docking.bg","ToolBar.dockingBackground");
defineCustomColorGroup(values, "color.flat.progressbar.bg","ProgressBar.background");
defineCustomColorGroup(values, "color.flat.progressbar.fg","ProgressBar.foreground");
defineCustomColorGroup(values, "color.flat.icon.bg","Tree.icon.openColor");
defineCustomColorGroup(values, "color.flat.selection.inactive","Tree.selectionInactiveBackground");
// @formatter:on
super.group(values);
}
}

View file

@ -55,7 +55,7 @@ public class GNimbusLookAndFeel extends NimbusLookAndFeel {
String id = iconValue.getId();
// because some icons are weird, put raw icons into defaults, only use GIcons for
// setting Icons explicitly on components
Icon icon = Gui.getRawIcon(id, true);
Icon icon = Gui.getIcon(id, true);
defaults.put(id, icon);
}
@ -69,21 +69,21 @@ public class GNimbusLookAndFeel extends NimbusLookAndFeel {
GThemeValueMap javaDefaults = new GThemeValueMap();
List<String> colorIds =
LookAndFeelInstaller.getLookAndFeelIdsForType(defaults, Color.class);
LookAndFeelManager.getLookAndFeelIdsForType(defaults, Color.class);
for (String id : colorIds) {
Color color = defaults.getColor(id);
ColorValue value = new ColorValue(id, color);
javaDefaults.addColor(value);
}
List<String> fontIds =
LookAndFeelInstaller.getLookAndFeelIdsForType(defaults, Font.class);
LookAndFeelManager.getLookAndFeelIdsForType(defaults, Font.class);
for (String id : fontIds) {
Font font = defaults.getFont(id);
FontValue value = new FontValue(id, LookAndFeelInstaller.fromUiResource(font));
FontValue value = new FontValue(id, LookAndFeelManager.fromUiResource(font));
javaDefaults.addFont(value);
}
List<String> iconIds =
LookAndFeelInstaller.getLookAndFeelIdsForType(defaults, Icon.class);
LookAndFeelManager.getLookAndFeelIdsForType(defaults, Icon.class);
for (String id : iconIds) {
Icon icon = defaults.getIcon(id);
javaDefaults.addIcon(new IconValue(id, icon));
@ -91,7 +91,6 @@ public class GNimbusLookAndFeel extends NimbusLookAndFeel {
// need to set javaDefalts now to trigger building currentValues so the when
// we create GColors below, they can be resolved.
Gui.setJavaDefaults(javaDefaults);
return javaDefaults;
}
}

View file

@ -1,45 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.theme.laf;
import javax.swing.LookAndFeel;
import javax.swing.UnsupportedLookAndFeelException;
import generic.theme.LafType;
/**
* LookAndFeelInstaller for the GTK {@link LookAndFeel}
*/
public class GtkLookAndFeelInstaller extends LookAndFeelInstaller {
public GtkLookAndFeelInstaller() {
super(LafType.GTK);
}
@Override
protected void installLookAndFeel() throws ClassNotFoundException, InstantiationException,
IllegalAccessException, UnsupportedLookAndFeelException {
super.installLookAndFeel();
}
// @Override
// protected void installJavaDefaults() {
// // GTK does not support changing its values, so set the javaDefaults to an empty map
// Gui.setJavaDefaults(new GThemeValueMap());
// }
//
}

View file

@ -25,10 +25,4 @@ public class GtkLookAndFeelManager extends LookAndFeelManager {
public GtkLookAndFeelManager() {
super(LafType.GTK);
}
@Override
protected LookAndFeelInstaller getLookAndFeelInstaller() {
return new GtkLookAndFeelInstaller();
}
}

View file

@ -1,341 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.theme.laf;
import java.awt.Color;
import java.awt.Font;
import java.util.*;
import java.util.Map.Entry;
import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.UIResource;
import org.apache.commons.collections4.IteratorUtils;
import generic.theme.*;
import generic.util.action.*;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
/**
* Installs a specific {@link LookAndFeel} into the {@link UIManager}. The idea is that there
* is a specific installer for each supported {@link LookAndFeel} to handle unique needs for
* that LookAndFeel. Subclasses can also override {@link #fixupLookAndFeelIssues()} to make
* UI tweaks to specific LookAndFeels.
*/
public class LookAndFeelInstaller {
private LafType lookAndFeel;
public LookAndFeelInstaller(LafType lookAndFeel) {
this.lookAndFeel = lookAndFeel;
}
/**
* Installs the {@link LookAndFeel} associated with this installer
* @throws ClassNotFoundException if the <code>LookAndFeel</code>
* class could not be found
* @throws InstantiationException if a new instance of the class
* couldn't be created
* @throws IllegalAccessException if the class or initializer isn't accessible
* @throws UnsupportedLookAndFeelException if
* <code>lnf.isSupportedLookAndFeel()</code> is false
*/
public final void install() throws ClassNotFoundException, InstantiationException,
IllegalAccessException, UnsupportedLookAndFeelException {
cleanUiDefaults();
installLookAndFeel();
installJavaDefaults();
fixupLookAndFeelIssues();
installGlobalProperties();
}
/**
* Subclass provide this method to install the specific loo
* @throws ClassNotFoundException if the <code>LookAndFeel</code>
* class could not be found
* @throws InstantiationException if a new instance of the class
* couldn't be created
* @throws IllegalAccessException if the class or initializer isn't accessible
* @throws UnsupportedLookAndFeelException if
* <code>lnf.isSupportedLookAndFeel()</code> is false
*/
protected void installLookAndFeel() throws ClassNotFoundException, InstantiationException,
IllegalAccessException, UnsupportedLookAndFeelException {
String name = lookAndFeel.getName();
UIManager.setLookAndFeel(findLookAndFeelClassName(name));
}
/**
* Subclass can override this method to do specific LookAndFeel fix ups
*/
protected void fixupLookAndFeelIssues() {
// no generic fix-ups at this time.
}
/**
* Extracts java default colors, fonts, and icons and stores them in {@link Gui}.
*/
protected void installJavaDefaults() {
GThemeValueMap javaDefaults = extractJavaDefaults();
Gui.setJavaDefaults(javaDefaults);
installPropertiesBackIntoUiDefaults(javaDefaults);
}
private void installPropertiesBackIntoUiDefaults(GThemeValueMap javaDefaults) {
UIDefaults defaults = UIManager.getDefaults();
GTheme theme = Gui.getActiveTheme();
// we replace java default colors with GColor equivalents so that we
// can change colors without having to reinstall ui on each component
// This trick only works for colors. Fonts and icons don't universally
// allow being wrapped like colors do.
for (ColorValue colorValue : javaDefaults.getColors()) {
String id = colorValue.getId();
defaults.put(id, Gui.getGColorUiResource(id));
}
// put fonts back into defaults in case they have been changed by the current theme
for (FontValue fontValue : javaDefaults.getFonts()) {
String id = fontValue.getId();
FontValue themeValue = theme.getFont(id);
if (themeValue != null) {
Font font = Gui.getFont(id);
defaults.put(id, new FontUIResource(font));
}
}
// put icons back into defaults in case they have been changed by the current theme
for (IconValue iconValue : javaDefaults.getIcons()) {
String id = iconValue.getId();
IconValue themeValue = theme.getIcon(id);
if (themeValue != null) {
// because some icons are weird, put raw icons into defaults, only use GIcons for
// setting Icons explicitly on components
Icon icon = Gui.getRawIcon(id, true);
defaults.put(id, icon);
}
}
}
protected GThemeValueMap extractJavaDefaults() {
return extractJavaDefaults(UIManager.getDefaults());
}
protected GThemeValueMap extractJavaDefaults(UIDefaults defaults) {
GThemeValueMap values = new GThemeValueMap();
// for now, just doing color properties.
List<String> ids = getLookAndFeelIdsForType(defaults, Color.class);
for (String id : ids) {
// convert UIResource color to regular colors so if used, they don't get wiped
// out when we update the UIs
values.addColor(new ColorValue(id, fromUiResource(UIManager.getColor(id))));
}
ids = getLookAndFeelIdsForType(defaults, Font.class);
for (String id : ids) {
// convert UIResource fonts to regular fonts so if used, they don't get wiped
// out when we update UIs
values.addFont(new FontValue(id, fromUiResource(UIManager.getFont(id))));
}
ids = getLookAndFeelIdsForType(defaults, Icon.class);
for (String id : ids) {
Icon icon = UIManager.getIcon(id);
values.addIcon(new IconValue(id, icon));
}
return values;
}
protected String findLookAndFeelClassName(String lookAndFeelName) {
LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels();
for (LookAndFeelInfo info : installedLookAndFeels) {
String className = info.getClassName();
if (lookAndFeelName.equals(className) || lookAndFeelName.equals(info.getName())) {
return className;
}
}
Msg.debug(this, "Unable to find requested Look and Feel: " + lookAndFeelName);
return UIManager.getSystemLookAndFeelClassName();
}
protected boolean isSupported(String lookAndFeelName) {
LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels();
for (LookAndFeelInfo info : installedLookAndFeels) {
if (lookAndFeelName.equals(info.getName())) {
return true;
}
}
return false;
}
protected void setKeyBinding(String existingKsText, String newKsText, String[] prefixValues) {
KeyStroke existingKs = KeyStroke.getKeyStroke(existingKsText);
KeyStroke newKs = KeyStroke.getKeyStroke(newKsText);
for (String properyPrefix : prefixValues) {
UIDefaults defaults = UIManager.getDefaults();
Object object = defaults.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
Integer overrideFontInteger = SystemUtilities.getFontSizeOverrideValue();
if (overrideFontInteger == null) {
return;
}
setGlobalFontSizeOverride(overrideFontInteger);
}
private void installCustomLookAndFeelActions() {
// these prefixes are for text components
String[] UIPrefixValues =
{ "TextField", "FormattedTextField", "TextArea", "TextPane", "EditorPane" };
DeleteToStartOfWordAction deleteToStartOfWordAction = new DeleteToStartOfWordAction();
registerAction(deleteToStartOfWordAction, DeleteToStartOfWordAction.KEY_STROKE,
UIPrefixValues);
DeleteToEndOfWordAction deleteToEndOfWordAction = new DeleteToEndOfWordAction();
registerAction(deleteToEndOfWordAction, DeleteToEndOfWordAction.KEY_STROKE, UIPrefixValues);
BeginningOfLineAction beginningOfLineAction = new BeginningOfLineAction();
registerAction(beginningOfLineAction, BeginningOfLineAction.KEY_STROKE, UIPrefixValues);
EndOfLineAction endOfLineAction = new EndOfLineAction();
registerAction(endOfLineAction, EndOfLineAction.KEY_STROKE, UIPrefixValues);
SelectBeginningOfLineAction selectBeginningOfLineAction = new SelectBeginningOfLineAction();
registerAction(selectBeginningOfLineAction, SelectBeginningOfLineAction.KEY_STROKE,
UIPrefixValues);
SelectEndOfLineAction selectEndOfLineAction = new SelectEndOfLineAction();
registerAction(selectEndOfLineAction, SelectEndOfLineAction.KEY_STROKE, UIPrefixValues);
}
/** Allows you to globally set the font size (don't use this method!) */
private void setGlobalFontSizeOverride(int fontSize) {
UIDefaults defaults = UIManager.getDefaults();
Set<Entry<Object, Object>> set = defaults.entrySet();
Iterator<Entry<Object, Object>> iterator = set.iterator();
while (iterator.hasNext()) {
Entry<Object, Object> entry = iterator.next();
Object key = entry.getKey();
if (key.toString().toLowerCase().indexOf("font") != -1) {
Font currentFont = defaults.getFont(key);
if (currentFont != null) {
Font newFont = currentFont.deriveFont((float) fontSize);
UIManager.put(key, newFont);
}
}
}
}
private void registerAction(Action action, KeyStroke keyStroke, String[] prefixValues) {
for (String properyPrefix : prefixValues) {
UIDefaults defaults = UIManager.getDefaults();
Object object = defaults.get(properyPrefix + ".focusInputMap");
InputMap inputMap = (InputMap) object;
inputMap.put(keyStroke, action);
}
}
private void installGlobalProperties() {
installGlobalLookAndFeelAttributes();
installGlobalFontSizeOverride();
installCustomLookAndFeelActions();
installPopupMenuSettingsOverride();
}
public static Color fromUiResource(Color color) {
if (color.getClass() == Color.class) {
return color;
}
return new Color(color.getRGB(), true);
}
public static Font fromUiResource(Font font) {
if (font instanceof UIResource) {
return new FontNonUiResource(font);
}
return font;
}
private void cleanUiDefaults() {
GThemeValueMap javaDefaults = Gui.getJavaDefaults();
if (javaDefaults == null) {
return;
}
UIDefaults defaults = UIManager.getDefaults();
for (ColorValue colorValue : javaDefaults.getColors()) {
String id = colorValue.getId();
defaults.put(id, null);
}
for (FontValue fontValue : javaDefaults.getFonts()) {
String id = fontValue.getId();
defaults.put(id, null);
}
for (IconValue iconValue : javaDefaults.getIcons()) {
String id = iconValue.getId();
defaults.put(id, null);
}
}
public static List<String> getLookAndFeelIdsForType(UIDefaults defaults, Class<?> clazz) {
List<String> colorKeys = 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);
}
}
}
return colorKeys;
}
}

View file

@ -18,25 +18,49 @@ package generic.theme.laf;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.Map.Entry;
import javax.swing.*;
import javax.swing.plaf.FontUIResource;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.plaf.*;
import org.apache.commons.collections4.IteratorUtils;
import generic.theme.*;
import generic.util.action.*;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
/**
* Manages installing and updating a {@link LookAndFeel}
*/
public abstract class LookAndFeelManager {
/**
* These are color ids (see {@link GColor} used to represent general concepts that
* application developers can use to get the color for that concept as defined by
* a specific {@link LookAndFeel}. This class will define some standard defaults
* mappings in the constructor, but it is expected that each specific LookAndFeelManager
* will override these mappings with values appropriate for that LookAndFeel.
*/
protected static final String SYSTEM_APP_BACKGROUND_COLOR_ID = "system.color.bg.application";
protected static final String SYSTEM_WIDGET_BACKGROUND_COLOR_ID = "system.color.bg.widget";
protected static final String SYSTEM_TOOLTIP_BACKGROUND_COLOR_ID = "system.color.bg.tooltip";
protected static final String SYSTEM_BORDER_COLOR_ID = "system.color.border";
private LafType laf;
private Map<String, ComponentFontRegistry> fontRegistryMap = new HashMap<>();
protected GThemeValueMap systemToLafMap = new GThemeValueMap();
protected LookAndFeelManager(LafType laf) {
this.laf = laf;
}
protected abstract LookAndFeelInstaller getLookAndFeelInstaller();
// establish system color to LookAndFeel colors
systemToLafMap.addColor(new ColorValue(SYSTEM_APP_BACKGROUND_COLOR_ID, "control"));
systemToLafMap.addColor(new ColorValue(SYSTEM_WIDGET_BACKGROUND_COLOR_ID, "control"));
systemToLafMap.addColor(new ColorValue(SYSTEM_TOOLTIP_BACKGROUND_COLOR_ID, "control"));
systemToLafMap.addColor(new ColorValue(SYSTEM_BORDER_COLOR_ID, "controlShadow"));
}
/**
* Returns the {@link LafType} managed by this manager.
@ -59,8 +83,11 @@ public abstract class LookAndFeelManager {
public void installLookAndFeel() throws ClassNotFoundException, InstantiationException,
IllegalAccessException, UnsupportedLookAndFeelException {
LookAndFeelInstaller installer = getLookAndFeelInstaller();
installer.install();
cleanUiDefaults();
doInstallLookAndFeel();
installJavaDefaults();
fixupLookAndFeelIssues();
installGlobalProperties();
updateComponentUis();
}
@ -102,7 +129,7 @@ public abstract class LookAndFeelManager {
UIDefaults defaults = UIManager.getDefaults();
for (IconValue iconValue : icons) {
String id = iconValue.getId();
Icon correctIcon = Gui.getRawIcon(id, false);
Icon correctIcon = Gui.getIcon(id, false);
Icon storedIcon = defaults.getIcon(id);
if (correctIcon != null && !correctIcon.equals(storedIcon)) {
defaults.put(id, correctIcon);
@ -120,11 +147,13 @@ public abstract class LookAndFeelManager {
/**
* Called when one or more icons have changed.
* @param id the id of primary icon that changed
* @param changedIconIds
* @param newIcon
* @param changedIconIds set of icon ids affected by this icon change
* @param newIcon the new icon to use for the given set of icon ids
*/
public void iconsChanged(Set<String> changedIconIds, Icon newIcon) {
if (!(newIcon instanceof UIResource)) {
newIcon = new IconUIResource(newIcon);
}
if (!changedIconIds.isEmpty()) {
UIDefaults defaults = UIManager.getDefaults();
for (String javaIconId : changedIconIds) {
@ -139,14 +168,15 @@ 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
* @param newFont the new font for the given ids
*/
public void fontsChanged(Set<String> changedJavaFontIds, Font newFont) {
public void fontsChanged(Set<String> changedJavaFontIds) {
if (!changedJavaFontIds.isEmpty()) {
UIDefaults defaults = UIManager.getDefaults();
newFont = new FontUIResource(newFont);
for (String javaFontId : changedJavaFontIds) {
defaults.put(javaFontId, newFont);
// even though all these derive from the new font, they might be different
// because of FontModifiers.
Font font = Gui.getFont(javaFontId);
defaults.put(javaFontId, new FontUIResource(font));
}
updateComponentFonts(changedJavaFontIds);
updateComponentUis();
@ -175,11 +205,311 @@ public abstract class LookAndFeelManager {
}
}
public void registerFont(Component c, String fontId) {
/**
* Binds the component to the font identified by the given font id. Whenever the font for
* the font id changes, the component will updated with the new font.
* @param component the component to set/update the font
* @param fontId the id of the font to register with the given component
*/
public void registerFont(Component component, String fontId) {
ComponentFontRegistry register =
fontRegistryMap.computeIfAbsent(fontId, id -> new ComponentFontRegistry(id));
register.addComponent(c);
register.addComponent(component);
}
/**
* Returns a color that is not a {@link UIResource}.
* @param color the color to return an non UIResource color for
* @return a color that is not a {@link UIResource}.
*/
public static Color fromUiResource(Color color) {
if (color.getClass() == Color.class) {
return color;
}
return new Color(color.getRGB(), true);
}
/**
* Returns a font that is not a {@link UIResource}.
* @param font the font to return an non UIResource font for
* @return a font that is not a {@link UIResource}.
*/
public static Font fromUiResource(Font font) {
if (font instanceof UIResource) {
return new FontNonUiResource(font);
}
return font;
}
/**
* Subclass provide this method to install the specific loo
* @throws ClassNotFoundException if the <code>LookAndFeel</code>
* class could not be found
* @throws InstantiationException if a new instance of the class
* couldn't be created
* @throws IllegalAccessException if the class or initializer isn't accessible
* @throws UnsupportedLookAndFeelException if
* <code>lnf.isSupportedLookAndFeel()</code> is false
*/
protected void doInstallLookAndFeel() throws ClassNotFoundException, InstantiationException,
IllegalAccessException, UnsupportedLookAndFeelException {
String name = laf.getName();
UIManager.setLookAndFeel(findLookAndFeelClassName(name));
}
/**
* Subclass can override this method to do specific LookAndFeel fix ups
*/
protected void fixupLookAndFeelIssues() {
// no generic fix-ups at this time.
}
/**
* Extracts java default colors, fonts, and icons and stores them in {@link Gui}.
*/
private void installJavaDefaults() {
GThemeValueMap javaDefaults = extractJavaDefaults();
javaDefaults.load(systemToLafMap); // add in our system color mappings
ThemeGrouper grouper = getThemeGrouper();
grouper.group(javaDefaults);
Gui.setJavaDefaults(javaDefaults);
installPropertiesBackIntoUiDefaults(javaDefaults);
}
protected ThemeGrouper getThemeGrouper() {
return new ThemeGrouper();
}
private void installPropertiesBackIntoUiDefaults(GThemeValueMap javaDefaults) {
UIDefaults defaults = UIManager.getDefaults();
GTheme theme = Gui.getActiveTheme();
// we replace java default colors with GColor equivalents so that we
// can change colors without having to reinstall ui on each component
// This trick only works for colors. Fonts and icons don't universally
// allow being wrapped like colors do.
for (ColorValue colorValue : javaDefaults.getColors()) {
String id = colorValue.getId();
defaults.put(id, Gui.getGColorUiResource(id));
}
// put fonts back into defaults in case they have been changed by the current theme
for (FontValue fontValue : javaDefaults.getFonts()) {
String id = fontValue.getId();
FontValue themeValue = theme.getFont(id);
if (themeValue != null) {
Font font = Gui.getFont(id);
defaults.put(id, new FontUIResource(font));
}
}
// put icons back into defaults in case they have been changed by the current theme
for (IconValue iconValue : javaDefaults.getIcons()) {
String id = iconValue.getId();
IconValue themeValue = theme.getIcon(id);
if (themeValue != null) {
// because some icons are weird, put raw icons into defaults, only use GIcons for
// setting Icons explicitly on components
Icon icon = Gui.getIcon(id, true);
defaults.put(id, icon);
}
}
}
protected GThemeValueMap extractJavaDefaults() {
UIDefaults defaults = UIManager.getDefaults();
GThemeValueMap values = new GThemeValueMap();
// for now, just doing color properties.
List<String> ids = getLookAndFeelIdsForType(defaults, Color.class);
for (String id : ids) {
// convert UIResource color to regular colors so if used, they don't get wiped
// out when we update the UIs
values.addColor(new ColorValue(id, fromUiResource(UIManager.getColor(id))));
}
ids = getLookAndFeelIdsForType(defaults, Font.class);
for (String id : ids) {
// convert UIResource fonts to regular fonts so if used, they don't get wiped
// out when we update UIs
values.addFont(new FontValue(id, fromUiResource(UIManager.getFont(id))));
}
ids = getLookAndFeelIdsForType(defaults, Icon.class);
for (String id : ids) {
Icon icon = UIManager.getIcon(id);
values.addIcon(new IconValue(id, icon));
}
return values;
}
protected String findLookAndFeelClassName(String lookAndFeelName) {
LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels();
for (LookAndFeelInfo info : installedLookAndFeels) {
String className = info.getClassName();
if (lookAndFeelName.equals(className) || lookAndFeelName.equals(info.getName())) {
return className;
}
}
Msg.debug(this, "Unable to find requested Look and Feel: " + lookAndFeelName);
return UIManager.getSystemLookAndFeelClassName();
}
protected boolean isSupported(String lookAndFeelName) {
LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels();
for (LookAndFeelInfo info : installedLookAndFeels) {
if (lookAndFeelName.equals(info.getName())) {
return true;
}
}
return false;
}
protected void setKeyBinding(String existingKsText, String newKsText, String[] prefixValues) {
KeyStroke existingKs = KeyStroke.getKeyStroke(existingKsText);
KeyStroke newKs = KeyStroke.getKeyStroke(newKsText);
for (String properyPrefix : prefixValues) {
UIDefaults defaults = UIManager.getDefaults();
Object object = defaults.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
Integer overrideFontInteger = SystemUtilities.getFontSizeOverrideValue();
if (overrideFontInteger == null) {
return;
}
setGlobalFontSizeOverride(overrideFontInteger);
}
private void installCustomLookAndFeelActions() {
// these prefixes are for text components
String[] UIPrefixValues =
{ "TextField", "FormattedTextField", "TextArea", "TextPane", "EditorPane" };
DeleteToStartOfWordAction deleteToStartOfWordAction = new DeleteToStartOfWordAction();
registerAction(deleteToStartOfWordAction, DeleteToStartOfWordAction.KEY_STROKE,
UIPrefixValues);
DeleteToEndOfWordAction deleteToEndOfWordAction = new DeleteToEndOfWordAction();
registerAction(deleteToEndOfWordAction, DeleteToEndOfWordAction.KEY_STROKE, UIPrefixValues);
BeginningOfLineAction beginningOfLineAction = new BeginningOfLineAction();
registerAction(beginningOfLineAction, BeginningOfLineAction.KEY_STROKE, UIPrefixValues);
EndOfLineAction endOfLineAction = new EndOfLineAction();
registerAction(endOfLineAction, EndOfLineAction.KEY_STROKE, UIPrefixValues);
SelectBeginningOfLineAction selectBeginningOfLineAction = new SelectBeginningOfLineAction();
registerAction(selectBeginningOfLineAction, SelectBeginningOfLineAction.KEY_STROKE,
UIPrefixValues);
SelectEndOfLineAction selectEndOfLineAction = new SelectEndOfLineAction();
registerAction(selectEndOfLineAction, SelectEndOfLineAction.KEY_STROKE, UIPrefixValues);
}
/** Allows you to globally set the font size (don't use this method!) */
private void setGlobalFontSizeOverride(int fontSize) {
UIDefaults defaults = UIManager.getDefaults();
Set<Entry<Object, Object>> set = defaults.entrySet();
Iterator<Entry<Object, Object>> iterator = set.iterator();
while (iterator.hasNext()) {
Entry<Object, Object> entry = iterator.next();
Object key = entry.getKey();
if (key.toString().toLowerCase().indexOf("font") != -1) {
Font currentFont = defaults.getFont(key);
if (currentFont != null) {
Font newFont = currentFont.deriveFont((float) fontSize);
UIManager.put(key, newFont);
}
}
}
}
private void registerAction(Action action, KeyStroke keyStroke, String[] prefixValues) {
for (String properyPrefix : prefixValues) {
UIDefaults defaults = UIManager.getDefaults();
Object object = defaults.get(properyPrefix + ".focusInputMap");
InputMap inputMap = (InputMap) object;
inputMap.put(keyStroke, action);
}
}
private void installGlobalProperties() {
installGlobalLookAndFeelAttributes();
installGlobalFontSizeOverride();
installCustomLookAndFeelActions();
installPopupMenuSettingsOverride();
}
private void cleanUiDefaults() {
GThemeValueMap javaDefaults = Gui.getJavaDefaults();
if (javaDefaults == null) {
return;
}
UIDefaults defaults = UIManager.getDefaults();
for (ColorValue colorValue : javaDefaults.getColors()) {
String id = colorValue.getId();
defaults.put(id, null);
}
for (FontValue fontValue : javaDefaults.getFonts()) {
String id = fontValue.getId();
defaults.put(id, null);
}
for (IconValue iconValue : javaDefaults.getIcons()) {
String id = iconValue.getId();
defaults.put(id, null);
}
}
/**
* Searches the given UIDefaults for ids whose value matches the given class
* @param defaults the UIDefaults to search
* @param clazz the value class to look for (i.e., Color, Font, or Icon)
* @return the list of ids whose value is of the given class type.
*/
public static List<String> getLookAndFeelIdsForType(UIDefaults defaults, Class<?> clazz) {
List<String> colorKeys = 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);
}
}
}
return colorKeys;
}
}

View file

@ -17,18 +17,14 @@ package generic.theme.laf;
import generic.theme.LafType;
/**
* Common {@link LookAndFeelManager} for any of the "Flat" lookAndFeels
*/
public class GenericFlatLookAndFeelManager extends LookAndFeelManager {
public class MacLookAndFeelManager extends LookAndFeelManager {
public GenericFlatLookAndFeelManager(LafType laf) {
super(laf);
public MacLookAndFeelManager() {
super(LafType.MAC);
}
@Override
protected LookAndFeelInstaller getLookAndFeelInstaller() {
return new FlatLookAndFeelInstaller(getLookAndFeelType());
protected ThemeGrouper getThemeGrouper() {
return new MacThemeGrouper();
}
}

View file

@ -0,0 +1,38 @@
/* ###
* 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.laf;
import generic.theme.GThemeValueMap;
/**
* Adds specialized groupings unique to the Mac LookAndFeel
*/
public class MacThemeGrouper extends ThemeGrouper {
@Override
public void group(GThemeValueMap values) {
// @formatter:off
defineCustomColorGroup(values, "color.mac.disabled.fg", "Menu.disabledForeground");
defineCustomColorGroup(values, "color.mac.button.select", "Button.select");
defineCustomColorGroup(values, "color.mac.menu.select","Menu.selectionBackground");
defineCustomColorGroup(values, "color.mac.seletion.inactive.bg","List.selectionInactiveBackground");//d4d4d4
defineCustomFontGroup(values, "font.mac.smallFont", "IconButton.font");
// @formatter:on
super.group(values);
}
}

View file

@ -0,0 +1,25 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.theme.laf;
import generic.theme.LafType;
public class MetalLookAndFeelManager extends LookAndFeelManager {
public MetalLookAndFeelManager() {
super(LafType.METAL);
}
}

View file

@ -1,41 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.theme.laf;
import generic.theme.LafType;
public class MotifLookAndFeelInstaller extends LookAndFeelInstaller {
public MotifLookAndFeelInstaller() {
super(LafType.MOTIF);
}
@Override
protected void 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.
//
// these prefixes are for text components
String[] UIPrefixValues =
{ "TextField", "FormattedTextField", "TextArea", "TextPane", "EditorPane" };
setKeyBinding("COPY", "ctrl C", UIPrefixValues);
setKeyBinding("PASTE", "ctrl V", UIPrefixValues);
setKeyBinding("CUT", "ctrl X", UIPrefixValues);
}
}

View file

@ -15,17 +15,42 @@
*/
package generic.theme.laf;
import generic.theme.ColorValue;
import generic.theme.LafType;
/**
* Motif {@link LookAndFeelManager}. Specialized so that it can return the Motif installer
*/
public class MotifLookAndFeelManager extends LookAndFeelManager {
public MotifLookAndFeelManager() {
super(LafType.MOTIF);
// establish system color to LookAndFeel colors
systemToLafMap.addColor(new ColorValue(SYSTEM_APP_BACKGROUND_COLOR_ID, "control"));
systemToLafMap.addColor(new ColorValue(SYSTEM_WIDGET_BACKGROUND_COLOR_ID, "window"));
systemToLafMap.addColor(new ColorValue(SYSTEM_TOOLTIP_BACKGROUND_COLOR_ID, "info"));
systemToLafMap.addColor(new ColorValue(SYSTEM_BORDER_COLOR_ID, "activeCaptionBorder"));
}
@Override
protected LookAndFeelInstaller getLookAndFeelInstaller() {
return new MotifLookAndFeelInstaller();
protected void 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.
//
// these prefixes are for text components
String[] UIPrefixValues =
{ "TextField", "FormattedTextField", "TextArea", "TextPane", "EditorPane" };
setKeyBinding("COPY", "ctrl C", UIPrefixValues);
setKeyBinding("PASTE", "ctrl V", UIPrefixValues);
setKeyBinding("CUT", "ctrl X", UIPrefixValues);
}
@Override
protected ThemeGrouper getThemeGrouper() {
return new MotifThemeGrouper();
}
}

View file

@ -0,0 +1,35 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.theme.laf;
import generic.theme.GThemeValueMap;
/**
* Adds specialized groupings unique to the Motif LookAndFeel
*/
public class MotifThemeGrouper extends ThemeGrouper {
public MotifThemeGrouper() {
}
@Override
public void group(GThemeValueMap values) {
defineCustomFontGroup(values, "font.monospaced", "Spinner.font");
super.group(values);
}
}

View file

@ -1,54 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.theme.laf;
import java.awt.Dimension;
import javax.swing.*;
import generic.theme.*;
public class NimbusLookAndFeelInstaller extends LookAndFeelInstaller {
public NimbusLookAndFeelInstaller() {
super(LafType.NIMBUS);
}
@Override
protected void installLookAndFeel() throws UnsupportedLookAndFeelException {
UIManager.setLookAndFeel(new GNimbusLookAndFeel());
}
@Override
protected void installJavaDefaults() {
// even though java defaults have been installed, we need to fix them up now
// that Nimbus has finished initializing
GColor.refreshAll();
Gui.setJavaDefaults(Gui.getJavaDefaults());
}
@Override
protected void fixupLookAndFeelIssues() {
super.fixupLookAndFeelIssues();
// 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));
// (see NimbusDefaults for key values that can be changed here)
}
}

View file

@ -15,7 +15,7 @@
*/
package generic.theme.laf;
import java.awt.Font;
import java.awt.Dimension;
import java.util.Set;
import javax.swing.*;
@ -23,15 +23,18 @@ import javax.swing.*;
import generic.theme.*;
import ghidra.util.exception.AssertException;
/**
* Nimbus {@link LookAndFeelManager}. Specialized so that it can return the Nimbus installer and
* do specialized updating when icons or fonts change. Basically, needs to re-install a new
* instance of the Nimbus LookAndFeel each time a font or icon changes
*/
public class NimbusLookAndFeelManager extends LookAndFeelManager {
public NimbusLookAndFeelManager() {
super(LafType.NIMBUS);
}
@Override
protected LookAndFeelInstaller getLookAndFeelInstaller() {
return new NimbusLookAndFeelInstaller();
// establish system color specific to Nimbus
systemToLafMap.addColor(new ColorValue(SYSTEM_BORDER_COLOR_ID, "nimbusBorder"));
}
@Override
@ -42,7 +45,7 @@ public class NimbusLookAndFeelManager extends LookAndFeelManager {
}
@Override
public void fontsChanged(Set<String> affectedJavaIds, Font newFont) {
public void fontsChanged(Set<String> affectedJavaIds) {
if (!affectedJavaIds.isEmpty()) {
reinstallNimubus();
updateComponentFonts(affectedJavaIds);
@ -62,6 +65,7 @@ public class NimbusLookAndFeelManager extends LookAndFeelManager {
private void reinstallNimubus() {
try {
UIManager.setLookAndFeel(new GNimbusLookAndFeel() {
@Override
protected GThemeValueMap extractJavaDefaults(UIDefaults defaults) {
return Gui.getJavaDefaults();
}
@ -73,4 +77,32 @@ public class NimbusLookAndFeelManager extends LookAndFeelManager {
updateComponentUis();
}
@Override
protected void doInstallLookAndFeel() throws UnsupportedLookAndFeelException {
UIManager.setLookAndFeel(new GNimbusLookAndFeel());
}
@Override
protected GThemeValueMap extractJavaDefaults() {
// The GNimbusLookAndFeel already extracted the java defaults and installed them in the Gui
return Gui.getJavaDefaults();
}
@Override
protected ThemeGrouper getThemeGrouper() {
return new NimbusThemeGrouper();
}
@Override
protected void fixupLookAndFeelIssues() {
super.fixupLookAndFeelIssues();
// 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));
// (see NimbusDefaults for key values that can be changed here)
}
}

View file

@ -0,0 +1,43 @@
/* ###
* 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.laf;
import generic.theme.GThemeValueMap;
/**
* Adds specialized groupings unique to the Nimbus LookAndFeel
*/
public class NimbusThemeGrouper extends ThemeGrouper {
public NimbusThemeGrouper() {
// Nimbus defines a new type of button
buttonGroup.addComponents("ArrowButton");
// Nimbus defines some other color sources
colorSourceProperties.add("nimbusFocus");
colorSourceProperties.add("nimbusOrange");
colorSourceProperties.add("nimbusBorder");
}
@Override
public void group(GThemeValueMap values) {
defineCustomColorGroup(values, "color.nimbus.text.alt", "Menu.foreground");
defineCustomFontGroup(values, "font.titledborder", "TitledBorder.font");
super.group(values);
}
}

View file

@ -0,0 +1,433 @@
/* ###
* 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.laf;
import java.awt.Color;
import java.awt.Font;
import java.util.*;
import javax.swing.LookAndFeel;
import javax.swing.plaf.basic.BasicLookAndFeel;
import generic.theme.*;
/**
* Organizes UIDefaults color and font properties into groups so that every property doesn't
* have its own direct value. The idea is that users can affect many properties that have the
* same value by just changing the value for the group. For colors, the {@link LookAndFeel}s
* organize the properties internally and this class attempts to restore that organization
* as much as possible by using the values defined in the {@link BasicLookAndFeel} such as
* "control", "window", "controlShadlow", etc. The fonts don't appear to have any such internal
* organization, so we created our own groups and used an exemplar property to initialize each
* group source value. Then whenever the font matched a group source value, the color is replace
* with an indirect reference to the group source property value.
* <p>
* This class is sometimes sub-classed for a particular {@link LookAndFeel}. The subclass can
* create new groups and mappings that are unique to that LookAndFeel.
*/
public class ThemeGrouper {
private static String DEFAULT_FONT_GROUP_ID = "font.default";
private static String BUTTON_FONT_GROUP_ID = "font.button";
private static String TEXT_FONT_GROUP_ID = "font.text";
private static String WIDGET_FONT_GROUP_ID = "font.widget";
private static String COMPONENT_FONT_GROUP_ID = "font.component";
private static String MENU_FONT_GROUP_ID = "font.menu";
private static String MENU_ACCELERATOR_FONT_GROUP_ID = "font.menu.accelerator";
static List<String> DEFAULT_FONT_SOURCE_PROPERTIES = List.of(
DEFAULT_FONT_GROUP_ID,
COMPONENT_FONT_GROUP_ID,
WIDGET_FONT_GROUP_ID,
TEXT_FONT_GROUP_ID,
BUTTON_FONT_GROUP_ID,
MENU_FONT_GROUP_ID,
MENU_ACCELERATOR_FONT_GROUP_ID);
// The list of color properties (defined in BasicLookAndFeel) that are used to populate
// other component specific colors.
// The order is important. If any have the same color value, the one higher in the list is used.
// Individual groups (buttons, menus, etc.) may define a different order that is more specific
// to that group
public static List<String> DEFAULT_COLOR_SOURCE_PROPERTIES = List.of(
"control",
"window",
"activeCaption",
"activeCaptionBorder",
"activeCaptionText",
"controlDkShadow",
"controlHighlight",
"controlLtHighlight",
"controlShadow",
"controlText",
"desktp",
"inactiveCaption",
"inactiveCaptionBorder",
"inactiveCaptionText",
"info",
"infoText",
"menu",
"menuText",
"scrollbar",
"scrollBarTrack",
"text",
"textHighlight",
"textHighlightText",
"textInactiveText",
"textText",
"windowBorder",
"windowText");
private static final String[] BUTTON_GROUP = {
"Button",
"ToggleButton",
"RadioButton",
"CheckBox"
};
private static final String[] MENU_GROUP = {
"Menu",
"MenuBar",
"MenuItem",
"PopupMenu",
"RadioButtonMenuItem",
"CheckBoxMenuItem"
};
private static final String[] TEXT_GROUP = {
"TextField",
"FormattedTextField",
"PasswordField",
"TextArea",
"TextPane",
"EditorPane"
};
private static final String[] WIDGET_GROUP = {
"FileChooser",
"ColorChooser",
"ComboBox",
"List",
"Table",
"Tree"
};
private static final String[] COMPONENT_GROUP = {
"Desktop",
"Panel",
"InternalFrame",
"Label",
"OptionPane",
"ProgressBar",
"Separator",
"ScrollBar",
"ScrollPane",
"Viewport",
"Slider",
"Spinner",
"SplitPane",
"TabbedPane",
"TableHeader",
"TitledBorder",
"ToolBar",
"ToolTip"
};
// often the many of the various group source ids have the same color value. To try and group
// properties as defined in BasicLookAndFeel, the preferred source ids are
// defined for each group. These will be tried first, but if a match isn't found among the
// preferred sources, then all the sources will be searched for a match
private static final String[] BUTTON_PREFERRED_SOURCES = {
"control",
"controlText",
"controlShadow",
"controlDkShadow",
"controlHighlight",
"controlLtHighlight"
};
private static final String[] MENU_PREFERRED_SOURCES = {
"menu",
"menuText",
"textHighlightText",
"textHighlight",
"controlShadow",
"controlDkShadow",
"controlHighlight",
"controlLtHighlight"
};
private static final String[] TEXT_PREFERRED_SOURCES = {
"window",
"text",
"textText",
"textInactiveText",
"textHighlight",
"textHighlightText",
"controlShadow",
"controlDkShadow",
"controlHighlight",
"controlLtHighlight"
};
private static final String[] WIDGET_PREFERRED_SOURCES = {
"window",
"textText",
"textHighlight",
"textHighlightText",
"control",
"controlShadow",
"controlDkShadow",
"controlHighlight",
"controlLtHighlight"
};
private static final String[] COMPONENT_PREFERRED_SOURCES = {
"control",
"controlText",
"controlShadow",
"controlDkShadow",
"controlHighlight",
"controlLtHighlight",
"textText",
"textHighlight"
};
protected List<String> colorSourceProperties;
protected List<String> fontSourceProperties;
protected Set<PropertyGroup> groups;
protected PropertyGroup buttonGroup = new PropertyGroup(BUTTON_GROUP, BUTTON_PREFERRED_SOURCES);
protected PropertyGroup menuGroup = new PropertyGroup(MENU_GROUP, MENU_PREFERRED_SOURCES);
protected PropertyGroup widgetGroup = new PropertyGroup(WIDGET_GROUP, WIDGET_PREFERRED_SOURCES);
protected PropertyGroup textGroup = new PropertyGroup(TEXT_GROUP, TEXT_PREFERRED_SOURCES);
protected PropertyGroup componentGroup =
new PropertyGroup(COMPONENT_GROUP, COMPONENT_PREFERRED_SOURCES);
public ThemeGrouper() {
colorSourceProperties = new ArrayList<>(DEFAULT_COLOR_SOURCE_PROPERTIES);
fontSourceProperties = new ArrayList<>(DEFAULT_FONT_SOURCE_PROPERTIES);
groups = getPropertyGroups();
}
/**
* Replaces direct property values in the given GThemeValueMap with indirect references
* using the values from match source ids.
* @param values the values to search and replace source matches
*/
public void group(GThemeValueMap values) {
initialize(values);
Map<String, PropertyGroup> groupMap = buildGroupMap(values);
groupColors(values, groupMap);
groupFonts(values, groupMap);
}
protected void defineCustomColorGroup(GThemeValueMap values, String customGroupName,
String exemplarComponentId) {
colorSourceProperties.add(customGroupName);
ColorValue colorValue = values.getColor(exemplarComponentId);
if (colorValue != null) {
Color color = colorValue.get(values);
values.addColor(new ColorValue(customGroupName, color));
}
}
protected void defineCustomFontGroup(GThemeValueMap values, String customGroupName,
String exemplarComponentId) {
fontSourceProperties.add(customGroupName);
FontValue fontValue = values.getFont(exemplarComponentId);
if (fontValue != null) {
Font font = fontValue.get(values);
values.addFont(new FontValue(customGroupName, font));
}
}
private void groupColors(GThemeValueMap values, Map<String, PropertyGroup> groupMap) {
Set<String> skip = new HashSet<>(colorSourceProperties); // we don't want to map sources
Map<Integer, String> defaultColorMapping = buildColorToSourceMap(values);
// try to map each color property to a source property (e.g., Button.background -> control)
for (ColorValue colorValue : values.getColors()) {
String id = colorValue.getId();
if (colorValue.isIndirect() || skip.contains(id)) {
continue;
}
PropertyGroup group = groupMap.get(getComponentName(id));
int rgb = colorValue.getRawValue().getRGB();
String sourceProperty = group == null ? null : group.getSourceProperty(rgb);
if (sourceProperty == null) {
sourceProperty = defaultColorMapping.get(rgb);
}
if (sourceProperty != null) {
values.addColor(new ColorValue(id, sourceProperty));
}
}
}
private void groupFonts(GThemeValueMap values, Map<String, PropertyGroup> groupMap) {
Set<String> skip = new HashSet<>(fontSourceProperties); // we don't want to map sources
Map<Font, String> defaultFontMapping = buildFontToSourceMap(values);
// try to map each color property to a source property (e.g., Button.background -> control)
for (FontValue fontValue : values.getFonts()) {
String id = fontValue.getId();
if (fontValue.isIndirect() || skip.contains(id)) {
continue;
}
Font font = fontValue.getRawValue();
PropertyGroup group = groupMap.get(getComponentName(id));
String sourceProperty = group == null ? null : group.getSourceProperty(font);
if (sourceProperty == null) {
sourceProperty = defaultFontMapping.get(font);
}
if (sourceProperty != null) {
values.addFont(new FontValue(id, sourceProperty));
}
}
}
private void initialize(GThemeValueMap values) {
// initialized default font to the Panel's font
FontValue defaultFontValue = values.getFont("Panel.font");
if (defaultFontValue != null) {
values.addFont(new FontValue(DEFAULT_FONT_GROUP_ID, defaultFontValue.get(values)));
}
// initialize the default group fonts to a font from an exemplar property in that group
initializeFontGroup(buttonGroup, BUTTON_FONT_GROUP_ID, "Button.font", values);
initializeFontGroup(textGroup, TEXT_FONT_GROUP_ID, "TextField.font", values);
initializeFontGroup(widgetGroup, WIDGET_FONT_GROUP_ID, "Table.font", values);
initializeFontGroup(componentGroup, COMPONENT_FONT_GROUP_ID, "Panel.font", values);
initializeFontGroup(menuGroup, MENU_FONT_GROUP_ID, "Menu.font", values);
initializeFontGroup(menuGroup, MENU_ACCELERATOR_FONT_GROUP_ID, "Menu.acceleratorFont",
values);
}
private void initializeFontGroup(PropertyGroup group, String fontGroupId, String exemplarId,
GThemeValueMap values) {
FontValue fontValue = values.getFont(exemplarId);
if (fontValue != null) {
Font font = fontValue.getRawValue();
values.addFont(new FontValue(fontGroupId, font));
group.addFontMapping(font, fontGroupId);
}
}
private Set<PropertyGroup> getPropertyGroups() {
Set<PropertyGroup> set = new HashSet<>();
set.add(buttonGroup);
set.add(menuGroup);
set.add(textGroup);
set.add(widgetGroup);
set.add(componentGroup);
return set;
}
private Map<String, PropertyGroup> buildGroupMap(GThemeValueMap values) {
Map<String, PropertyGroup> map = new HashMap<>();
for (PropertyGroup group : groups) {
group.initialize(values);
group.populateGroupMap(map);
}
return map;
}
private String getComponentName(String id) {
int dotIndex = id.indexOf(".");
if (dotIndex < 0) {
return id;
}
return id.substring(0, dotIndex);
}
private Map<Integer, String> buildColorToSourceMap(GThemeValueMap values) {
Map<Integer, String> colorMapping = new HashMap<>();
ArrayList<String> reversed = new ArrayList<>(colorSourceProperties);
Collections.reverse(reversed);
// go through in reverse order so that values at the top of the list have precedence
// if multiple propertyBases have the save value.
for (String propertyBase : reversed) {
ColorValue colorValue = values.getColor(propertyBase);
if (colorValue != null) {
Color color = colorValue.get(values);
colorMapping.put(color.getRGB(), propertyBase);
}
}
return colorMapping;
}
private Map<Font, String> buildFontToSourceMap(GThemeValueMap values) {
Map<Font, String> fontMapping = new HashMap<>();
ArrayList<String> reversed = new ArrayList<>(fontSourceProperties);
Collections.reverse(reversed);
// go through in reverse order so that values at the top of the list have precedence
// if multiple propertyBases have the save value.
for (String propertyBase : reversed) {
FontValue fontValue = values.getFont(propertyBase);
if (fontValue != null) {
Font font = fontValue.get(values);
fontMapping.put(font, propertyBase);
}
}
return fontMapping;
}
static class PropertyGroup {
private Set<String> groupComponents = new HashSet<>();
private List<String> preferredPropertyColorSources = new ArrayList<>();
private Map<Integer, String> colorMapping;
private Map<Font, String> fontMapping = new HashMap<>();
PropertyGroup(String[] components, String[] perferredSources) {
addComponents(components);
addPreferredColorSources(perferredSources);
}
String getSourceProperty(int rgb) {
return colorMapping.get(rgb);
}
String getSourceProperty(Font font) {
return fontMapping.get(font);
}
void populateGroupMap(Map<String, PropertyGroup> groupMap) {
for (String component : groupComponents) {
groupMap.put(component, this);
}
}
void addPreferredColorSources(String... preferedColorSources) {
this.preferredPropertyColorSources.addAll(Arrays.asList(preferedColorSources));
}
void addComponents(String... properties) {
groupComponents.addAll(Arrays.asList(properties));
}
void addFontMapping(Font font, String sourceId) {
fontMapping.put(font, sourceId);
}
private Map<Integer, String> initialize(GThemeValueMap values) {
colorMapping = new HashMap<>();
ArrayList<String> reversed = new ArrayList<>(preferredPropertyColorSources);
Collections.reverse(reversed);
// go through in reverse order so that values at the top of the list have precedence
// if multiple propertyBases have the save value.
for (String propertyBase : reversed) {
ColorValue colorValue = values.getColor(propertyBase);
if (colorValue != null) {
Color color = colorValue.get(values);
colorMapping.put(color.getRGB(), propertyBase);
}
}
return colorMapping;
}
}
}

View file

@ -0,0 +1,25 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.theme.laf;
import generic.theme.LafType;
public class WindowsClassicLookAndFeelManager extends LookAndFeelManager {
public WindowsClassicLookAndFeelManager() {
super(LafType.WINDOWS_CLASSIC);
}
}

View file

@ -17,19 +17,10 @@ package generic.theme.laf;
import generic.theme.LafType;
/**
* Generic {@link LookAndFeelManager} for lookAndFeels that do not require any special handling
* to install or update
*/
public class GenericLookAndFeelManager extends LookAndFeelManager {
public class WindowsLookAndFeelManager extends LookAndFeelManager {
public GenericLookAndFeelManager(LafType laf) {
super(laf);
}
@Override
protected LookAndFeelInstaller getLookAndFeelInstaller() {
return new LookAndFeelInstaller(getLookAndFeelType());
public WindowsLookAndFeelManager() {
super(LafType.WINDOWS);
}
}

View file

@ -96,4 +96,25 @@ public class UrlImageIcon extends LazyImageIcon {
}
return null;
}
@Override
public int hashCode() {
return imageUrl.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
UrlImageIcon other = (UrlImageIcon) obj;
return Objects.equals(imageUrl, other.imageUrl);
}
}

View file

@ -22,6 +22,8 @@ import java.awt.Color;
import org.junit.Before;
import org.junit.Test;
import ghidra.util.WebColors;
public class ColorValueTest {
private GThemeValueMap values;
@ -85,17 +87,33 @@ public class ColorValueTest {
}
@Test
public void testToExernalId() {
public void testGetSerializationString() {
ColorValue value = new ColorValue("color.test", Color.BLUE);
assertEquals("color.test", value.toExternalId("color.test"));
assertEquals("[color]foo.bar", value.toExternalId("foo.bar"));
assertEquals("color.test = #0000ff // Blue", value.getSerializationString());
value = new ColorValue("foo.bar", Color.BLUE);
assertEquals("[color]foo.bar = #0000ff // Blue", value.getSerializationString());
value = new ColorValue("color.test", "xyz.abc");
assertEquals("color.test = [color]xyz.abc", value.getSerializationString());
}
@Test
public void testFromExternalId() {
ColorValue value = new ColorValue("color.test", Color.BLUE);
assertEquals("color.test", value.fromExternalId("color.test"));
assertEquals("foo.bar", value.fromExternalId("[color]foo.bar"));
public void testParse() {
ColorValue value = ColorValue.parse("color.test", "#0000ff");
assertEquals("color.test", value.getId());
assertEquals(WebColors.BLUE, value.getRawValue());
assertEquals(null, value.getReferenceId());
value = ColorValue.parse("[color]foo.bar", "#0000ff");
assertEquals("foo.bar", value.getId());
assertEquals(WebColors.BLUE, value.getRawValue());
assertEquals(null, value.getReferenceId());
value = ColorValue.parse("color.test", "[color]xyz.abc");
assertEquals("color.test", value.getId());
assertEquals(null, value.getRawValue());
assertEquals("xyz.abc", value.getReferenceId());
}
@Test

View file

@ -0,0 +1,191 @@
/* ###
* 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 static org.junit.Assert.*;
import java.awt.Font;
import org.junit.Test;
public class FontModifierTest {
private Font baseFont = new Font("Dialog", Font.PLAIN, 12);
@Test
public void testNoModifiers() {
assertNull(FontModifier.parse(""));
}
@Test
public void testSizeModifier() {
FontModifier modifier = FontModifier.parse("[6]");
assertNotNull(modifier);
Font newFont = modifier.modify(baseFont);
assertEquals(6, newFont.getSize());
assertEquals(baseFont.getName(), newFont.getName());
assertEquals(baseFont.getStyle(), newFont.getStyle());
}
@Test
public void testStyleModifierPlain() {
FontModifier modifier = FontModifier.parse("[plain]");
assertNotNull(modifier);
Font newFont = modifier.modify(baseFont);
assertEquals(Font.PLAIN, newFont.getStyle());
assertEquals(baseFont.getName(), newFont.getName());
assertEquals(baseFont.getSize(), newFont.getSize());
}
@Test
public void testStyleModifierBold() {
FontModifier modifier = FontModifier.parse("[bold]");
assertNotNull(modifier);
Font newFont = modifier.modify(baseFont);
assertEquals(Font.BOLD, newFont.getStyle());
assertEquals(baseFont.getName(), newFont.getName());
assertEquals(baseFont.getSize(), newFont.getSize());
}
@Test
public void testStyleModifierItalic() {
FontModifier modifier = FontModifier.parse("[ITALIC]");
assertNotNull(modifier);
Font newFont = modifier.modify(baseFont);
assertEquals(Font.ITALIC, newFont.getStyle());
assertEquals(baseFont.getName(), newFont.getName());
assertEquals(baseFont.getSize(), newFont.getSize());
}
@Test
public void testStyleModifierBoldItalic() {
FontModifier modifier = FontModifier.parse("[BOLDitalic]");
assertNotNull(modifier);
Font newFont = modifier.modify(baseFont);
assertEquals(Font.ITALIC | Font.BOLD, newFont.getStyle());
assertEquals(baseFont.getName(), newFont.getName());
assertEquals(baseFont.getSize(), newFont.getSize());
}
@Test
public void testStyleModifierBoldItalic2() {
FontModifier modifier = FontModifier.parse("[BOLD][italic]");
assertNotNull(modifier);
Font newFont = modifier.modify(baseFont);
assertEquals(Font.ITALIC | Font.BOLD, newFont.getStyle());
assertEquals(baseFont.getName(), newFont.getName());
assertEquals(baseFont.getSize(), newFont.getSize());
}
@Test
public void testFamilyModification() {
FontModifier modifier = FontModifier.parse("[monospaced]");
assertNotNull(modifier);
Font newFont = modifier.modify(baseFont);
assertEquals("Monospaced", newFont.getFamily());
assertEquals(baseFont.getStyle(), newFont.getStyle());
assertEquals(baseFont.getSize(), newFont.getSize());
}
@Test
public void testSizeAndStyleModification() {
FontModifier modifier = FontModifier.parse("[16][bold]");
assertNotNull(modifier);
Font newFont = modifier.modify(baseFont);
assertEquals(baseFont.getName(), newFont.getFamily());
assertEquals(Font.BOLD, newFont.getStyle());
assertEquals(16, newFont.getSize());
}
@Test
public void testFamilyModificationMultiple() {
try {
FontModifier.parse("[monospaced][courier]");
fail("Expecected Exception");
}
catch (IllegalStateException e) {
// expected
}
}
@Test
public void testStyleModifierIncompatableStyles() {
try {
FontModifier.parse("[plain][italic]");
fail("Expected IllegalStateException");
}
catch (IllegalStateException e) {
// expected
}
}
@Test
public void testInvalidModifierString() {
try {
FontModifier.parse("asdfasf");
fail("Expected IllegalArgumentExcption");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testInvalidModifierString2() {
try {
FontModifier.parse("[12]aa[13]");
fail("Expected IllegalArgumentExcption");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testInvalidModifierString3() {
try {
FontModifier.parse("[12]aa13]");
fail("Expected IllegalArgumentExcption");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testInvalidModifierString4() {
try {
FontModifier.parse("[12][plain]sz");
fail("Expected IllegalArgumentExcption");
}
catch (IllegalArgumentException e) {
// expected
}
}
@Test
public void testGetSerializationString() {
//@formatter:off
assertEquals("[12]", new FontModifier(null, null, 12).getSerializationString());
assertEquals("[plain]", new FontModifier(null, Font.PLAIN, null).getSerializationString());
assertEquals("[bold]", new FontModifier(null, Font.BOLD, null).getSerializationString());
assertEquals("[italic]", new FontModifier(null, Font.ITALIC, null).getSerializationString());
assertEquals("[bold][italic]", new FontModifier(null, Font.BOLD | Font.ITALIC, null).getSerializationString());
assertEquals("[Monospaced]",new FontModifier("Monospaced", null, null).getSerializationString());
assertEquals("[Monospaced][12][plain]",new FontModifier("Monospaced", Font.PLAIN, 12).getSerializationString());
//@formatter:on
}
}

View file

@ -22,11 +22,8 @@ import java.awt.Font;
import org.junit.Before;
import org.junit.Test;
import generic.theme.FontValue;
import generic.theme.GThemeValueMap;
public class FontValueTest {
private static Font FONT = new Font("Dialog", 12, Font.PLAIN);
private static Font FONT = new Font("Dialog", Font.PLAIN, 12);
private GThemeValueMap values;
@Before
@ -88,17 +85,33 @@ public class FontValueTest {
}
@Test
public void testToExernalId() {
public void testGetSerializationString() {
FontValue value = new FontValue("font.test", FONT);
assertEquals("font.test", value.toExternalId("font.test"));
assertEquals("[font]foo.bar", value.toExternalId("foo.bar"));
assertEquals("font.test = Dialog-PLAIN-12", value.getSerializationString());
value = new FontValue("foo.bar", FONT);
assertEquals("[font]foo.bar = Dialog-PLAIN-12", value.getSerializationString());
value = new FontValue("font.test", "xyz.abc");
assertEquals("font.test = [font]xyz.abc", value.getSerializationString());
}
@Test
public void testFromExternalId() {
FontValue value = new FontValue("font.test", FONT);
assertEquals("font.test", value.fromExternalId("font.test"));
assertEquals("foo.bar", value.fromExternalId("[font]foo.bar"));
public void testParse() {
FontValue value = FontValue.parse("font.test", "Dialog-PLAIN-12");
assertEquals("font.test", value.getId());
assertEquals(FONT, value.getRawValue());
assertEquals(null, value.getReferenceId());
value = FontValue.parse("[font]foo.bar", "Dialog-PLAIN-12");
assertEquals("foo.bar", value.getId());
assertEquals(FONT, value.getRawValue());
assertEquals(null, value.getReferenceId());
value = FontValue.parse("font.test", "[font]xyz.abc");
assertEquals("font.test", value.getId());
assertEquals(null, value.getRawValue());
assertEquals("xyz.abc", value.getReferenceId());
}
@Test

View file

@ -15,90 +15,345 @@
*/
package generic.theme;
import static ghidra.util.WebColors.*;
import static org.junit.Assert.*;
import java.awt.Color;
import java.awt.Font;
import java.net.URL;
import java.util.*;
import javax.swing.Icon;
import javax.swing.plaf.UIResource;
import org.junit.Before;
import org.junit.Test;
import generic.theme.builtin.*;
import resources.ResourceManager;
import resources.icons.UrlImageIcon;
public class GuiTest {
private GThemeValueMap darkValues = new GThemeValueMap();
private Font FONT = new Font("Dialog", Font.PLAIN, 13);
private Font SMALL_FONT = new Font("Dialog", Font.PLAIN, 4);
private Icon ICON1 = ResourceManager.loadImage("images/exec.png");
private Icon ICON2 = ResourceManager.loadImage("images/flag.png");
private GThemeValueMap defaultValues = new GThemeValueMap();
private GThemeValueMap darkDefaultValues = new GThemeValueMap();
private Set<GTheme> themes;
private GTheme METAL_THEME = new MetalTheme();
private GTheme NIMBUS_THEME = new NimbusTheme();
private GTheme WINDOWS_THEME = new WindowsTheme();
private GTheme MAC_THEME = new MacTheme();
@Before
public void setUp() {
Gui.setPropertiesLoader(new ThemePropertiesLoader() {
themes = new HashSet<>();
themes.add(METAL_THEME);
themes.add(NIMBUS_THEME);
themes.add(WINDOWS_THEME);
themes.add(MAC_THEME);
defaultValues.addColor(new ColorValue("color.test.bg", WHITE));
defaultValues.addColor(new ColorValue("color.test.fg", RED));
defaultValues.addFont(new FontValue("font.test.foo", FONT));
defaultValues.addIcon(new IconValue("icon.test.foo", ICON1));
darkDefaultValues.addColor(new ColorValue("color.test.bg", BLACK));
darkDefaultValues.addColor(new ColorValue("color.test.fg", BLUE));
Gui.setThemePreferenceManager(new ThemePreferenceManager() {
public GTheme getTheme() {
return new MetalTheme();
}
@Override
public void load() {
public void saveThemeToPreferences(GTheme theme) {
// do nothing
}
});
Gui.setPropertiesLoader(new ThemeFileLoader() {
@Override
public void loadThemeDefaultFiles() {
// do nothing
}
@Override
public Collection<GTheme> loadThemeFiles() {
return new HashSet<>(themes);
}
@Override
public GThemeValueMap getDefaults() {
return defaultValues;
}
@Override
public GThemeValueMap getDarkDefaults() {
return darkValues;
return darkDefaultValues;
}
});
Gui.initialize();
}
// @Test
// public void testRegisteredColorBeforeAndAfterGuiInit() {
// Gui.registerColor("test.before", Color.RED);
// Gui.initialize();
// Gui.registerColor("test.after", Color.BLUE);
//
// assertEquals(Color.RED, Gui.getColor("test.before"));
// assertEquals(Color.BLUE, Gui.getColor("test.after"));
// }
@Test
public void testDarkThemeColorOverride() {
GColor gColor = new GColor("color.test.bg");
// @Test
// public void testThemeColorOverride() {
// Gui.initialize();
// String id = "color.test.bg";
// Gui.registerColor(id, Color.RED);
// assertEquals(Color.RED, Gui.getColor(id));
//
// GTheme theme = new GTheme("Test");
// theme.setColor(id, Color.BLUE);
// Gui.setTheme(theme);
//
// assertEquals(Color.BLUE, Gui.getColor(id));
//
// Gui.setTheme(new GTheme("Test2"));
// assertEquals(Color.RED, Gui.getColor(id));
//
// }
assertColor(WHITE, gColor);
Gui.setTheme(new GTheme("Test", LafType.FLAT_DARK, true));
assertEquals(BLACK, gColor);
// @Test
// public void testDarkOverride() {
// String id = "color.test.bg";
// // simulate registered dark color from theme property file
// darkValues.addColor(new ColorValue(id, Color.BLACK));
//
// Gui.registerColor(id, Color.RED);
// Gui.initialize();
//
// assertEquals(Color.RED, Gui.getColor(id));
//
// GTheme theme = new GTheme("Dark Test", "System", true);
// Gui.setTheme(theme);
//
// assertEquals(Color.BLACK, Gui.getColor(id));
// }
Gui.setTheme(new GTheme("Test2"));
assertEquals(WHITE, gColor);
// @Test
// public void testAliasOverride() {
// String id = "color.test.bg";
// //simulate alias defined
// List<String> aliases = Arrays.asList("Menu.background");
// aliasMap.put(id, aliases);
//
// Gui.registerColor(id, Color.RED);
// Gui.initialize();
// Color menuColor = UIManager.getColor("Menu.background");
// assertNotEquals(menuColor, Color.RED);
// assertEquals(menuColor, Gui.getColor(id));
// }
}
// private void assertEqual(Color a, GColor b) {
// if (a.getRGB() != b.getRGB()) {
// fail("Expected: " + a.toString() + " but was: " + b.toString());
// }
// }
@Test
public void testThemeColorOverride() {
GColor gColor = new GColor("color.test.bg");
GTheme theme = new GTheme("Test");
theme.setColor("color.test.bg", GREEN);
assertColor(WHITE, gColor);
Gui.setTheme(theme);
assertEquals(GREEN, gColor);
Gui.setTheme(new GTheme("Test2"));
assertEquals(WHITE, gColor);
}
@Test
public void testThemeFontOverride() {
assertEquals(FONT, Gui.getFont("font.test.foo"));
GTheme theme = new GTheme("Test");
theme.setFont("font.test.foo", SMALL_FONT);
Gui.setTheme(theme);
assertEquals(SMALL_FONT, Gui.getFont("font.test.foo"));
Gui.setTheme(new GTheme("Test2"));
assertEquals(FONT, Gui.getFont("font.test.foo"));
}
@Test
public void testThemeIconOverride() {
GIcon gIcon = new GIcon("icon.test.foo");
GTheme theme = new GTheme("Test");
theme.setIcon("icon.test.foo", ICON2);
assertIcon(ICON1, gIcon);
Gui.setTheme(theme);
assertIcon(ICON2, gIcon);
Gui.setTheme(new GTheme("Test2"));
assertIcon(ICON1, gIcon);
}
@Test
public void testReloadGhidraDefaults() {
GColor gColor = new GColor("color.test.bg");
assertColor(WHITE, gColor);
defaultValues.addColor(new ColorValue("color.test.bg", YELLOW));
Gui.reloadApplicationDefaults();
assertEquals(YELLOW, gColor);
}
@Test
public void testRestoreThemeValues() {
GColor gColor = new GColor("color.test.bg");
assertColor(WHITE, gColor);
Gui.setColor("color.test.bg", PURPLE);
assertColor(PURPLE, gColor);
Gui.restoreThemeValues();
assertEquals(WHITE, gColor);
}
@Test
public void testGetAllThemes() {
assertEquals(themes, Gui.getAllThemes());
}
@Test
public void testAddTheme() {
GTheme newTheme = new GTheme("Test");
Set<GTheme> allThemes = Gui.getAllThemes();
assertEquals(themes.size(), allThemes.size());
assertFalse(allThemes.contains(newTheme));
Gui.addTheme(newTheme);
allThemes = Gui.getAllThemes();
assertTrue(allThemes.contains(newTheme));
}
@Test
public void testDeleteTheme() {
GTheme newTheme = new GTheme("Test");
Set<GTheme> allThemes = Gui.getAllThemes();
assertFalse(allThemes.contains(newTheme));
Gui.addTheme(newTheme);
allThemes = Gui.getAllThemes();
assertTrue(allThemes.contains(newTheme));
Gui.deleteTheme(newTheme);
allThemes = Gui.getAllThemes();
assertFalse(allThemes.contains(newTheme));
}
@Test
public void testGetSupportedThemes() {
Set<GTheme> supportedThemes = Gui.getSupportedThemes();
// since we put mac specific and windows specific themes, they can't all be here
// regardless of the current platform
assertTrue(supportedThemes.size() < themes.size());
for (GTheme gTheme : supportedThemes) {
assertTrue(gTheme.hasSupportedLookAndFeel());
}
}
@Test
public void testGetLookAndFeelType() {
LafType lookAndFeelType = Gui.getLookAndFeelType();
// in the test setup, we defaulted to the MetalLookAndFeel
assertEquals(LafType.METAL, lookAndFeelType);
}
@Test
public void testGetActiveTheme() {
GTheme activeTheme = Gui.getActiveTheme();
assertEquals(METAL_THEME, activeTheme);
}
@Test
public void testGetThemeByName() {
GTheme theme = Gui.getTheme("Nimbus Theme");
assertEquals(NIMBUS_THEME, theme);
}
@Test
public void testGetAllValues() {
GThemeValueMap allValues = Gui.getAllValues();
assertEquals(WHITE, allValues.getColor("color.test.bg").getRawValue());
Gui.setColor("color.test.bg", PURPLE);
allValues = Gui.getAllValues();
assertEquals(PURPLE, allValues.getColor("color.test.bg").getRawValue());
}
@Test
public void testGetNonDefaultValues() {
// should be empty if we haven't changed any themeValues
GThemeValueMap nonDefaultValues = Gui.getNonDefaultValues();
assertTrue(nonDefaultValues.isEmpty());
// change some values and see that they show up in the nonDefaultValues
Gui.setColor("color.test.bg", RED);
Gui.setFont("font.test.foo", SMALL_FONT);
Gui.setIcon("icon.test.foo", ICON2);
// also add in a totally new value
Gui.setColor("color.test.xxx", GREEN);
nonDefaultValues = Gui.getNonDefaultValues();
assertEquals(4, nonDefaultValues.size());
assertEquals(RED, nonDefaultValues.getColor("color.test.bg").getRawValue());
assertEquals(GREEN, nonDefaultValues.getColor("color.test.xxx").getRawValue());
assertEquals(SMALL_FONT, nonDefaultValues.getFont("font.test.foo").getRawValue());
assertEquals(ICON2, nonDefaultValues.getIcon("icon.test.foo").getRawValue());
}
@Test
public void testGetColor() {
assertEquals(WHITE, Gui.getColor("color.test.bg"));
}
@Test
public void testGetFont() {
assertEquals(FONT, Gui.getFont("font.test.foo"));
}
@Test
public void testGetIcon() {
assertEquals(ICON1, Gui.getIcon("icon.test.foo"));
}
@Test
public void testGetColorWithUnresolvedId() {
assertEquals(CYAN, Gui.getColor("color.badid", false));
}
@Test
public void testGetIconWithUnresolvedId() {
assertEquals(ResourceManager.getDefaultIcon(), Gui.getIcon("icon.badid", false));
}
@Test
public void testGetFontWithUnresolvedId() {
assertEquals(Gui.DEFAULT_FONT, Gui.getFont("font.badid", false));
}
@Test
public void testGetGColorUiResource() {
Color color = Gui.getGColorUiResource("color.test.bg");
assertTrue(color instanceof UIResource);
// make sure there is only one instance for an id;
Color color2 = Gui.getGColorUiResource("color.test.bg");
assertTrue(color == color2);
}
@Test
public void testGetGIconUiResource() {
Icon icon = Gui.getGIconUiResource("icon.test.foo");
assertTrue(icon instanceof UIResource);
// make sure there is only one instance for an id;
Icon gIcon2 = Gui.getGIconUiResource("icon.test.foo");
assertTrue(icon == gIcon2);
}
@Test
public void testGetApplicationLightDefaults() {
assertEquals(defaultValues, Gui.getApplicationLightDefaults());
}
@Test
public void testGetApplicationDarkDefaults() {
// dark defaults are a combination of standard defalts overlayed with dark defaults
GThemeValueMap expected = new GThemeValueMap();
expected.load(defaultValues);
expected.load(darkDefaultValues);
assertEquals(expected, Gui.getApplicationDarkDefaults());
}
private void assertColor(Color color, GColor gColor) {
if (color.getRGB() != gColor.getRGB()) {
fail("RGB values don't match! Expected " + color + " but got " + gColor);
}
}
private void assertIcon(Icon icon, GIcon gIcon) {
URL url = ((UrlImageIcon) icon).getUrl();
URL gUrl = gIcon.getUrl();
if (!url.equals(gUrl)) {
fail("Icons don't match. Expected " + url + ", but got " + gUrl);
}
}
}

View file

@ -87,17 +87,33 @@ public class IconValueTest {
}
@Test
public void testToExernalId() {
public void testGetSerializationString() {
IconValue value = new IconValue("icon.test", ICON1);
assertEquals("icon.test", value.toExternalId("icon.test"));
assertEquals("[icon]foo.bar", value.toExternalId("foo.bar"));
assertEquals("icon.test = images/core.png", value.getSerializationString());
value = new IconValue("foo.bar", ICON1);
assertEquals("[icon]foo.bar = images/core.png", value.getSerializationString());
value = new IconValue("icon.test", "xyz.abc");
assertEquals("icon.test = [icon]xyz.abc", value.getSerializationString());
}
@Test
public void testFromExternalId() {
IconValue value = new IconValue("icon.test", ICON1);
assertEquals("icon.test", value.fromExternalId("icon.test"));
assertEquals("foo.bar", value.fromExternalId("[icon]foo.bar"));
public void testParse() {
IconValue value = IconValue.parse("icon.test", "images/core.png");
assertEquals("icon.test", value.getId());
assertEquals(ICON1, value.getRawValue());
assertEquals(null, value.getReferenceId());
value = IconValue.parse("[icon]foo.bar", "images/core.png");
assertEquals("foo.bar", value.getId());
assertEquals(ICON1, value.getRawValue());
assertEquals(null, value.getReferenceId());
value = IconValue.parse("icon.test", "[icon]xyz.abc");
assertEquals("icon.test", value.getId());
assertEquals(null, value.getRawValue());
assertEquals("xyz.abc", value.getReferenceId());
}
@Test

View file

@ -24,6 +24,8 @@ import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import javax.swing.Icon;
import org.junit.Test;
import resources.ResourceManager;
@ -44,6 +46,7 @@ public class ThemePropertyFileReaderTest {
" color.b.7 = color.b.1", // ref
" font.a.8 = dialog-PLAIN-14",
" font.a.9 = font.a.8",
" font.a.b = (font.a.8[20][BOLD])",
" icon.a.10 = core.png",
" icon.a.11 = icon.a.10",
"")));
@ -51,21 +54,22 @@ public class ThemePropertyFileReaderTest {
Color halfAlphaRed = new Color(0x80ff0000, true);
GThemeValueMap values = reader.getDefaultValues();
assertEquals(11, values.size());
assertEquals(12, values.size());
assertEquals(WHITE, getColorOrRef(values, "color.b.1"));
assertEquals(RED, getColorOrRef(values, "color.b.2"));
assertEquals(GREEN, getColorOrRef(values, "color.b.3"));
assertEquals(halfAlphaRed, getColorOrRef(values, "color.b.4"));
assertEquals(BLUE, getColorOrRef(values, "color.b.5"));
assertEquals(halfAlphaRed, getColorOrRef(values, "color.b.6"));
assertEquals("color.b.1", getColorOrRef(values, "color.b.7"));
assertEquals(WHITE, getColor(values, "color.b.1"));
assertEquals(RED, getColor(values, "color.b.2"));
assertEquals(GREEN, getColor(values, "color.b.3"));
assertEquals(halfAlphaRed, getColor(values, "color.b.4"));
assertEquals(BLUE, getColor(values, "color.b.5"));
assertEquals(halfAlphaRed, getColor(values, "color.b.6"));
assertEquals(WHITE, getColor(values, "color.b.7"));
assertEquals(new Font("dialog", Font.PLAIN, 14), getFontOrRef(values, "font.a.8"));
assertEquals("font.a.8", getFontOrRef(values, "font.a.9"));
assertEquals(new Font("dialog", Font.PLAIN, 14), getFont(values, "font.a.8"));
assertEquals(new Font("dialog", Font.PLAIN, 14), getFont(values, "font.a.9"));
assertEquals(new Font("dialog", Font.BOLD, 20), getFont(values, "font.a.b"));
assertEquals(ResourceManager.loadImage("core.png"), getIconOrRef(values, "icon.a.10"));
assertEquals("icon.a.10", getIconOrRef(values, "icon.a.11"));
assertEquals(ResourceManager.loadImage("core.png"), getIcon(values, "icon.a.10"));
assertEquals(ResourceManager.loadImage("core.png"), getIcon(values, "icon.a.11"));
}
@ -88,13 +92,13 @@ public class ThemePropertyFileReaderTest {
GThemeValueMap values = reader.getDarkDefaultValues();
assertEquals(7, values.size());
assertEquals(WHITE, getColorOrRef(values, "color.b.1"));
assertEquals(RED, getColorOrRef(values, "color.b.2"));
assertEquals(GREEN, getColorOrRef(values, "color.b.3"));
assertEquals(halfAlphaRed, getColorOrRef(values, "color.b.4"));
assertEquals(BLUE, getColorOrRef(values, "color.b.5"));
assertEquals(halfAlphaRed, getColorOrRef(values, "color.b.6"));
assertEquals("color.b.1", getColorOrRef(values, "color.b.7"));
assertEquals(WHITE, getColor(values, "color.b.1"));
assertEquals(RED, getColor(values, "color.b.2"));
assertEquals(GREEN, getColor(values, "color.b.3"));
assertEquals(halfAlphaRed, getColor(values, "color.b.4"));
assertEquals(BLUE, getColor(values, "color.b.5"));
assertEquals(halfAlphaRed, getColor(values, "color.b.6"));
assertEquals(WHITE, getColor(values, "color.b.7"));
}
@Test
@ -116,10 +120,10 @@ public class ThemePropertyFileReaderTest {
GThemeValueMap darkValues = reader.getDarkDefaultValues();
assertEquals(2, values.size());
assertEquals(WHITE, getColorOrRef(values, "color.b.1"));
assertEquals(RED, getColorOrRef(values, "color.b.2"));
assertEquals(BLACK, getColorOrRef(darkValues, "color.b.1"));
assertEquals(BLUE, getColorOrRef(darkValues, "color.b.2"));
assertEquals(WHITE, getColor(values, "color.b.1"));
assertEquals(RED, getColor(values, "color.b.2"));
assertEquals(BLACK, getColor(darkValues, "color.b.1"));
assertEquals(BLUE, getColor(darkValues, "color.b.2"));
}
@Test
@ -133,32 +137,51 @@ public class ThemePropertyFileReaderTest {
//@formatter:on
List<String> errors = reader.getErrors();
assertEquals(1, errors.size());
assertEquals("Error parsing file \"test\" at line: 3, Could not parse Color: sdfsdf",
assertEquals("Error parsing file \"test\" at line: 3, Could not parse Color value: sdfsdf",
errors.get(0));
}
@Test
public void testParseFontError() throws IOException {
//@formatter:off
ThemePropertyFileReader reader = new ThemePropertyFileReader("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();
assertEquals(2, errors.size());
}
private Object getColorOrRef(GThemeValueMap values, String id) {
@Test
public void testParseFontModiferError() throws IOException {
//@formatter:off
ThemePropertyFileReader reader = new ThemePropertyFileReader("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();
assertEquals(1, errors.size());
}
private Color getColor(GThemeValueMap values, String id) {
ColorValue color = values.getColor(id);
if (color.getReferenceId() != null) {
return color.getReferenceId();
}
return color.getRawValue();
return color.get(values);
}
private Object getFontOrRef(GThemeValueMap values, String id) {
private Font getFont(GThemeValueMap values, String id) {
FontValue font = values.getFont(id);
if (font.getReferenceId() != null) {
return font.getReferenceId();
}
return font.getRawValue();
return font.get(values);
}
private Object getIconOrRef(GThemeValueMap values, String id) {
private Icon getIcon(GThemeValueMap values, String id) {
IconValue icon = values.getIcon(id);
if (icon.getReferenceId() != null) {
return icon.getReferenceId();
}
return icon.getRawValue();
return icon.get(values);
}
}

View file

@ -0,0 +1,104 @@
/* ###
* 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.laf;
import static ghidra.util.WebColors.*;
import static org.junit.Assert.*;
import java.awt.Color;
import java.awt.Font;
import org.junit.Before;
import org.junit.Test;
import generic.theme.*;
public class ThemeGrouperTest {
private static Font FONT1 = new Font("Dialog", Font.PLAIN, 12);
private static Font FONT2 = new Font("Dialog", Font.BOLD, 16);
private ThemeGrouper grouper;
private GThemeValueMap values;
@Before
public void setUp() {
grouper = new ThemeGrouper();
values = new GThemeValueMap();
}
@Test
public void testGroupColorUsingPreferredSources() {
initColor("control", RED);
initColor("menu", RED);
initColor("Menu.background", RED);
grouper.group(values);
ColorValue colorValue = values.getColor("Menu.background");
assertEquals("menu", colorValue.getReferenceId());
}
@Test
public void testGroupColorUsingNonPreferredSourceWhenPreferredDoesntMatch() {
initColor("control", RED);
initColor("menu", BLUE);
initColor("Menu.background", RED);
grouper.group(values);
ColorValue colorValue = values.getColor("Menu.background");
assertEquals("control", colorValue.getReferenceId());
}
@Test
public void testGroupFontUsingPreferredSources() {
initFont("Button.font", FONT1);
initFont("RadioButton.font", FONT1);
initFont("Menu.font", FONT1);
initFont("MenuItem.font", FONT1);
grouper.group(values);
assertEquals(FONT1, values.getFont("font.button").getRawValue());
assertEquals(FONT1, values.getFont("font.menu").getRawValue());
assertEquals("font.button", values.getFont("Button.font").getReferenceId());
assertEquals("font.button", values.getFont("RadioButton.font").getReferenceId());
assertEquals("font.menu", values.getFont("Menu.font").getReferenceId());
assertEquals("font.menu", values.getFont("MenuItem.font").getReferenceId());
}
@Test
public void testGroupFontUsingNonPreferredSourceWhenPreferredDoesntMatch() {
initFont("Button.font", FONT1);
initFont("RadioButton.font", FONT1);
initFont("Menu.font", FONT2);
initFont("MenuItem.font", FONT1);
grouper.group(values);
assertEquals(FONT1, values.getFont("font.button").getRawValue());
assertEquals(FONT2, values.getFont("font.menu").getRawValue());
assertEquals("font.button", values.getFont("Button.font").getReferenceId());
assertEquals("font.button", values.getFont("RadioButton.font").getReferenceId());
assertEquals("font.menu", values.getFont("Menu.font").getReferenceId());
assertEquals("font.button", values.getFont("MenuItem.font").getReferenceId());
}
private void initColor(String id, Color color) {
values.addColor(new ColorValue(id, color));
}
private void initFont(String id, Font font) {
values.addFont(new FontValue(id, font));
}
}