mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
GP-4154 - Theming - Fixed font issues; updated font usage with attributes
This commit is contained in:
parent
c5bad0a88f
commit
b586d65a3b
91 changed files with 1309 additions and 1191 deletions
|
@ -34,7 +34,8 @@ color.fg.tree.selected = [color]system.color.fg.selected.view
|
|||
|
||||
// Fonts
|
||||
font.standard = [font]system.font.control
|
||||
font.monospaced = monospaced-PLAIN-12
|
||||
font.standard.bold = font.standard[bold]
|
||||
font.monospaced = monospaced-plain-12
|
||||
|
||||
|
||||
//
|
||||
|
|
|
@ -298,6 +298,11 @@ public class ApplicationThemeManager extends ThemeManager {
|
|||
lookAndFeelManager.registerFont(component, fontId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerFont(Component component, String fontId, int fontStyle) {
|
||||
lookAndFeelManager.registerFont(component, fontId, fontStyle);
|
||||
}
|
||||
|
||||
private void installFlatLookAndFeels() {
|
||||
UIManager.installLookAndFeel(LafType.FLAT_LIGHT.getName(), FlatLightLaf.class.getName());
|
||||
UIManager.installLookAndFeel(LafType.FLAT_DARK.getName(), FlatDarkLaf.class.getName());
|
||||
|
|
|
@ -68,7 +68,7 @@ public class FontModifier {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sets the font stle modifier. This can be called multiple times to bold and italicize.
|
||||
* Sets the font style modifier. This can be called multiple times to bold and italicize.
|
||||
* @param newStyle the style to use for the font.
|
||||
*/
|
||||
public void addStyleModifier(int newStyle) {
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/* ###
|
||||
* 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 javax.swing.text.SimpleAttributeSet;
|
||||
import javax.swing.text.StyleConstants;
|
||||
|
||||
import ghidra.util.HTMLUtilities;
|
||||
|
||||
/**
|
||||
* A drop-in replacement for clients using {@link SimpleAttributeSet}s. This class will apply a
|
||||
* default set of font attributes based on the given font and optional color.
|
||||
*/
|
||||
public class GAttributes extends SimpleAttributeSet {
|
||||
|
||||
public GAttributes(Font f) {
|
||||
this(f, null);
|
||||
}
|
||||
|
||||
public GAttributes(Font f, GColor c) {
|
||||
addAttribute(StyleConstants.FontFamily, f.getFamily());
|
||||
addAttribute(StyleConstants.FontSize, f.getSize());
|
||||
addAttribute(StyleConstants.Bold, f.isBold());
|
||||
addAttribute(StyleConstants.Italic, f.isItalic());
|
||||
|
||||
if (c != null) {
|
||||
addAttribute(StyleConstants.Foreground, c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method to style the given text in HTML using the font and color attributes
|
||||
* defined in this attribute set. The text will be HTML escaped.
|
||||
*
|
||||
* @param content the content
|
||||
* @return the styled content
|
||||
* @see HTMLUtilities#styleText(SimpleAttributeSet, String)
|
||||
*/
|
||||
public String toStyledHtml(String content) {
|
||||
return HTMLUtilities.styleText(this, content);
|
||||
}
|
||||
}
|
|
@ -17,8 +17,9 @@ package generic.theme;
|
|||
|
||||
import java.awt.*;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.LookAndFeel;
|
||||
import javax.swing.*;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Provides a static set of methods for globally managing application themes and their values.
|
||||
|
@ -36,6 +37,8 @@ import javax.swing.LookAndFeel;
|
|||
*
|
||||
*/
|
||||
public class Gui {
|
||||
private static final String FONT_SUFFIX = ".font";
|
||||
|
||||
// Start with an StubThemeManager so that simple tests can operate without having
|
||||
// to initialize the theme system. Applications and integration tests will
|
||||
// called ThemeManager.initialize() which will replace this with a fully initialized version.
|
||||
|
@ -146,6 +149,9 @@ public class Gui {
|
|||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
* Calling this method will trigger a call to {@link JComponent#setFont(Font)}.
|
||||
*
|
||||
* @param component the component to set/update the font
|
||||
* @param fontId the id of the font to register with the given component
|
||||
*/
|
||||
|
@ -153,6 +159,37 @@ public class Gui {
|
|||
themeManager.registerFont(component, fontId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given component with the given font style. This method allows clients to not
|
||||
* define a font id in the theme system, but instead to signal that they want the default font
|
||||
* for the given component, modified with the given style. As the underlying font is changed,
|
||||
* the client will be updated with that new font with the given style applied.
|
||||
* <P>
|
||||
* Most clients should <b>not</b> be using this method. Instead, use
|
||||
* {@link #registerFont(JComponent, int)}.
|
||||
* <P>
|
||||
* The downside of using this method is that the end user cannot modify the style of the font.
|
||||
* By using the standard theming mechanism for registering fonts, the end user has full control.
|
||||
*
|
||||
* @param component the component to set/update the font
|
||||
* @param fontStyle the font style, one of Font.BOLD, Font.ITALIC,
|
||||
*/
|
||||
public static void registerFont(JComponent component, int fontStyle) {
|
||||
|
||||
if (fontStyle == Font.PLAIN) {
|
||||
Msg.warn(Gui.class,
|
||||
"Gui.registerFont(Component, int) may only be used for a non-plain font style. " +
|
||||
"Use registerFont(Component, String) instead.");
|
||||
return;
|
||||
}
|
||||
|
||||
String id = component.getUIClassID(); // e.g., ButtonUI
|
||||
String name = id.substring(0, id.length() - 2); // strip off "UI"
|
||||
String fontId = FontValue.LAF_ID_PREFIX + name + FONT_SUFFIX; // e.g., laf.font.Button.font
|
||||
|
||||
themeManager.registerFont(component, fontId, fontStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the active theme is using dark defaults
|
||||
* @return true if the active theme is using dark defaults
|
||||
|
|
|
@ -575,6 +575,21 @@ public abstract class ThemeManager {
|
|||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
* This method is fairly niche and should not be called by most clients. Instead, call
|
||||
* {@link #registerFont(Component, String)}.
|
||||
*
|
||||
* @param component the component to set/update the font
|
||||
* @param fontId the id of the font to register with the given component
|
||||
* @param fontStyle the font style
|
||||
*/
|
||||
public void registerFont(Component component, String fontId, int fontStyle) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current theme use dark default values.
|
||||
* @return true if the current theme use dark default values.
|
||||
|
|
|
@ -28,7 +28,8 @@ import ghidra.util.datastruct.WeakSet;
|
|||
* for the font id, this class will update the component's font to the new value.
|
||||
*/
|
||||
public class ComponentFontRegistry {
|
||||
private WeakSet<Component> components = WeakDataStructureFactory.createCopyOnReadWeakSet();
|
||||
private WeakSet<StyledComponent> components =
|
||||
WeakDataStructureFactory.createCopyOnReadWeakSet();
|
||||
private String fontId;
|
||||
|
||||
/**
|
||||
|
@ -45,8 +46,18 @@ public class ComponentFontRegistry {
|
|||
* @param component the component to add
|
||||
*/
|
||||
public void addComponent(Component component) {
|
||||
component.setFont(Gui.getFont(fontId));
|
||||
components.add(component);
|
||||
addComponent(component, Font.PLAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows clients to update the default font being used for a component to use the given style.
|
||||
* @param component the component
|
||||
* @param fontStyle the font style (e.g., {@link Font#BOLD})
|
||||
*/
|
||||
public void addComponent(Component component, int fontStyle) {
|
||||
StyledComponent sc = new StyledComponent(component, fontStyle);
|
||||
sc.setFont(Gui.getFont(fontId));
|
||||
components.add(sc);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,10 +65,26 @@ public class ComponentFontRegistry {
|
|||
*/
|
||||
public void updateComponentFonts() {
|
||||
Font font = Gui.getFont(fontId);
|
||||
for (Component component : components) {
|
||||
for (StyledComponent c : components) {
|
||||
c.setFont(font);
|
||||
}
|
||||
}
|
||||
|
||||
private record StyledComponent(Component component, int fontStyle) {
|
||||
|
||||
void setFont(Font font) {
|
||||
Font existingFont = component.getFont();
|
||||
if (!Objects.equals(existingFont, font)) {
|
||||
component.setFont(font);
|
||||
Font styledFont = font;
|
||||
int style = fontStyle();
|
||||
if (style != Font.PLAIN) {
|
||||
// Only style the font when it is not plain. Doing this means that clients cannot
|
||||
// override a non-plain font to be plain. If clients need that behavior, they must
|
||||
// create their own custom font id and register their component with Gui.
|
||||
styledFont = font.deriveFont(style);
|
||||
}
|
||||
|
||||
if (!Objects.equals(existingFont, styledFont)) {
|
||||
component.setFont(styledFont);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import generic.theme.*;
|
|||
import generic.util.action.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import utilities.util.reflection.ReflectionUtilities;
|
||||
|
||||
/**
|
||||
* Manages installing and updating a {@link LookAndFeel}
|
||||
|
@ -38,6 +39,7 @@ public abstract class LookAndFeelManager {
|
|||
|
||||
private LafType laf;
|
||||
private Map<String, ComponentFontRegistry> fontRegistryMap = new HashMap<>();
|
||||
private Map<Component, String> componentToIdMap = new WeakHashMap<>();
|
||||
protected ApplicationThemeManager themeManager;
|
||||
protected Map<String, String> normalizedIdToLafIdMap;
|
||||
|
||||
|
@ -158,7 +160,7 @@ public abstract class LookAndFeelManager {
|
|||
* Called when one or more fonts have changed.
|
||||
* <p>
|
||||
* This will update the Java {@link UIManager} and trigger a reload of the UIs.
|
||||
*
|
||||
*
|
||||
* @param changedFontIds the set of Java Font ids that are affected by this change; these are
|
||||
* the normalized ids
|
||||
*/
|
||||
|
@ -202,12 +204,49 @@ public abstract class LookAndFeelManager {
|
|||
* @param fontId the id of the font to register with the given component
|
||||
*/
|
||||
public void registerFont(Component component, String fontId) {
|
||||
|
||||
checkForAlreadyRegistered(component, fontId);
|
||||
componentToIdMap.put(component, fontId);
|
||||
|
||||
ComponentFontRegistry register =
|
||||
fontRegistryMap.computeIfAbsent(fontId, id -> new ComponentFontRegistry(id));
|
||||
|
||||
register.addComponent(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the component to the font identified by the given font id. Whenever the font for
|
||||
* the font id changes, the component will be updated with the new font.
|
||||
* <p>
|
||||
* This method is fairly niche and should not be called by most clients. Instead, call
|
||||
* {@link #registerFont(Component, String)}.
|
||||
*
|
||||
* @param component the component to set/update the font
|
||||
* @param fontId the id of the font to register with the given component
|
||||
* @param fontStyle the font style
|
||||
*/
|
||||
public void registerFont(Component component, String fontId, int fontStyle) {
|
||||
|
||||
checkForAlreadyRegistered(component, fontId);
|
||||
componentToIdMap.put(component, fontId);
|
||||
|
||||
ComponentFontRegistry register =
|
||||
fontRegistryMap.computeIfAbsent(fontId, id -> new ComponentFontRegistry(id));
|
||||
|
||||
register.addComponent(component, fontStyle);
|
||||
}
|
||||
|
||||
private void checkForAlreadyRegistered(Component component, String newFontId) {
|
||||
String existingFontId = componentToIdMap.get(component);
|
||||
if (existingFontId != null) {
|
||||
Msg.warn(this, """
|
||||
Component has a Font ID registered more than once. \
|
||||
Previously registered ID: '%s'. Newly registered ID: '%s'.
|
||||
""".formatted(existingFontId, newFontId),
|
||||
ReflectionUtilities.createJavaFilteredThrowable());
|
||||
}
|
||||
}
|
||||
|
||||
private Font toUiResource(Font font) {
|
||||
if (!(font instanceof UIResource)) {
|
||||
return new FontUIResource(font);
|
||||
|
@ -292,8 +331,7 @@ public abstract class LookAndFeelManager {
|
|||
return false;
|
||||
}
|
||||
|
||||
protected void setKeyBinding(String existingKsText, String newKsText,
|
||||
String[] prefixValues) {
|
||||
protected void setKeyBinding(String existingKsText, String newKsText, String[] prefixValues) {
|
||||
|
||||
KeyStroke existingKs = KeyStroke.getKeyStroke(existingKsText);
|
||||
KeyStroke newKs = KeyStroke.getKeyStroke(newKsText);
|
||||
|
|
|
@ -153,8 +153,8 @@ public class UiDefaultsMapper {
|
|||
* the user changeable values for affecting the Java LookAndFeel colors, fonts, and icons.
|
||||
* <p>
|
||||
* The keys in the returned map have been normalized and all start with 'laf.'
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @return a map of changeable values that affect java LookAndFeel values
|
||||
*/
|
||||
public GThemeValueMap getNormalizedJavaDefaults() {
|
||||
|
@ -184,7 +184,7 @@ public class UiDefaultsMapper {
|
|||
* Returns a mapping of normalized LaF Ids so that when fonts and icons get changed using the
|
||||
* normalized ids that are presented to the user, we know which LaF ids need to be updated in
|
||||
* the UiDefaults so that the LookAndFeel will pick up and use the changes.
|
||||
*
|
||||
*
|
||||
* @return a mapping of normalized LaF ids to original LaF ids.
|
||||
*/
|
||||
public Map<String, String> getNormalizedIdToLafIdMap() {
|
||||
|
@ -281,7 +281,7 @@ public class UiDefaultsMapper {
|
|||
|
||||
/**
|
||||
* This allows clients to hard-code a chosen color for a group
|
||||
*
|
||||
*
|
||||
* @param group the system color id to assign the given color
|
||||
* @param color the color to be assigned to the system color id
|
||||
*/
|
||||
|
@ -291,7 +291,7 @@ public class UiDefaultsMapper {
|
|||
|
||||
/**
|
||||
* This allows clients to hard-code a chosen font for a group
|
||||
*
|
||||
*
|
||||
* @param group the system font id to assign the given font
|
||||
* @param font the font to be assigned to the system font id
|
||||
*/
|
||||
|
@ -693,7 +693,7 @@ public class UiDefaultsMapper {
|
|||
* Groups allow us to use the same group id for many components that by default have the same
|
||||
* value (Color or Font). This grouper allows us to specify the precedence to use when
|
||||
* searching for the best group.
|
||||
*
|
||||
*
|
||||
* @param <T> The theme value type (Color or Font)
|
||||
*/
|
||||
private abstract class ValueGrouper<T> {
|
||||
|
|
|
@ -22,9 +22,10 @@ import java.util.regex.Pattern;
|
|||
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.plaf.basic.BasicHTML;
|
||||
import javax.swing.text.View;
|
||||
import javax.swing.text.*;
|
||||
|
||||
import generic.text.TextLayoutGraphics;
|
||||
import generic.theme.GAttributes;
|
||||
import ghidra.util.html.HtmlLineSplitter;
|
||||
import utilities.util.reflection.ReflectionUtilities;
|
||||
|
||||
|
@ -343,6 +344,67 @@ public class HTMLUtilities {
|
|||
return buffy.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes and wraps the given text in {@code SPAN} tag with font attributes specified in the
|
||||
* given attributes. Specifically, these attributes are used:
|
||||
*
|
||||
* <UL>
|
||||
* <LI>{@link StyleConstants#Foreground} - {@link Color} object</LI>
|
||||
* <LI>{@link StyleConstants#FontFamily} - font name</LI>
|
||||
* <LI>{@link StyleConstants#FontSize} - size in pixels</LI>
|
||||
* <LI>{@link StyleConstants#Italic} - true if italic</LI>
|
||||
* <LI>{@link StyleConstants#Bold} - true if bold</LI>
|
||||
* </UL>
|
||||
* <P>
|
||||
* See {@link GAttributes} for a convenient way to create the correct attributes for a font and
|
||||
* color.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @param text the content to style
|
||||
* @return the styled content
|
||||
* @see GAttributes
|
||||
*/
|
||||
public static String styleText(SimpleAttributeSet attributes, String text) {
|
||||
|
||||
// StyleConstants.Foreground color: #00FF00;
|
||||
// StyleConstants.FontFamily font-family: "Tahoma";
|
||||
// StyleConstants.FontSize font-size: 40px;
|
||||
// StyleConstants.Italic font-style: italic;
|
||||
// StyleConstants.Bold font-weight: bold;
|
||||
|
||||
String family = attributes.getAttribute(StyleConstants.FontFamily).toString();
|
||||
String size = attributes.getAttribute(StyleConstants.FontSize).toString();
|
||||
String style = "plain";
|
||||
String weight = "plain";
|
||||
Boolean isItalic = (Boolean) attributes.getAttribute(StyleConstants.Italic);
|
||||
Boolean isBold = (Boolean) attributes.getAttribute(StyleConstants.Bold);
|
||||
if (Boolean.TRUE.equals(isItalic)) {
|
||||
style = "italic";
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(isBold)) {
|
||||
weight = "bold";
|
||||
}
|
||||
|
||||
// color is optional and defaults to the containing component's color
|
||||
String color = "";
|
||||
Object colorAttribute = attributes.getAttribute(StyleConstants.Foreground);
|
||||
if (colorAttribute instanceof Color fgColor) {
|
||||
String hexColor = HTMLUtilities.toHexString(fgColor);
|
||||
color = "color: % s;".formatted(hexColor);
|
||||
}
|
||||
|
||||
String escaped = escapeHTML(text);
|
||||
|
||||
//@formatter:off
|
||||
return """
|
||||
<SPAN STYLE=\"%s font-family: '%s'; font-size: %spx; font-style: %s; font-weight: %s;\">\
|
||||
%s\
|
||||
</SPAN>
|
||||
""".formatted(color, family, size, style, weight, escaped);
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given text wrapped in {@link #LINK_PLACEHOLDER_OPEN} and close tags.
|
||||
* If <code>foo</code> is passed for the HTML text, with a content value of <code>123456</code>, then
|
||||
|
@ -510,7 +572,7 @@ public class HTMLUtilities {
|
|||
|
||||
/**
|
||||
* See {@link #friendlyEncodeHTML(String)}
|
||||
*
|
||||
*
|
||||
* @param text string to be encoded
|
||||
* @param skipLeadingWhitespace true signals to ignore any leading whitespace characters.
|
||||
* This is useful when line wrapping to force wrapped lines to the left
|
||||
|
@ -593,8 +655,7 @@ public class HTMLUtilities {
|
|||
* Calling this twice will result in text being double-escaped, which will not display correctly.
|
||||
* <p>
|
||||
* See also <code>StringEscapeUtils#escapeHtml3(String)</code> if you need quote-safe html encoding.
|
||||
* <p>
|
||||
*
|
||||
*
|
||||
* @param text plain-text that might have some characters that should NOT be interpreted as HTML
|
||||
* @param makeSpacesNonBreaking true to convert spaces into {@value #HTML_SPACE}
|
||||
* @return string with any html characters replaced with equivalents
|
||||
|
@ -634,7 +695,7 @@ public class HTMLUtilities {
|
|||
|
||||
/**
|
||||
* Escapes any HTML special characters in the specified text.
|
||||
*
|
||||
*
|
||||
* @param text plain-text that might have some characters that should NOT be interpreted as HTML
|
||||
* @return string with any html characters replaced with equivalents
|
||||
* @see #escapeHTML(String, boolean)
|
||||
|
@ -647,7 +708,7 @@ public class HTMLUtilities {
|
|||
* Tests a unicode code point (i.e., 32 bit character) to see if it needs to be escaped before
|
||||
* being added to a HTML document because it is non-printable or a non-standard control
|
||||
* character
|
||||
*
|
||||
*
|
||||
* @param codePoint character to test
|
||||
* @return boolean true if character should be escaped
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue