mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
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:
parent
15e633c239
commit
780d4b7671
55 changed files with 2719 additions and 1485 deletions
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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 + ")";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
// }
|
||||
//
|
||||
}
|
|
@ -25,10 +25,4 @@ public class GtkLookAndFeelManager extends LookAndFeelManager {
|
|||
public GtkLookAndFeelManager() {
|
||||
super(LafType.GTK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LookAndFeelInstaller getLookAndFeelInstaller() {
|
||||
return new GtkLookAndFeelInstaller();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue