GP-4154 - Theming - Fixed font issues; updated font usage with attributes

This commit is contained in:
dragonmacher 2024-02-23 13:13:06 -05:00
parent c5bad0a88f
commit b586d65a3b
91 changed files with 1309 additions and 1191 deletions

View file

@ -133,10 +133,18 @@ icon.task.progress.hourglass.11 = hourglass24_11.png
// Fonts
font.splash.header.default = Serif-BOLD-35
font.splash.status = Serif-BOLD-12
font.table.header.number = arial-BOLD-12
font.input.hint = monospaced-PLAIN-10
font.splash.header.default = serif-bold-35
font.splash.status = serif-bold-12
// default table renderer uses the JLabel font, which is mapped to system.font.control
font.table.base = [font]system.font.control
font.table.header.number = arial-bold-12
font.input.hint = monospaced-plain-10
font.task.monitor.label.message = sansserif-plain-10
font.wizard.border.title = sansserif-plain-10
@ -144,7 +152,6 @@ font.input.hint = monospaced-PLAIN-10
[Dark Defaults]
color.fg.filterfield = color.palette.darkslategray
color.bg.highlight = #703401 // orangish

View file

@ -59,9 +59,9 @@
<tocdef id="Root" sortgroup="a" text="Welcome to Help">
<tocdef id="Theming" text="Theming" sortgroup="t" target="help/topics/Theming/ThemingOverview.html">
<tocdef id="Overview" sortgroup="1" text="Overview" target="help/topics/Theming/ThemingOverview.html"/>
<tocdef id="Editing Themes" sortgroup="2" text="Editing Themes" target="help/topics/Theming/ThemingUserDocs.html"/>
<tocdef id="Architecture" sortgroup="3" text="Architecture" target="help/topics/Theming/ThemingInternals.html"/>
<tocdef id="Developer Documentation" sortgroup="4" text="Developer's Guide" target="help/topics/Theming/ThemingDeveloperDocs.html"/>
<tocdef id="Editing Themes" sortgroup="2" text="User's Guide" target="help/topics/Theming/ThemingUserDocs.html"/>
<tocdef id="Developer Documentation" sortgroup="3" text="Developer's Guide" target="help/topics/Theming/ThemingDeveloperDocs.html"/>
<tocdef id="Architecture" sortgroup="4" text="Architecture" target="help/topics/Theming/ThemingInternals.html"/>
</tocdef>
</tocdef>
</tocroot>

View file

@ -13,6 +13,14 @@
plugins, actions, scripts, etc., that use colors, fonts, or icons. By following these guidelines,
developers can easily make use of Ghidra's theming capabilities.</P>
<BLOCKQUOTE>
<BLOCKQUOTE>
<P><IMG border="0" src="help/shared/tip.png" alt="Tip">Most classes referenced in this document
live in the <CODE>generic.theme</CODE> package.
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2>Theme Resource Types</H2>
<P>When developing application code for Ghidra such as plugins, actions, etc., developers often
@ -122,8 +130,58 @@
<BLOCKQUOTE>
<CODE>Gui.registerFont(myLabel, "font.xyz");</CODE>
</BLOCKQUOTE>
<H3>Font Usage in Tables, Lists and Custom Painting</H3>
<P>
Ghidra makes great use of tables and to a lesser extent, lists. Both tables and lists
use renderers to paint cell values.
</P>
<UL>
<LI>
Java - By default, Java will use the font of the table/list as the font used during rendering.
</LI>
<LI>
Ghidra Tables - Ghidra does <U>not</U> use the table's font for rendering by default. Instead,
the renderer is initialized with the fonts used by <CODE>JLabel</CODE>, with additional
fonts for bold and monospaced text.
</LI>
<LI>
Ghidra Lists - Ghidra does not currently use custom rendering for lists. Thus, list cell
rendering will make use of the list's font, which is Java's default behavior.
</LI>
</UL>
<P>
We point out this difference between Java and Ghidra here so that developers understand
that changing fonts used for tables differs from Java. Specifically, calling
<BLOCKQUOTE>
<BLOCKQUOTE>
<PRE>
<CODE>
table.setFont(newFont);
</CODE>
</PRE>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P>
will not affect the font used when rendering the table. To programmatically change the
fonts used for tables, you can set the font directly on each cell renderer. As with a
any custom fonts used, be sure to define theme properties file and register then with the
Gui class as outlined above.
</P>
<P>
The fonts used for any painting operations, including table and list cell rendering, as well
as any code that overrides <CODE>paint</CODE> will work correctly within the theming
environment as long as the fonts are derived from the default Look and Feel values or are
obtained from the <CODE>Gui</CODE> class. In other words, as long as the
fonts used in custom painting are not hard-coded, any changes to the fonts via the theming
API will appear on the next call to paint the UI.
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>

View file

@ -26,7 +26,9 @@ import docking.action.DockingActionIf;
import docking.action.KeyBindingData;
import docking.tool.ToolConstants;
import docking.widgets.label.GIconLabel;
import generic.theme.GAttributes;
import generic.theme.GThemeDefaults.Colors.Messages;
import generic.theme.Gui;
import ghidra.util.HelpLocation;
import resources.Icons;
@ -43,7 +45,8 @@ public class KeyEntryDialog extends DialogComponentProvider {
private KeyEntryTextField keyEntryField;
private JTextPane collisionPane;
private StyledDocument doc;
private SimpleAttributeSet textAttrSet;
private SimpleAttributeSet textAttrs;
private Color bgColor;
public KeyEntryDialog(Tool tool, DockingActionIf action) {
@ -172,10 +175,8 @@ public class KeyEntryDialog extends DialogComponentProvider {
}
private void setUpAttributes() {
textAttrSet = new SimpleAttributeSet();
textAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
textAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
textAttrSet.addAttribute(StyleConstants.Foreground, Messages.NORMAL);
Font font = Gui.getFont("font.standard");
textAttrs = new GAttributes(font, Messages.NORMAL);
}
private void updateCollisionPane(KeyStroke ks) {
@ -194,7 +195,7 @@ public class KeyEntryDialog extends DialogComponentProvider {
String ksName = KeyBindingUtils.parseKeyStroke(ks);
String text = keyBindings.getActionsForKeyStrokeText(ksName);
try {
doc.insertString(0, text, textAttrSet);
doc.insertString(0, text, textAttrs);
collisionPane.setCaretPosition(0);
}
catch (BadLocationException e) {

View file

@ -26,6 +26,7 @@ import javax.swing.*;
import docking.widgets.combobox.GComboBox;
import docking.widgets.label.GDLabel;
import generic.theme.Gui;
import ghidra.util.Swing;
/**
@ -133,7 +134,7 @@ public class FontPropertyEditor extends PropertyEditorSupport {
JPanel panel = new JPanel(new GridLayout(2, 1));
GDLabel styleLabel = new GDLabel("Styles");
styleLabel.setFont(getFont().deriveFont(1));
Gui.registerFont(styleLabel, Font.BOLD);
styleLabel.setHorizontalAlignment(SwingConstants.CENTER);
panel.add(styleLabel);
@ -150,7 +151,7 @@ public class FontPropertyEditor extends PropertyEditorSupport {
JPanel panel = new JPanel(new GridLayout(2, 1));
GDLabel sizeLabel = new GDLabel("Sizes");
sizeLabel.setFont(getFont().deriveFont(1));
Gui.registerFont(sizeLabel, Font.BOLD);
sizeLabel.setHorizontalAlignment(SwingConstants.CENTER);
panel.add(sizeLabel);
@ -168,7 +169,7 @@ public class FontPropertyEditor extends PropertyEditorSupport {
JPanel panel = new JPanel(new GridLayout(2, 1));
GDLabel fontLabel = new GDLabel("Fonts");
fontLabel.setFont(getFont().deriveFont(1));
Gui.registerFont(fontLabel, Font.BOLD);
fontLabel.setHorizontalAlignment(SwingConstants.CENTER);
panel.add(fontLabel);

View file

@ -25,22 +25,32 @@ import javax.swing.plaf.basic.BasicHTML;
import javax.swing.table.DefaultTableCellRenderer;
import docking.widgets.label.GDHtmlLabel;
import generic.theme.GColor;
import generic.theme.GColorUIResource;
import generic.theme.*;
import generic.theme.GThemeDefaults.Colors.Palette;
import generic.theme.GThemeDefaults.Colors.Tables;
import ghidra.util.Msg;
import util.CollectionUtils;
import utilities.util.reflection.ReflectionUtilities;
/**
* A common base class for list and table renderer objects, unifying the Ghidra look and feel.
* <p>
* It allows (but default-disables) HTML content, automatically paints alternating row
* background colors, and highlights the drop target in a drag-n-drop operation.
*
* It allows (but default-disables) HTML content, automatically paints alternating row background
* colors, and highlights the drop target in a drag-n-drop operation.
* <p>
* The preferred method to change the font used by this renderer is {@link #setBaseFontId(String)}.
* If you would like this renderer to use a monospaced font, then, as an alternative to creating a
* font ID, you can instead override {@link #getDefaultFont()} to return this
* class's {@link #fixedWidthFont}. Also, the fixed width font of this class is based on the
* default font set when calling {@link #setBaseFontId(String)}, so it stays up-to-date with theme
* changes.
*/
public abstract class AbstractGCellRenderer extends GDHtmlLabel {
private static final Color BACKGROUND_COLOR = new GColor("color.bg.table.row");
private static final Color ALT_BACKGROUND_COLOR = new GColor("color.bg.table.row.alt");
private static final String BASE_FONT_ID = "font.table.base";
/** Allows the user to disable alternating row colors on JLists and JTables */
private static final String DISABLE_ALTERNATING_ROW_COLORS_PROPERTY =
"disable.alternating.row.colors";
@ -61,6 +71,9 @@ public abstract class AbstractGCellRenderer extends GDHtmlLabel {
private boolean instanceAlternateRowColors = true;
public AbstractGCellRenderer() {
setBaseFontId(BASE_FONT_ID);
noFocusBorder = BorderFactory.createEmptyBorder(0, 5, 0, 5);
Border innerBorder = BorderFactory.createEmptyBorder(0, 4, 0, 4);
Border outerBorder = BorderFactory.createLineBorder(Palette.YELLOW, 1);
@ -114,34 +127,48 @@ public abstract class AbstractGCellRenderer extends GDHtmlLabel {
return getBackgroundColorForRow(row);
}
/**
* Sets this renderer's theme font id. This will be used to load the base font and to create
* the derived fonts, such as bold and fixed width.
* @param fontId the font id
* @see Gui#registerFont(Component, String)
*/
public void setBaseFontId(String fontId) {
Font f = Gui.getFont(fontId);
defaultFont = f;
fixedWidthFont = new Font("monospaced", f.getStyle(), f.getSize());
boldFont = f.deriveFont(Font.BOLD);
Gui.registerFont(this, fontId);
}
@Override
public void setFont(Font f) {
super.setFont(f);
defaultFont = f;
fixedWidthFont = new Font("monospaced", defaultFont.getStyle(), defaultFont.getSize());
boldFont = f.deriveFont(Font.BOLD);
}
protected void superSetFont(Font font) {
super.setFont(font);
}
//
// Due to the nature of how setFont() is typically used (external client setup vs internal
// rendering), we created setBaseFontId() to allow external clients to set the base font in
// a way that is consistent with theming. Ignore any request to use one of our existing
// fonts, as some clients may do that from the getTableCellRendererComponent() method.
//
if (defaultFont != null &&
!CollectionUtils.isOneOf(f, defaultFont, fixedWidthFont, boldFont)) {
// sets the font of this renderer to be bold until the next time that
// getTableCellRenderer() is called, as it resets the font to the default font on each pass
protected void setBold() {
super.setFont(boldFont);
String caller =
ReflectionUtilities.getClassNameOlderThan(getClass().getName(), "generic.theme");
Msg.debug(this, "Calling setFont() on the renderer is discouraged. " +
"To change the font, call setBaseFontId(). Called from " + caller);
}
}
/**
* Sets the row where DnD would perform drop operation.
* @param dropRow the drop row
* Sets the font of this renderer to be bold until the next time that getTableCellRenderer() is
* called, as it resets the font to the default font on each pass.
* @see #getDefaultFont()
*/
public void setDropRow(int dropRow) {
this.dropRow = dropRow;
}
protected Border getNoFocusBorder() {
return noFocusBorder;
protected void setBold() {
super.setFont(boldFont);
}
protected Font getDefaultFont() {
@ -156,6 +183,18 @@ public abstract class AbstractGCellRenderer extends GDHtmlLabel {
return boldFont;
}
/**
* Sets the row where DnD would perform drop operation.
* @param dropRow the drop row
*/
public void setDropRow(int dropRow) {
this.dropRow = dropRow;
}
protected Border getNoFocusBorder() {
return noFocusBorder;
}
protected Color getDefaultBackgroundColor() {
return BACKGROUND_COLOR;
}

View file

@ -72,7 +72,8 @@ public class DropDownTextField<T> extends JTextField implements GComponent {
new DropDownWindowVisibilityListener<>();
private GDHtmlLabel previewLabel;
protected GList<T> list = new GList<>();
protected DropDownList list = new DropDownList();
private WeakSet<DropDownSelectionChoiceListener<T>> choiceListeners =
WeakDataStructureFactory.createSingleThreadAccessWeakSet();
private Collection<CellEditorListener> cellEditorListeners = new HashSet<>();
@ -82,7 +83,6 @@ public class DropDownTextField<T> extends JTextField implements GComponent {
private WindowComponentListener parentWindowListener = new WindowComponentListener();
private T selectedValue;
private int cellHeight;
private int matchingWindowHeight = MIN_HEIGHT;
private Point lastLocation;
protected final DropDownTextFieldDataModel<T> dataModel;
@ -278,15 +278,6 @@ public class DropDownTextField<T> extends JTextField implements GComponent {
private void initDataList() {
Font font = list.getFont();
FontMetrics fontMetrics = list.getFontMetrics(font);
int padding = 2; // top and bottom border height
int lineHeight = fontMetrics.getHeight() + padding;
int iconAndPaddingHeight = 16 + padding;
cellHeight = Math.max(lineHeight, iconAndPaddingHeight);
list.setFixedCellHeight(cellHeight);
list.setFixedCellWidth(MIN_WIDTH - 20); // add some fudge for scrollbars
list.setCellRenderer(dataModel.getListRenderer());
list.addKeyListener(keyListener);
@ -654,7 +645,7 @@ public class DropDownTextField<T> extends JTextField implements GComponent {
* signalling to use the clicked item. When pressing Enter, they may have been typing and
* ignoring the list, so we have to do some validation.
*/
@SuppressWarnings("unchecked") // for the cast to T
@SuppressWarnings("unchecked") // the item better be our type
private void setTextFromListOnEnterPress() {
Object selectedItem = list.getSelectedValue();
if (selectedItem == null) {
@ -747,6 +738,30 @@ public class DropDownTextField<T> extends JTextField implements GComponent {
// Inner Classes
//=================================================================================================
protected class DropDownList extends GList<T> {
@Override
public void setFont(Font f) {
super.setFont(f);
updateCellDimensions(f);
}
private void updateCellDimensions(Font font) {
if (font == null || list == null) {
return; // UI is initializing
}
FontMetrics fontMetrics = list.getFontMetrics(font);
int padding = 2; // top and bottom border height
int lineHeight = fontMetrics.getHeight() + padding;
int iconAndPaddingHeight = 16 + padding;
int cellHeight = Math.max(lineHeight, iconAndPaddingHeight);
list.setFixedCellHeight(cellHeight);
list.setFixedCellWidth(MIN_WIDTH - 20); // add some fudge for scrollbars
}
}
private class HideWindowFocusListener extends FocusAdapter {
@Override
public void focusLost(FocusEvent event) {

View file

@ -15,7 +15,8 @@
*/
package docking.widgets.dialogs;
import java.awt.*;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
@ -26,6 +27,7 @@ import docking.DockingUtils;
import docking.widgets.label.GDLabel;
import docking.widgets.label.GLabel;
import generic.theme.GThemeDefaults.Colors.Messages;
import generic.theme.Gui;
import ghidra.framework.OperatingSystem;
import ghidra.framework.Platform;
@ -34,6 +36,8 @@ public class MultiLineInputDialog extends DialogComponentProvider {
private static final KeyStroke SUBMIT_KEYSTROKE =
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, DockingUtils.CONTROL_KEY_MODIFIER_MASK);
private static final String FONT_ID = "font.input.hint";
private boolean isCanceled;
private JTextArea inputTextArea;
@ -84,10 +88,7 @@ public class MultiLineInputDialog extends DialogComponentProvider {
}
JLabel hintLabel = new GLabel("(" + metaKeyText + "-Enter to accept)");
hintLabel.setHorizontalAlignment(SwingConstants.CENTER);
Font font = hintLabel.getFont();
Font smallerFont = font.deriveFont(12F);
Font smallItalicFont = smallerFont.deriveFont(Font.ITALIC);
hintLabel.setFont(smallItalicFont);
Gui.registerFont(hintLabel, FONT_ID);
hintLabel.setForeground(Messages.HINT);
dataPanel.add(messageLabel, BorderLayout.NORTH);

View file

@ -49,39 +49,54 @@ class DirectoryList extends GList<File> implements GhidraFileChooserDirectoryMod
/**
* Create a new DirectoryList instance.
*
*
* @param chooser the {@link GhidraFileChooser} this instance is nested in
* @param model the {@link DirectoryListModel}
* @param font the parent component's font, used to calculate row height in the list once
*/
DirectoryList(GhidraFileChooser chooser, DirectoryListModel model, Font font) {
DirectoryList(GhidraFileChooser chooser, DirectoryListModel model) {
super(model);
this.chooser = chooser;
this.model = model;
build(font);
build();
}
private void build(Font font) {
@Override
public void setFont(Font font) {
super.setFont(font);
updateCellDimensions(font);
}
private void updateCellDimensions(Font font) {
if (font == null) {
return; // UI is being updated
}
FileListCellRenderer cellRenderer = (FileListCellRenderer) getCellRenderer();
if (cellRenderer == null) {
return; // initializing
}
// Enable the list to calculate the width of the cells on its own, but manually specify the
// height to ensure some padding between rows.
//
// Use 1/3 of the line height of the font to ensure visually consistent padding between
// rows. (Historically, 5px was used as the padding between the default 12pt (15px line
// height) rows, so 15px line height/5px padding equals .333 ratio.)
FontMetrics metrics = cellRenderer.getFontMetrics(font);
setFixedCellHeight(Math.max(metrics.getHeight(), DEFAULT_ICON_SIZE) +
Math.max(metrics.getHeight() / 3, MIN_HEIGHT_PADDING));
setFixedCellWidth(-1);
}
private void build() {
setLayoutOrientation(JList.VERTICAL_WRAP);
FileListCellRenderer cellRenderer = new FileListCellRenderer(chooser);
setCellRenderer(cellRenderer);
// Enable the list to calculate the width of the cells on its own, but manually
// specify the height to ensure some padding between rows.
// We need the parent component's Font instead of using our
// own #getFont() because we are not a child of the parent yet and
// the font may be set to something other than the default.
// Use 1/3 of the line height of the font to ensure visually consistent
// padding between rows. (historically, 5px was used as the padding
// between the default 12pt (15px lineht) rows, so 15px lineht/5px padding
// equals .333 ratio.)
FontMetrics metrics = cellRenderer.getFontMetrics(font);
setFixedCellHeight(
Math.max(metrics.getHeight(), DEFAULT_ICON_SIZE) +
Math.max(metrics.getHeight() / 3, MIN_HEIGHT_PADDING));
setFixedCellWidth(-1);
updateCellDimensions(getFont());
addMouseListener(new GMouseListenerAdapter() {
@Override

View file

@ -558,7 +558,7 @@ public class GhidraFileChooser extends ReusableDialogComponentProvider implement
private JScrollPane buildDirectoryList() {
directoryListModel = new DirectoryListModel();
directoryList = new DirectoryList(this, directoryListModel, rootPanel.getFont());
directoryList = new DirectoryList(this, directoryListModel);
directoryList.setName("LIST");
directoryList.setBackground(BACKGROUND_COLOR);

View file

@ -51,18 +51,17 @@ public class GTableCellRenderer extends AbstractGCellRenderer implements TableCe
private static final Color BG_DRAG = new GColor("color.bg.table.row.drag");
/*
* The map uses thread local variables to ensure that rendering and background model
* The map uses thread local variables to ensure that rendering and background model
* manipulation are thread safe.
*/
private static Map<Integer, ThreadLocal<DecimalFormat>> decimalFormatCache =
new HashMap<>();
private static Map<Integer, ThreadLocal<DecimalFormat>> decimalFormatCache = new HashMap<>();
static {
int n = FloatingPointPrecisionSettingsDefinition.MAX_PRECISION;
for (int i = 0; i <= n; i++) {
int precision = i;
ThreadLocal<DecimalFormat> localFormatter = ThreadLocal.withInitial(
() -> new DecimalFormat(createDecimalFormat(precision)));
ThreadLocal<DecimalFormat> localFormatter =
ThreadLocal.withInitial(() -> new DecimalFormat(createDecimalFormat(precision)));
decimalFormatCache.put(precision, localFormatter);
}
}
@ -84,7 +83,7 @@ public class GTableCellRenderer extends AbstractGCellRenderer implements TableCe
/**
* Constructs a new GTableCellRenderer using the specified font.
*
*
* @param f the font to use when rendering text in the table cells
*/
public GTableCellRenderer(Font f) {
@ -94,7 +93,7 @@ public class GTableCellRenderer extends AbstractGCellRenderer implements TableCe
/**
* Return the cell renderer text
*
*
* @param value Cell object value
* @return A string interpretation of value; generated by calling value.toString()
*/
@ -149,7 +148,7 @@ public class GTableCellRenderer extends AbstractGCellRenderer implements TableCe
* Provide basic cell rendering -- setting foreground and background colors, font, text,
* alignment, drop color, and border. Additional data that may be of use to the renderer is
* passed through the {@link docking.widgets.table.GTableCellRenderingData} object.
*
*
* @param data Context data used in the rendering of a data cell.
* @return The component used for drawing the table cell.
*/
@ -158,7 +157,6 @@ public class GTableCellRenderer extends AbstractGCellRenderer implements TableCe
Object value = data.getValue();
JTable table = data.getTable();
int row = data.getRowViewIndex();
int column = data.getColumnViewIndex();
boolean isSelected = data.isSelected();
boolean hasFocus = data.hasFocus();
Settings settings = data.getColumnSettings();
@ -173,7 +171,7 @@ public class GTableCellRenderer extends AbstractGCellRenderer implements TableCe
}
TableModel model = table.getModel();
configureFont(table, model, column);
setFont(getDefaultFont());
if (isSelected) {
setForeground(table.getSelectionForeground());
@ -199,8 +197,14 @@ public class GTableCellRenderer extends AbstractGCellRenderer implements TableCe
setForeground(table.getForeground());
}
protected void configureFont(JTable table, TableModel model, int column) {
setFont(defaultFont);
/**
* Override to change the font that will be used each time the renderer is initialized inside
* of {@link #getTableCellRendererComponent(GTableCellRenderingData)}
* @return the font
*/
@Override
protected Font getDefaultFont() {
return defaultFont;
}
protected int getRadix(Settings settings) {
@ -217,7 +221,7 @@ public class GTableCellRenderer extends AbstractGCellRenderer implements TableCe
/**
* Format a Number per the Settings parameters.
*
*
* @param value the number to format
* @param settings settings controlling the display of the Number parameter
* @return a formatted representation of the Number value

View file

@ -1,80 +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 docking.widgets.textarea;
import java.awt.*;
import javax.swing.JTextArea;
import generic.theme.GThemeDefaults.Colors;
import generic.theme.GThemeDefaults.Colors.Messages;
/**
* Simple text area that shows a text hint when the field is empty.
*
* Hint text will be shown in light grey, italicized, and in angle brackets. Normal text will
* be plain black.
*/
public class HintTextArea extends JTextArea {
private String hint;
/**
* Constructs the class with the hint text to be shown.
*
* @param hint the hint
*/
public HintTextArea(String hint) {
this.hint = hint;
}
/**
* Need to override the setText method so we can set font attributes.
*
* @param text the text
*/
@Override
public void setText(String text) {
super.setText(text);
setAttributes();
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (getText().isEmpty()) {
if (g instanceof Graphics2D) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Messages.HINT);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
if (hint != null) {
g2.drawString(hint, 5, 12);
}
}
}
}
/**
* Sets the text attributes to be used when NOT viewing the hint.
*/
protected void setAttributes() {
this.setFont(getFont().deriveFont(Font.PLAIN));
setForeground(Colors.FOREGROUND);
}
}

View file

@ -67,7 +67,7 @@ public class HintTextField extends JTextField {
/**
* Constructor
*
*
* @param hint the hint text
* @param required true, if the field should be marked as required
* @param verifier input verifier, or null if none needed
@ -78,7 +78,6 @@ public class HintTextField extends JTextField {
this.verifier = verifier;
addListeners();
setFont(getFont().deriveFont(Font.PLAIN));
validateField();
}
@ -143,7 +142,7 @@ public class HintTextField extends JTextField {
/**
* Sets whether the field is required or not. If so, it will be rendered
* differently to indicate that to the user.
*
*
* @param required true if required, false otherwise
*/
public void setRequired(boolean required) {
@ -161,7 +160,7 @@ public class HintTextField extends JTextField {
/**
* Returns true if the field contains valid input.
*
*
* @return true if valid, false otherwise
*/
public boolean isFieldValid() {
@ -179,7 +178,7 @@ public class HintTextField extends JTextField {
}
/**
* Checks the validity of the field and sets the appropriate
* Checks the validity of the field and sets the appropriate
* field attributes.
*/
private void validateField() {

View file

@ -28,6 +28,7 @@ import docking.ReusableDialogComponentProvider;
import docking.widgets.EmptyBorderButton;
import docking.widgets.label.GDLabel;
import generic.theme.GThemeDefaults.Colors.Messages;
import generic.theme.Gui;
import ghidra.util.*;
import help.Help;
import help.HelpService;
@ -47,6 +48,8 @@ public class WizardManager extends ReusableDialogComponentProvider implements Wi
private final static String INIT_TITLE = "<< untitled >>";
private static final String FONT_ID = "font.wizard.border.title";
private PanelManager panelMgr;
private WizardPanel currWizPanel;
private JButton backButton;
@ -91,7 +94,7 @@ public class WizardManager extends ReusableDialogComponentProvider implements Wi
}
/**
*
*
* @see docking.wizard.WizardPanelListener#validityChanged()
*/
@Override
@ -108,7 +111,7 @@ public class WizardManager extends ReusableDialogComponentProvider implements Wi
return getStatusText();
}
/**
/**
* @see docking.wizard.WizardPanelListener#setStatusMessage(String)
*/
@Override
@ -220,8 +223,7 @@ public class WizardManager extends ReusableDialogComponentProvider implements Wi
titleLabel = (wizardIcon == null ? new GDLabel(INIT_TITLE)
: new GDLabel(INIT_TITLE, wizardIcon, SwingConstants.TRAILING));
EmptyBorderButton helpButton =
new EmptyBorderButton(Icons.INFO_ICON);
EmptyBorderButton helpButton = new EmptyBorderButton(Icons.INFO_ICON);
helpButton.setToolTipText("Help (F1)");
helpButton.addActionListener(
e -> DockingWindowManager.getHelpService().showHelp(rootPanel, false, rootPanel));
@ -438,7 +440,7 @@ if (!visitedMap.containsKey(currWizPanel)) {
return; // nothing to do
}
// this will have no effect if we are not showing, but the above call will handle that
// this will have no effect if we are not showing, but the above call will handle that
// case
defaultFocusComponent.requestFocusInWindow();
}
@ -465,14 +467,12 @@ if (!visitedMap.containsKey(currWizPanel)) {
if (scrollPane.getVerticalScrollBar().isShowing()) {
TitledBorder titledBorder =
new TitledBorder(BorderFactory.createEmptyBorder(), "(scroll for more options)");
Font font = titledBorder.getTitleFont();
if (font == null) {
// workaround for bug on Java 7
font = titleLabel.getFont();
}
titledBorder.setTitleFont(font.deriveFont(10f));
Gui.addThemeListener(e -> {
if (e.isFontChanged(FONT_ID)) {
titledBorder.setTitleFont(Gui.getFont(FONT_ID));
}
});
titledBorder.setTitleFont(Gui.getFont(FONT_ID));
titledBorder.setTitleColor(Messages.NORMAL);
titledBorder.setTitlePosition(TitledBorder.BOTTOM);
titledBorder.setTitleJustification(TitledBorder.TRAILING);

View file

@ -15,20 +15,14 @@
*/
package ghidra.docking.util;
import java.awt.Font;
import java.awt.Taskbar;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import javax.swing.*;
import javax.swing.LookAndFeel;
import javax.swing.plaf.ComponentUI;
import docking.framework.ApplicationInformationDisplayFactory;
import generic.theme.LafType;
import generic.theme.ThemeManager;
import ghidra.framework.preferences.Preferences;
import ghidra.util.SystemUtilities;
/**
* A utility class to manage LookAndFeel (LaF) settings.
@ -40,38 +34,12 @@ public class LookAndFeelUtils {
}
/**
* Loads settings from {@link Preferences}.
* This method does nothing. This is not handled by the theming system in the look and feel
* manager.
*/
@Deprecated(since = "11.1", forRemoval = true)
public static void installGlobalOverrides() {
//
// Users can change this via the SystemUtilities.FONT_SIZE_OVERRIDE_PROPERTY_NAME
// system property.
//
Integer fontOverride = SystemUtilities.getFontSizeOverrideValue();
if (fontOverride != null) {
setGlobalFontSizeOverride(fontOverride);
}
}
/** Allows you to globally set the font size (don't use this method!) */
private static 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);
}
}
}
}
public static void performPlatformSpecificFixups() {

View file

@ -28,6 +28,7 @@ import org.apache.commons.lang3.StringUtils;
import docking.widgets.EmptyBorderButton;
import docking.widgets.OptionDialog;
import docking.widgets.label.GDHtmlLabel;
import generic.theme.Gui;
import ghidra.util.Swing;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
@ -43,6 +44,8 @@ import resources.Icons;
*/
public class TaskMonitorComponent extends JPanel implements TaskMonitor {
private static final String MESSAGE_FONT_ID = "font.task.monitor.label.message";
private WeakSet<CancelledListener> listeners =
WeakDataStructureFactory.createCopyOnReadWeakSet();
@ -458,7 +461,7 @@ public class TaskMonitorComponent extends JPanel implements TaskMonitor {
// don't care
}
};
messageLabel.setFont(messageLabel.getFont().deriveFont((float) 10.0));
Gui.registerFont(messageLabel, MESSAGE_FONT_ID);
Dimension d = messageLabel.getPreferredSize();
d.width = 180;
messageLabel.setPreferredSize(d);

View file

@ -35,7 +35,7 @@ public class ReflectionUtilitiesTest {
@Test
public void testGetClassNameAfter_NoClasses() {
String caller = ReflectionUtilities.getClassNameOlderThan();
String caller = ReflectionUtilities.getClassNameOlderThan(new String[0]);
assertThat(caller, is(equalTo(ReflectionUtilitiesTest.class.getName())));
}

View file

@ -23,12 +23,13 @@ color.visualgraph.view.satellite.edge.focused = color.palette.green
color.visualgraph.view.satellite.edge.selected = color.palette.lime
color.visualgraph.view.satellite.edge.hovered = color.palette.lime
color.graphdisplay.vertex.default = color.palette.green
color.graphdisplay.edge.default = color.palette.green
color.graphdisplay.vertex.selected = color.palette.blue
color.graphdisplay.edge.selected = color.palette.blue
font.visualgraph.view.label.message = SansSerif-PLAIN-22 // bigger for legibility in the graph
icon.graph.satellite = network-wireless-16.png
icon.graph.satellite.large = network-wireless.png
icon.graph.layout.default = color_swatch.png

View file

@ -27,6 +27,7 @@ import docking.widgets.label.GDLabel;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import generic.theme.Gui;
import ghidra.graph.VisualGraph;
import ghidra.graph.viewer.event.mouse.VertexTooltipProvider;
import ghidra.graph.viewer.event.mouse.VisualGraphMousePlugin;
@ -38,18 +39,18 @@ import ghidra.graph.viewer.vertex.VertexFocusListener;
* A view object, where 'view' is used in the sense of the Model-View-Controller (MVC) pattern.
* This class will contain all UI widgets need to display and interact with a graph.
*
* <p><b><u>Implementation Note:</u></b>
* <p><b><u>Implementation Note:</u></b>
* <ol>
* <li>The graph of this component can be null, changing to non-null values over the
* lifetime of this view. This allows this view to be installed in a UI component, with the
* contents changing as needed.
* <li>The graph of this component can be null, changing to non-null values over the
* lifetime of this view. This allows this view to be installed in a UI component, with the
* contents changing as needed.
* </li>
* <li>
* When the graph is {@link #setGraph(VisualGraph) set}, the view portion of the class is
* recreated.
* </li>
* <li>
* At any given point in time there may not be a {@link #graphComponent}. This means that
* At any given point in time there may not be a {@link #graphComponent}. This means that
* this class must maintain settings state that it will apply when the component is created.
* This state is atypical and makes this class a bit harder to understand.
* </li>
@ -60,14 +61,16 @@ import ghidra.graph.viewer.vertex.VertexFocusListener;
* @param <G> the graph type
*/
//@formatter:off
public class VisualGraphView<V extends VisualVertex,
E extends VisualEdge<V>,
public class VisualGraphView<V extends VisualVertex,
E extends VisualEdge<V>,
G extends VisualGraph<V, E>> {
//@formatter:on
//@formatter:on
private static final float ZOOM_OUT_AMOUNT = .9f;
private static final float ZOOM_IN_AMOUNT = 1.1f;
private static final String MESSAGE_FONT_ID = "font.visualgraph.view.label.message";
private JPanel viewPanel;
private JPanel viewContentPanel;
@ -76,7 +79,7 @@ public class VisualGraphView<V extends VisualVertex,
* As graph data is updated, we set and clear the contents of this panel as needed. This
* allows the client to initialize the satellite window once, with updates controlled by
* this class.
*
*
* Note: this panel will be empty when docked and when the viewer is not yet built
*/
private JPanel undockedSatelliteContentPanel;
@ -99,7 +102,7 @@ public class VisualGraphView<V extends VisualVertex,
private Optional<GraphSatelliteListener> clientSatelliteListener = Optional.empty();
// this internal listener is the way we manage keeping our state in sync with the
// this internal listener is the way we manage keeping our state in sync with the
// graph component, as well as how we notify the client listener
private GraphSatelliteListener internalSatelliteListener = (docked, visible) -> {
@ -190,10 +193,10 @@ public class VisualGraphView<V extends VisualVertex,
}
/**
* Sets a listener that allows clients to be notified of vertex double-clicks. Normal
* Sets a listener that allows clients to be notified of vertex double-clicks. Normal
* mouse processing is handled by the {@link VisualGraphMousePlugin} class. This is a
* convenience method so that clients do not have to deal with the mouse plugin.
*
*
* @param l the listener
*/
public void setVertexClickListener(VertexClickListener<V, E> l) {
@ -290,9 +293,9 @@ public class VisualGraphView<V extends VisualVertex,
}
/**
* Returns the primary viewer of the graph (as opposed to the satellite viewer). The
* Returns the primary viewer of the graph (as opposed to the satellite viewer). The
* viewer returned is responsible for maintaining view information for a given graph.
*
*
* @return the primary viewer
*/
public GraphViewer<V, E> getPrimaryGraphViewer() {
@ -308,7 +311,7 @@ public class VisualGraphView<V extends VisualVertex,
/**
* Sets the perspective for this view
*
*
* @param newPerspective the new perspective
*/
public void setGraphPerspective(GraphPerspectiveInfo<V, E> newPerspective) {
@ -350,8 +353,7 @@ public class VisualGraphView<V extends VisualVertex,
viewContentPanel.removeAll();
viewContentPanel.paintImmediately(viewContentPanel.getBounds());
JLabel messageLabel = new GDLabel(errorMessage);
Font font = messageLabel.getFont();
messageLabel.setFont(font.deriveFont(22f)); // make a bit bigger for readability
Gui.registerFont(messageLabel, MESSAGE_FONT_ID);
messageLabel.setHorizontalAlignment(SwingConstants.CENTER);
messageLabel.setFocusable(true); // we have to have something focusable in our provider
viewContentPanel.add(messageLabel, BorderLayout.NORTH);
@ -362,8 +364,8 @@ public class VisualGraphView<V extends VisualVertex,
/**
* Sets a message to be painted on the viewer. This is useful to show a text message to the
* user. Passing null will clear the message.
*
* @param message the status message
*
* @param message the status message
*/
public void setStatusMessage(String message) {
if (graphComponent != null) {
@ -377,10 +379,10 @@ public class VisualGraphView<V extends VisualVertex,
/**
* Returns whether the satellite intended to be visible. If this component is built, then
* a result of true means that the satellite is showing. If the component is not yet
* built, then a result of true means that the satellite will be made visible when the
* a result of true means that the satellite is showing. If the component is not yet
* built, then a result of true means that the satellite will be made visible when the
* component is built.
*
*
* @return true if visible
*/
public boolean isSatelliteVisible() {
@ -421,10 +423,10 @@ public class VisualGraphView<V extends VisualVertex,
/**
* Returns whether the satellite intended to be docked. If this component is built, then
* a result of true means that the satellite is docked. If the component is not yet
* built, then a result of true means that the satellite will be made docked when the
* a result of true means that the satellite is docked. If the component is not yet
* built, then a result of true means that the satellite will be made docked when the
* component is built.
*
*
* @return true if visible
*/
public boolean isSatelliteDocked() {
@ -517,8 +519,8 @@ public class VisualGraphView<V extends VisualVertex,
}
public Point translatePointFromVertexToViewSpace(V v, Point p) {
return GraphViewerUtils.translatePointFromVertexRelativeSpaceToViewSpace(
getPrimaryGraphViewer(), v, p);
return GraphViewerUtils
.translatePointFromVertexRelativeSpaceToViewSpace(getPrimaryGraphViewer(), v, p);
}
public Rectangle translateRectangleFromVertexToViewSpace(V v, Rectangle r) {
@ -577,7 +579,7 @@ public class VisualGraphView<V extends VisualVertex,
}
/**
* Effectively clears this display. This method is not called dispose, as that implies
* Effectively clears this display. This method is not called dispose, as that implies
* the end of an object's lifecycle. This object can be re-used after this method is
* called.
*/

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -84,8 +84,12 @@ icon.plugin.manager.default = plasma.png
font.help.about = font.monospaced
font.keybindings.status = sansserif-plain-11
font.task.viewer = sansserif-bold-36
font.user.agreement = sansserif-plain-16
font.task.progress.label.message = sansserif-plain-12
font.user.agreement = sansserif-italic-22
font.panel.details = font.standard
font.panel.details.monospaced = font.monospaced[bold]
font.pluginpanel.name = sansserif-plain-18

View file

@ -15,7 +15,8 @@
*/
package ghidra.framework.main;
import java.awt.*;
import java.awt.BorderLayout;
import java.awt.Insets;
import java.io.InputStream;
import javax.swing.*;
@ -57,11 +58,10 @@ public class UserAgreementDialog extends DialogComponentProvider {
}
private JComponent buildWorkPanel() {
Font font = Gui.getFont(FONT_ID);
JPanel panel = new JPanel(new BorderLayout());
JLabel label = new GDLabel("Ghidra User Agreement", SwingConstants.CENTER);
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
label.setFont(font.deriveFont(Font.ITALIC, 22f));
Gui.registerFont(label, FONT_ID);
panel.add(label, BorderLayout.NORTH);
panel.setBorder(BorderFactory.createEmptyBorder(10, 40, 40, 40));
JEditorPane editorPane = new JEditorPane();

View file

@ -23,6 +23,7 @@ import java.util.List;
import javax.swing.*;
import docking.widgets.label.GDLabel;
import generic.theme.Gui;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.remote.User;
@ -33,9 +34,9 @@ import ghidra.framework.remote.User;
*/
public class ViewProjectAccessPanel extends ProjectAccessPanel {
/**
/**
* Construct a new panel.
*
*
* @param repository handle to the repository adapter
* @param tool the plugin tool
* @throws IOException if there's an error processing repository information
@ -47,13 +48,13 @@ public class ViewProjectAccessPanel extends ProjectAccessPanel {
/**
* Constructs a new panel.
*
*
* @param knownUsers names of the users that are known to the remote server
* @param currentUser the current user
* @param allUsers all users known to the repository
* @param repositoryName the name of the repository
* @param anonymousServerAccessAllowed true if the server allows anonymous access
* @param anonymousAccessEnabled true if the repository allows anonymous access
* @param anonymousAccessEnabled true if the repository allows anonymous access
* (ignored if anonymousServerAccessAllowed is false)
* @param tool the current tool
*/
@ -66,7 +67,7 @@ public class ViewProjectAccessPanel extends ProjectAccessPanel {
}
/**
* Creates the main gui panel, containing the known users, button, and user access
* Creates the main gui panel, containing the known users, button, and user access
* panels.
*/
@Override
@ -82,9 +83,7 @@ public class ViewProjectAccessPanel extends ProjectAccessPanel {
if (anonymousServerAccessAllowed && origAnonymousAccessEnabled) {
JLabel anonymousAccessLabel = new GDLabel("Anonymous Read-Only Access Enabled");
anonymousAccessLabel.setBorder(BorderFactory.createEmptyBorder(5, 2, 0, 0));
Font f = anonymousAccessLabel.getFont().deriveFont(Font.ITALIC);
anonymousAccessLabel.setFont(f);
Gui.registerFont(anonymousAccessLabel, Font.ITALIC);
mainPanel.add(anonymousAccessLabel, BorderLayout.SOUTH);
}

View file

@ -17,79 +17,57 @@ package ghidra.framework.plugintool.dialog;
import static ghidra.util.HTMLUtilities.*;
import java.awt.*;
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.*;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import docking.widgets.label.GDHtmlLabel;
import generic.theme.GColor;
import ghidra.util.HTMLUtilities;
import generic.theme.*;
/**
* Abstract class that defines a panel for displaying name/value pairs with html-formatting.
*/
public abstract class AbstractDetailsPanel extends JPanel {
protected static final String FONT_DEFAULT = "font.panel.details";
protected static final String FONT_MONOSPACED = "font.panel.details.monospaced";
private static final int MIN_WIDTH = 700;
protected static final int LEFT_COLUMN_WIDTH = 150;
protected static final int RIGHT_MARGIN = 30;
// Font attributes for the title of each row.
protected static SimpleAttributeSet titleAttrSet;
protected static GAttributes titleAttrs;
protected JLabel textLabel;
protected JScrollPane sp;
private ThemeListener themeListener = e -> {
if (e.isFontChanged(FONT_DEFAULT) || e.isFontChanged(FONT_MONOSPACED)) {
updateFieldAttributes();
}
};
protected AbstractDetailsPanel() {
createFieldAttributes();
Gui.addThemeListener(themeListener);
}
private void updateFieldAttributes() {
createFieldAttributes();
refresh();
repaint();
}
/**
* Sets attributes for the different pieces of information being displayed in this
* Sets attributes for the different pieces of information being displayed in this
* panel.
*/
protected abstract void createFieldAttributes();
/**
* Returns a new {@link SimpleAttributeSet} with all attributes set by the caller.
*
* @param fontFamily the font to use
* @param fontSize the font size
* @param bold if true, render text bold
* @param color the foreground text color
* @return a new attribute set
*/
protected SimpleAttributeSet createAttributeSet(String fontFamily, int fontSize, boolean bold,
Color color) {
SimpleAttributeSet attrSet = new SimpleAttributeSet();
attrSet.addAttribute(StyleConstants.FontFamily, fontFamily);
attrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(fontSize));
attrSet.addAttribute(StyleConstants.Bold, bold);
attrSet.addAttribute(StyleConstants.Foreground, color);
return attrSet;
}
/**
* Returns a new {@link SimpleAttributeSet} with the following default attributes set:
* <ul>
* <li>FontFamily: "Tahoma"</li>
* <li>FontSize: 11</li>
* <li>Bold: True</li>
* </ul>
*
* @param color the foreground text color
* @return a new attribute set
*/
protected SimpleAttributeSet createAttributeSet(Color color) {
SimpleAttributeSet attrSet = new SimpleAttributeSet();
attrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
attrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
attrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
attrSet.addAttribute(StyleConstants.Foreground, color);
return attrSet;
}
protected abstract void refresh();
/**
* Clears the text in the details pane.
@ -127,27 +105,26 @@ public abstract class AbstractDetailsPanel extends JPanel {
/**
* Inserts an html-formatted string into the given buffer. This is meant to be used
* for inserting the name of each row in the description text.
*
*
* @param buffer the string buffer to add to
* @param rowName the name of the row to add
*/
protected void insertRowTitle(StringBuilder buffer, String rowName) {
buffer.append("<TR>");
buffer.append("<TD VALIGN=\"TOP\">");
insertHTMLLine(buffer, rowName + ":", titleAttrSet);
insertHTMLLine(buffer, rowName + ":", titleAttrs);
buffer.append("</TD>");
}
/**
* Inserts an html-formatted string into the given buffer. This is meant to be used
* for inserting the value of each row in the description text.
*
*
* @param buffer the string buffer to add to
* @param value the text to add
* @param attributes the structure containing formatting information
* @param attributes the structure containing formatting information
*/
protected void insertRowValue(StringBuilder buffer, String value,
SimpleAttributeSet attributes) {
protected void insertRowValue(StringBuilder buffer, String value, GAttributes attributes) {
buffer.append("<TD VALIGN=\"TOP\" WIDTH=\"80%\">");
insertHTMLLine(buffer, value, attributes);
buffer.append("</TD>");
@ -161,33 +138,13 @@ public abstract class AbstractDetailsPanel extends JPanel {
* @param string the string to add
* @param attributes the formatting instructions
*/
protected void insertHTMLString(StringBuilder buffer, String string,
SimpleAttributeSet attributes) {
protected void insertHTMLString(StringBuilder buffer, String string, GAttributes attributes) {
if (string == null) {
return;
}
buffer.append("<FONT COLOR=\"");
Color foregroundColor = (Color) attributes.getAttribute(StyleConstants.Foreground);
buffer.append(HTMLUtilities.toHexString(foregroundColor));
buffer.append("\" FACE=\"");
buffer.append(attributes.getAttribute(StyleConstants.FontFamily).toString());
buffer.append("\">");
Boolean isBold = (Boolean) attributes.getAttribute(StyleConstants.Bold);
isBold = (isBold == null) ? Boolean.FALSE : isBold;
String text = HTMLUtilities.escapeHTML(string);
if (isBold) {
text = HTMLUtilities.bold(text);
}
buffer.append(text);
buffer.append("</FONT>");
buffer.append(attributes.toStyledHtml(string));
}
/**
@ -196,8 +153,7 @@ public abstract class AbstractDetailsPanel extends JPanel {
* @param string the string to insert
* @param attributes the attributes to apply
*/
protected void insertHTMLLine(StringBuilder buffer, String string,
SimpleAttributeSet attributes) {
protected void insertHTMLLine(StringBuilder buffer, String string, GAttributes attributes) {
if (string == null) {
return;
}

View file

@ -15,17 +15,16 @@
*/
package ghidra.framework.plugintool.dialog;
import java.awt.Font;
import java.awt.Point;
import java.util.*;
import javax.swing.KeyStroke;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import docking.action.DockingActionIf;
import docking.action.MenuData;
import docking.actions.KeyBindingUtils;
import generic.theme.GColor;
import generic.theme.*;
import ghidra.framework.plugintool.PluginConfigurationModel;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginDescription;
@ -37,29 +36,52 @@ import ghidra.util.HTMLUtilities;
*/
class PluginDetailsPanel extends AbstractDetailsPanel {
private SimpleAttributeSet nameAttrSet;
private SimpleAttributeSet depNameAttrSet;
private SimpleAttributeSet descrAttrSet;
private SimpleAttributeSet categoriesAttrSet;
private SimpleAttributeSet classAttrSet;
private SimpleAttributeSet locAttrSet;
private SimpleAttributeSet developerAttrSet;
private SimpleAttributeSet dependencyAttrSet;
private SimpleAttributeSet noValueAttrSet;
private static final GColor NO_VALUE_COLOR = new GColor("color.fg.pluginpanel.details.novalue");
private static final GColor DEPENDENCY_COLOR =
new GColor("color.fg.pluginpanel.details.dependency");
private static final GColor LOCATION_COLOR = new GColor("color.fg.pluginpanel.details.loc");
private static final GColor DEVELOPER_COLOR =
new GColor("color.fg.pluginpanel.details.developer");
private static final GColor CLASS_COLOR = new GColor("color.fg.pluginpanel.details.class");
private static final GColor CATEGORIES_COLOR =
new GColor("color.fg.pluginpanel.details.category");
private static final GColor TITLE_COLOR = new GColor("color.fg.pluginpanel.details.title");
private static final GColor DESCRIPTION_COLOR =
new GColor("color.fg.pluginpanel.details.description");
private static final GColor NAME_NO_DEPENDENTS_COLOR =
new GColor("color.fg.pluginpanel.details.name.no.dependents");
private static final GColor NAME_DEPENDENTS_COLOR =
new GColor("color.fg.pluginpanel.details.name.has.dependents");
private GAttributes nameAttrs;
private GAttributes dependenciesNameAttrs;
private GAttributes descriptionAttrs;
private GAttributes categoriesAttrs;
private GAttributes classAttrs;
private GAttributes locationAttrs;
private GAttributes developerAttrs;
private GAttributes dependencyAttrs;
private GAttributes noValueAttrs;
private final PluginConfigurationModel model;
private PluginTool tool;
private PluginDescription currentDescriptor;
PluginDetailsPanel(PluginTool tool, PluginConfigurationModel model) {
super();
this.tool = tool;
this.model = model;
createFieldAttributes();
createMainPanel();
}
@Override
protected void refresh() {
setPluginDescription(currentDescriptor);
}
void setPluginDescription(PluginDescription descriptor) {
this.currentDescriptor = descriptor;
textLabel.setText("");
if (descriptor == null) {
return;
@ -74,43 +96,43 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
insertRowTitle(buffer, "Name");
insertRowValue(buffer, descriptor.getName(),
!dependencies.isEmpty() ? depNameAttrSet : nameAttrSet);
!dependencies.isEmpty() ? dependenciesNameAttrs : nameAttrs);
insertRowTitle(buffer, "Description");
insertRowValue(buffer, descriptor.getDescription(), descrAttrSet);
insertRowValue(buffer, descriptor.getDescription(), descriptionAttrs);
insertRowTitle(buffer, "Status");
insertRowValue(buffer, descriptor.getStatus().getDescription(),
(descriptor.getStatus() == PluginStatus.RELEASED) ? titleAttrSet : developerAttrSet);
(descriptor.getStatus() == PluginStatus.RELEASED) ? titleAttrs : developerAttrs);
insertRowTitle(buffer, "Package");
insertRowValue(buffer, descriptor.getPluginPackage().getName(), categoriesAttrSet);
insertRowValue(buffer, descriptor.getPluginPackage().getName(), categoriesAttrs);
insertRowTitle(buffer, "Category");
insertRowValue(buffer, descriptor.getCategory(), categoriesAttrSet);
insertRowValue(buffer, descriptor.getCategory(), categoriesAttrs);
insertRowTitle(buffer, "Plugin Class");
insertRowValue(buffer, descriptor.getPluginClass().getName(), classAttrSet);
insertRowValue(buffer, descriptor.getPluginClass().getName(), classAttrs);
insertRowTitle(buffer, "Class Location");
insertRowValue(buffer, descriptor.getSourceLocation(), locAttrSet);
insertRowValue(buffer, descriptor.getSourceLocation(), locationAttrs);
insertRowTitle(buffer, "Used By");
buffer.append("<TD VALIGN=\"TOP\">");
if (dependencies.isEmpty()) {
insertHTMLLine(buffer, "None", noValueAttrSet);
insertHTMLLine(buffer, "None", noValueAttrs);
}
else {
for (int i = 0; i < dependencies.size(); i++) {
insertHTMLString(buffer, dependencies.get(i).getPluginClass().getName(),
dependencyAttrSet);
dependencyAttrs);
if (i < dependencies.size() - 1) {
buffer.append(HTMLUtilities.BR);
}
}
insertHTMLLine(buffer, "", titleAttrSet); // add a newline
insertHTMLLine(buffer, "", titleAttrs); // add a newline
}
buffer.append("</TD>");
buffer.append("</TR>");
@ -121,16 +143,16 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
List<Class<?>> servicesRequired = descriptor.getServicesRequired();
if (servicesRequired.isEmpty()) {
insertHTMLLine(buffer, "None", noValueAttrSet);
insertHTMLLine(buffer, "None", noValueAttrs);
}
else {
for (int i = 0; i < servicesRequired.size(); i++) {
insertHTMLString(buffer, servicesRequired.get(i).getName(), dependencyAttrSet);
insertHTMLString(buffer, servicesRequired.get(i).getName(), dependencyAttrs);
if (i < servicesRequired.size() - 1) {
buffer.append(HTMLUtilities.BR);
}
}
insertHTMLLine(buffer, "", titleAttrSet); // add a newline
insertHTMLLine(buffer, "", titleAttrs); // add a newline
}
buffer.append("</TD>");
buffer.append("</TR>");
@ -158,7 +180,7 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
buffer.append("<TR>");
buffer.append("<TD VALIGN=\"TOP\">");
insertHTMLLine(buffer, "Loaded Actions:", titleAttrSet);
insertHTMLLine(buffer, "Loaded Actions:", titleAttrs);
buffer.append("</TD>");
Set<DockingActionIf> actions = Collections.emptySet();
@ -169,7 +191,7 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
if (actions.isEmpty()) {
buffer.append("<TD VALIGN=\"TOP\">");
insertHTMLLine(buffer, "No actions for plugin", noValueAttrSet);
insertHTMLLine(buffer, "No actions for plugin", noValueAttrs);
buffer.append("</TD>");
buffer.append("</TR>");
return;
@ -182,7 +204,7 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
for (DockingActionIf dockableAction : actions) {
buffer.append("<TR><TD WIDTH=\"200\">");
insertHTMLString(buffer, dockableAction.getName(), locAttrSet);
insertHTMLString(buffer, dockableAction.getName(), locationAttrs);
buffer.append("</TD>");
buffer.append("<TD WIDTH=\"300\">");
@ -190,17 +212,17 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
String[] menuPath = menuBarData == null ? null : menuBarData.getMenuPath();
String menuPathString = createStringForMenuPath(menuPath);
if (menuPathString != null) {
insertHTMLString(buffer, menuPathString, locAttrSet);
insertHTMLString(buffer, menuPathString, locationAttrs);
}
else {
MenuData popupMenuData = dockableAction.getPopupMenuData();
String[] popupPath = popupMenuData == null ? null : popupMenuData.getMenuPath();
if (popupPath != null) {
insertHTMLString(buffer, "(in a context popup menu)", noValueAttrSet);
insertHTMLString(buffer, "(in a context popup menu)", noValueAttrs);
}
else {
insertHTMLString(buffer, "Not in a menu", noValueAttrSet);
insertHTMLString(buffer, "Not in a menu", noValueAttrs);
}
}
@ -210,10 +232,10 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
KeyStroke keyBinding = dockableAction.getKeyBinding();
if (keyBinding != null) {
String keyStrokeString = KeyBindingUtils.parseKeyStroke(keyBinding);
insertHTMLString(buffer, keyStrokeString, locAttrSet);
insertHTMLString(buffer, keyStrokeString, locationAttrs);
}
else {
insertHTMLString(buffer, "No keybinding", noValueAttrSet);
insertHTMLString(buffer, "No keybinding", noValueAttrs);
}
buffer.append("</TD></TR>");
@ -242,74 +264,19 @@ class PluginDetailsPanel extends AbstractDetailsPanel {
@Override
protected void createFieldAttributes() {
titleAttrSet = new SimpleAttributeSet();
titleAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
titleAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
titleAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
titleAttrSet.addAttribute(StyleConstants.Foreground,
new GColor("color.fg.pluginpanel.details.title"));
Font font = Gui.getFont(FONT_DEFAULT);
titleAttrs = new GAttributes(font, TITLE_COLOR);
nameAttrs = new GAttributes(font, NAME_NO_DEPENDENTS_COLOR);
dependenciesNameAttrs = new GAttributes(font, NAME_DEPENDENTS_COLOR);
descriptionAttrs = new GAttributes(font, DESCRIPTION_COLOR);
categoriesAttrs = new GAttributes(font, CATEGORIES_COLOR);
locationAttrs = new GAttributes(font, LOCATION_COLOR);
developerAttrs = new GAttributes(font, DEVELOPER_COLOR);
nameAttrSet = new SimpleAttributeSet();
nameAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
nameAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
nameAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
nameAttrSet.addAttribute(StyleConstants.Foreground,
new GColor("color.fg.pluginpanel.details.name.no.dependents"));
Font fontMonospaced = Gui.getFont(FONT_MONOSPACED);
classAttrs = new GAttributes(fontMonospaced, CLASS_COLOR);
dependencyAttrs = new GAttributes(fontMonospaced, DEPENDENCY_COLOR);
depNameAttrSet = new SimpleAttributeSet();
depNameAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
depNameAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
depNameAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
depNameAttrSet.addAttribute(StyleConstants.Foreground,
new GColor("color.fg.pluginpanel.details.name.has.dependents"));
descrAttrSet = new SimpleAttributeSet();
descrAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
descrAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
descrAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
descrAttrSet.addAttribute(StyleConstants.Foreground,
new GColor("color.fg.pluginpanel.details.description"));
categoriesAttrSet = new SimpleAttributeSet();
categoriesAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
categoriesAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
categoriesAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
categoriesAttrSet.addAttribute(StyleConstants.Foreground,
new GColor("color.fg.pluginpanel.details.category"));
classAttrSet = new SimpleAttributeSet();
classAttrSet.addAttribute(StyleConstants.FontFamily, "monospaced");
classAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
classAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
classAttrSet.addAttribute(StyleConstants.Foreground,
new GColor("color.fg.pluginpanel.details.class"));
locAttrSet = new SimpleAttributeSet();
locAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
locAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
locAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
locAttrSet.addAttribute(StyleConstants.Foreground,
new GColor("color.fg.pluginpanel.details.loc"));
developerAttrSet = new SimpleAttributeSet();
developerAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
developerAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
developerAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
developerAttrSet.addAttribute(StyleConstants.Foreground,
new GColor("color.fg.pluginpanel.details.developer"));
dependencyAttrSet = new SimpleAttributeSet();
dependencyAttrSet.addAttribute(StyleConstants.FontFamily, "monospaced");
dependencyAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
dependencyAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
dependencyAttrSet.addAttribute(StyleConstants.Foreground,
new GColor("color.fg.pluginpanel.details.dependency"));
noValueAttrSet = new SimpleAttributeSet();
noValueAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
noValueAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
noValueAttrSet.addAttribute(StyleConstants.Italic, Boolean.TRUE);
noValueAttrSet.addAttribute(StyleConstants.Foreground,
new GColor("color.fg.pluginpanel.details.novalue"));
noValueAttrs = new GAttributes(font, NO_VALUE_COLOR);
}
}

View file

@ -15,11 +15,14 @@
*/
package ghidra.framework.plugintool.dialog;
import static ghidra.framework.plugintool.dialog.PluginInstallerTableModel.*;
import java.awt.*;
import java.util.List;
import javax.swing.*;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import docking.DialogComponentProvider;
import docking.widgets.table.*;
@ -149,30 +152,23 @@ public class PluginInstallerDialog extends DialogComponentProvider {
tableFilterPanel = new GTableFilterPanel<>(table, tableModel);
JScrollPane sp = new JScrollPane(table);
pluginTablePanel.add(sp, BorderLayout.CENTER);
pluginTablePanel.add(tableFilterPanel, BorderLayout.SOUTH);
// Restrict the size of the first couple columns - the default size is
// way too large. This is annoying but our table column classes don't have a nice
// way to restrict column width.
TableColumn inst_col =
table.getColumnModel().getColumn(PluginInstallerTableModel.INSTALLED_COL);
inst_col.setMaxWidth(30);
TableColumn status_col =
table.getColumnModel().getColumn(PluginInstallerTableModel.STATUS_COL);
status_col.setMaxWidth(24);
TableColumnModel columnModel = table.getColumnModel();
TableColumn installedColumn = columnModel.getColumn(INSTALLED_COL);
installedColumn.setMaxWidth(30);
TableColumn statusColumn = columnModel.getColumn(STATUS_COL);
statusColumn.setMaxWidth(24);
tableModel.setTableSortState(
TableSortState.createDefaultSortState(PluginInstallerTableModel.NAME_COL));
tableModel.setTableSortState(TableSortState.createDefaultSortState(NAME_COL));
tableModel.refresh();
table.getColumnModel()
.getColumn(PluginInstallerTableModel.NAME_COL)
.setCellRenderer(new NameCellRenderer());
table.getColumnModel()
.getColumn(PluginInstallerTableModel.STATUS_COL)
.setCellRenderer(new StatusCellRenderer());
columnModel.getColumn(NAME_COL).setCellRenderer(new NameCellRenderer());
columnModel.getColumn(STATUS_COL).setCellRenderer(new StatusCellRenderer());
HelpService help = Help.getHelpService();
help.registerHelp(table, new HelpLocation(GenericHelpTopics.TOOL, "PluginDialog"));
@ -214,10 +210,10 @@ public class PluginInstallerDialog extends DialogComponentProvider {
renderer.setIcon((value instanceof Icon) ? (Icon) value : null);
String toolTipText = "";
if (value == PluginInstallerTableModel.EXPERIMENTAL_ICON) {
if (value == EXPERIMENTAL_ICON) {
toolTipText = "This plugin is usable, but not fully tested or documented";
}
else if (value == PluginInstallerTableModel.DEV_ICON) {
else if (value == DEV_ICON) {
toolTipText =
"This plugin is under development and not intended for general use.\n" +
"It could cause Ghidra to become unstable!";

View file

@ -176,7 +176,7 @@ public class PluginManagerComponent extends JPanel implements Scrollable {
labelPanel.setBackground(BG);
GLabel nameLabel = new GLabel(pluginPackage.getName());
nameLabel.setFont(nameLabel.getFont().deriveFont(18f));
Gui.registerFont(nameLabel, "font.pluginpanel.name");
nameLabel.setForeground(new GColor("color.fg.pluginpanel.name"));
labelPanel.add(nameLabel);

View file

@ -15,13 +15,11 @@
*/
package ghidra.framework.project.extensions;
import java.awt.Color;
import java.awt.Font;
import java.awt.Point;
import javax.swing.text.SimpleAttributeSet;
import docking.widgets.table.threaded.ThreadedTableModelListener;
import generic.theme.GColor;
import generic.theme.*;
import ghidra.framework.plugintool.dialog.AbstractDetailsPanel;
import ghidra.util.extensions.ExtensionDetails;
@ -33,27 +31,28 @@ import ghidra.util.extensions.ExtensionDetails;
*/
class ExtensionDetailsPanel extends AbstractDetailsPanel {
private static final Color FG_COLOR_AUTHOR =
private static final GColor FG_COLOR_AUTHOR =
new GColor("color.fg.extensionpanel.details.author");
private static final Color FG_COLOR_DATE = new GColor("color.fg.extensionpanel.details.date");
private static final Color FG_COLOR_DESCRIPTION =
private static final GColor FG_COLOR_DATE = new GColor("color.fg.extensionpanel.details.date");
private static final GColor FG_COLOR_DESCRIPTION =
new GColor("color.fg.extensionpanel.details.description");
private static final Color FG_COLOR_NAME = new GColor("color.fg.extensionpanel.details.name");
private static final Color FG_COLOR_PATH = new GColor("color.fg.extensionpanel.path");
private static final Color FG_COLOR_TITLE = new GColor("color.fg.extensionpanel.details.title");
private static final Color FG_COLOR_VERSION =
private static final GColor FG_COLOR_NAME = new GColor("color.fg.extensionpanel.details.name");
private static final GColor FG_COLOR_PATH = new GColor("color.fg.extensionpanel.path");
private static final GColor FG_COLOR_TITLE =
new GColor("color.fg.extensionpanel.details.title");
private static final GColor FG_COLOR_VERSION =
new GColor("color.fg.extensionpanel.details.version");
/** Attribute sets define the visual characteristics for each field */
private SimpleAttributeSet nameAttrSet;
private SimpleAttributeSet descrAttrSet;
private SimpleAttributeSet authorAttrSet;
private SimpleAttributeSet createdOnAttrSet;
private SimpleAttributeSet versionAttrSet;
private SimpleAttributeSet pathAttrSet;
private GAttributes nameAttrSet;
private GAttributes descrAttrSet;
private GAttributes authorAttrSet;
private GAttributes createdOnAttrSet;
private GAttributes versionAttrSet;
private GAttributes pathAttrSet;
private ExtensionDetails currentDetails;
ExtensionDetailsPanel(ExtensionTablePanel tablePanel) {
super();
createFieldAttributes();
createMainPanel();
@ -82,13 +81,19 @@ class ExtensionDetailsPanel extends AbstractDetailsPanel {
});
}
@Override
protected void refresh() {
setDescription(currentDetails);
}
/**
* Updates this panel with the given extension.
*
*
* @param details the extension to display
*/
public void setDescription(ExtensionDetails details) {
this.currentDetails = details;
clear();
if (details == null) {
return;
@ -134,12 +139,14 @@ class ExtensionDetailsPanel extends AbstractDetailsPanel {
@Override
protected void createFieldAttributes() {
titleAttrSet = createAttributeSet(FG_COLOR_TITLE);
nameAttrSet = createAttributeSet(FG_COLOR_NAME);
descrAttrSet = createAttributeSet(FG_COLOR_DESCRIPTION);
authorAttrSet = createAttributeSet(FG_COLOR_AUTHOR);
createdOnAttrSet = createAttributeSet(FG_COLOR_DATE);
versionAttrSet = createAttributeSet(FG_COLOR_VERSION);
pathAttrSet = createAttributeSet(FG_COLOR_PATH);
Font font = Gui.getFont(FONT_DEFAULT);
titleAttrs = new GAttributes(font, FG_COLOR_TITLE);
nameAttrSet = new GAttributes(font, FG_COLOR_NAME);
descrAttrSet = new GAttributes(font, FG_COLOR_DESCRIPTION);
authorAttrSet = new GAttributes(font, FG_COLOR_AUTHOR);
createdOnAttrSet = new GAttributes(font, FG_COLOR_DATE);
versionAttrSet = new GAttributes(font, FG_COLOR_VERSION);
pathAttrSet = new GAttributes(font, FG_COLOR_PATH);
}
}

View file

@ -25,6 +25,7 @@ import docking.util.AnimatedIcon;
import docking.widgets.EmptyBorderButton;
import docking.widgets.label.GDHtmlLabel;
import docking.widgets.label.GIconLabel;
import generic.theme.Gui;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.layout.VerticalLayout;
@ -36,6 +37,8 @@ import resources.ResourceManager;
public class GProgressBar extends JPanel {
private static final NumberFormat PERCENT_FORMAT = NumberFormat.getPercentInstance();
private static final String MESSAGE_FONT_ID = "font.task.progress.label.message";
private volatile long lastProgress = -1;
private volatile long progress;
private volatile long scaleFactor = 1;
@ -47,7 +50,6 @@ public class GProgressBar extends JPanel {
private volatile boolean paintProgressValue = true;
private boolean showingIcon = true;
private final float fontSize;
private JProgressBar progressBar;
private JLabel messageLabel;
private JLabel imageLabel;
@ -61,10 +63,9 @@ public class GProgressBar extends JPanel {
private CancelledListener cancelledListener;
public GProgressBar(CancelledListener cancelledListener, boolean includeTextField,
boolean includeCancelButton, boolean includeAnimatedIcon, float fontSize) {
boolean includeCancelButton, boolean includeAnimatedIcon) {
super(new BorderLayout(5, 1));
this.cancelledListener = cancelledListener;
this.fontSize = fontSize;
buildProgressPanel(includeTextField, includeCancelButton, includeAnimatedIcon);
@ -217,7 +218,7 @@ public class GProgressBar extends JPanel {
// don't care
}
};
messageLabel.setFont(messageLabel.getFont().deriveFont(fontSize));
Gui.registerFont(messageLabel, MESSAGE_FONT_ID);
Dimension d = messageLabel.getPreferredSize();
d.width = 180;
messageLabel.setPreferredSize(d);

View file

@ -31,7 +31,6 @@ public class ScheduledTaskPanel extends JPanel {
private ScheduledElementLayout layout;
public ScheduledTaskPanel(String labelText, int indention) {
super();
this.indention = indention;
setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
@ -44,7 +43,7 @@ public class ScheduledTaskPanel extends JPanel {
}
void addProgressBar() {
progressBar = new GProgressBar(null, true, true, false, 12);
progressBar = new GProgressBar(null, true, true, false);
progressBar.setBackgroundColor(Colors.BACKGROUND);
add(progressBar);
layout.clearPreferredSize();
@ -71,7 +70,7 @@ public class ScheduledTaskPanel extends JPanel {
//==================================================================================================
// Inner Classes
//==================================================================================================
//==================================================================================================
// This layout handles the scrolling based on the scrollOffset as set by the setHiddenViewAmount()
// It also optionally shows the scrollbar for the task or group.

View file

@ -15,8 +15,7 @@
*/
package ghidra.framework.task;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@ -46,11 +45,11 @@ public class GProgressBarTest extends AbstractDockingTest {
cancelled = true;
}
};
progressBar = new GProgressBar(cancelledListener, true, true, true, 10.0f);
progressBar = new GProgressBar(cancelledListener, true, true, true);
}
@Test
public void testBasicProgress() {
public void testBasicProgress() {
progressBar.initialize(100);
assertEquals(0, progressBar.getProgress());
assertEquals(100, progressBar.getMax());
@ -62,7 +61,7 @@ public class GProgressBarTest extends AbstractDockingTest {
}
@Test
public void testLongValues() {
public void testLongValues() {
progressBar.initialize(0x400000000L);
progressBar.setProgress(10);
assertEquals(10, progressBar.getProgress());
@ -73,7 +72,7 @@ public class GProgressBarTest extends AbstractDockingTest {
}
@Test
public void testMessage() {
public void testMessage() {
progressBar.initialize(100);
progressBar.setMessage("Hey");
assertEquals("Hey", progressBar.getMessage());
@ -91,7 +90,7 @@ public class GProgressBarTest extends AbstractDockingTest {
}
@Test
public void testCancel() {
public void testCancel() {
progressBar.initialize(100);
progressBar.setProgress(50);
assertTrue(!cancelled);

View file

@ -100,7 +100,7 @@ public class SystemUtilities {
}
/**
* Clean the specified user name to eliminate any spaces or leading domain name
* Clean the specified user name to eliminate any spaces or leading domain name
* which may be present (e.g., "MyDomain\John Doe" becomes "JohnDoe").
* @param name user name string to be cleaned-up
* @return the clean user name
@ -127,8 +127,8 @@ public class SystemUtilities {
}
/**
* Get the user that is running the application. This name may be modified to
* eliminate any spaces or leading domain name which may be present in Java's
* Get the user that is running the application. This name may be modified to
* eliminate any spaces or leading domain name which may be present in Java's
* {@code user.name} system property (see {@link #getCleanUserName(String)}).
* @return the user name
*/
@ -173,20 +173,15 @@ public class SystemUtilities {
}
/**
* Checks to see if the font size override setting is enabled and adjusts
* the given font as necessary to match the override setting. If the setting
* is not enabled, then <code>font</code> is returned.
* No longer supported. Use the theming system for fonts
*
* @param font
* The current font to adjust, if necessary.
* @return a font object with the proper size.
* @param font the font
* @return the same font passed in
* @deprecated Use the theming system for fonts
*/
@Deprecated(since = "11.1", forRemoval = true)
public static Font adjustForFontSizeOverride(Font font) {
if (FONT_SIZE_OVERRIDE_VALUE == null) {
return font;
}
return font.deriveFont((float) FONT_SIZE_OVERRIDE_VALUE.intValue());
return font;
}
/**
@ -350,10 +345,10 @@ public class SystemUtilities {
}
/**
* Returns a file that contains the given class. If the class is in a jar file, then
* the jar file will be returned. If the file is in a .class file, then the directory
* Returns a file that contains the given class. If the class is in a jar file, then
* the jar file will be returned. If the file is in a .class file, then the directory
* containing the package root will be returned (i.e. the "bin" directory).
*
*
* @param classObject the class for which to get the location
* @return the containing location
*/

View file

@ -42,12 +42,12 @@ public class ReflectionUtilities {
}
/**
* Locates the field of the name <code>fieldName</code> on the given
* class. If the given class does not contain the field, then this
* method will recursively call up <code>containingClass</code>'s
* implementation tree looking for a parent implementation of the
* Locates the field of the name <code>fieldName</code> on the given
* class. If the given class does not contain the field, then this
* method will recursively call up <code>containingClass</code>'s
* implementation tree looking for a parent implementation of the
* requested field.
*
*
* @param fieldName The name of the field to locate.
* @param containingClass The class that contains the desired field.
* @return The Field object that matches the given name, or null if not
@ -73,12 +73,12 @@ public class ReflectionUtilities {
}
/**
* Locates the field of the name <code>fieldName</code> on the given
* class. If the given class does not contain the field, then this
* method will recursively call up <code>containingClass</code>'s
* implementation tree looking for a parent implementation of the
* Locates the field of the name <code>fieldName</code> on the given
* class. If the given class does not contain the field, then this
* method will recursively call up <code>containingClass</code>'s
* implementation tree looking for a parent implementation of the
* requested field.
*
*
* @param fieldName The name of the field to locate.
* @param containingClass The class that contains the desired field.
* @return The Field object that matches the given name, or null if not
@ -104,12 +104,12 @@ public class ReflectionUtilities {
}
/**
* Locates the method of the name <code>methodName</code> on the given
* class. If the given class does not contain the method, then this
* method will recursively call up <code>containingClass</code>'s
* implementation tree looking for a parent implementation of the
* Locates the method of the name <code>methodName</code> on the given
* class. If the given class does not contain the method, then this
* method will recursively call up <code>containingClass</code>'s
* implementation tree looking for a parent implementation of the
* requested method.
*
*
* @param methodName The name of the method to locate.
* @param containingClass The class that contains the desired method.
* @param parameterTypes The parameters of the desired method (may be null).
@ -158,7 +158,7 @@ public class ReflectionUtilities {
/**
* Get the first field specification contained within containingClass which has the type classType.
* This method is only really useful if it is known that only a single field of
* This method is only really useful if it is known that only a single field of
* classType exists within the containingClass hierarchy.
* @param classType the class
* @param containingClass the class that contains a field of the given type
@ -184,40 +184,52 @@ public class ReflectionUtilities {
/**
* Returns the class name of the entry in the stack that comes before all references to the
* given classes. This is useful for figuring out at runtime who is calling a particular
* method.
* method.
* <p>
* This method can take multiple classes, but you really only need to pass the oldest
* This method can take multiple classes, but you really only need to pass the oldest
* class of disinterest.
*
*
* @param classes the classes to ignore
* @return the desired class name
*/
public static String getClassNameOlderThan(Class<?>... classes) {
Throwable t = createThrowableWithStackOlderThan(classes);
StackTraceElement[] stackTrace = t.getStackTrace();
return stackTrace[0].getClassName();
}
/**
* Creates a throwable whose stack trace is based upon the current call stack, with any
* information coming before, and including, the given classes removed.
* <p>
* This method can take multiple classes, but you really only need to pass the oldest
* class of disinterest.
*
* @param classes the classes to ignore
* @return the new throwable
* Returns the class name of the entry in the stack that comes before all references to the
* given patterns. This is useful for figuring out at runtime who is calling a particular
* method.
*
* @param patterns the patterns to ignore
* @return the desired class name
*/
public static Throwable createThrowableWithStackOlderThan(Class<?>... classes) {
public static String getClassNameOlderThan(String... patterns) {
Throwable t = createThrowableWithStackOlderThan(patterns);
StackTraceElement[] stackTrace = t.getStackTrace();
return stackTrace[0].getClassName();
}
List<String> toFind =
Arrays.stream(classes).map(c -> c.getName()).collect(Collectors.toList());
/**
* Creates a throwable whose stack trace is based upon the current call stack, with any
* information coming before, and including, the given patterns removed.
*
* @param patterns the strings to ignore (e.g., class or package names)
* @return the new throwable
* @see #createThrowableWithStackOlderThan(Class...)
*/
public static Throwable createThrowableWithStackOlderThan(String... patterns) {
return createThrowableWithStackOlderThan(List.of(patterns));
}
if (toFind.isEmpty()) {
// Always ignore our class. We get this for free if the client passes in any
// classes.
toFind.add(0, ReflectionUtilities.class.getName());
private static Throwable createThrowableWithStackOlderThan(List<String> patterns) {
if (patterns.isEmpty()) {
// always ignore our class. We get this for free if the client passes in any classes
patterns = new ArrayList<>();
patterns.add(0, ReflectionUtilities.class.getName());
}
Throwable t = new Throwable();
@ -227,7 +239,7 @@ public class ReflectionUtilities {
StackTraceElement element = trace[i];
String className = element.getClassName();
int nameIndex = toFind.indexOf(className);
int nameIndex = patterns.indexOf(className);
if (nameIndex != -1) {
lastIgnoreIndex = i;
}
@ -242,13 +254,13 @@ public class ReflectionUtilities {
if (lastIgnoreIndex == -1) {
Msg.error(ReflectionUtilities.class,
"Change call to ReflectionUtils. Did not find the " +
"following classes in the call stack: " + Arrays.toString(classes));
"following patterns in the call stack: " + patterns);
}
if (lastIgnoreIndex == trace.length - 1) {
Msg.error(ReflectionUtilities.class,
"Change call to ReflectionUtils. Call stack only contains the classes to ignore: " +
Arrays.toString(classes));
"Change call to ReflectionUtils. Call stack contains only ignored patterns: " +
patterns);
}
int startIndex = lastIgnoreIndex + 1;
@ -258,11 +270,27 @@ public class ReflectionUtilities {
}
/**
* Finds the first occurrence of the given pattern and then stops filtering when it finds
* Creates a throwable whose stack trace is based upon the current call stack, with any
* information coming before, and including, the given classes removed.
* <p>
* This method can take multiple classes, but you really only need to pass the oldest
* class of disinterest.
*
* @param classes the classes to ignore
* @return the new throwable
*/
public static Throwable createThrowableWithStackOlderThan(Class<?>... classes) {
List<String> patterns =
Arrays.stream(classes).map(c -> c.getName()).collect(Collectors.toList());
return createThrowableWithStackOlderThan(patterns);
}
/**
* Finds the first occurrence of the given pattern and then stops filtering when it finds
* something that is not that pattern
*
*
* @param trace the trace to update
* @param pattern the non-regex patterns used to perform a
* @param pattern the non-regex patterns used to perform a
* {@link String#contains(CharSequence)} on each {@link StackTraceElement} line
* @return the updated trace
*/
@ -296,12 +324,12 @@ public class ReflectionUtilities {
}
/**
* Uses the given <code>patterns</code> to remove elements from the given stack trace.
* Uses the given <code>patterns</code> to remove elements from the given stack trace.
* The current implementation will simply perform a <code>toString()</code> on each element and
* then check to see if that string contains any of the <code>patterns</code>.
*
*
* @param trace the trace to filter
* @param patterns the non-regex patterns used to perform a
* @param patterns the non-regex patterns used to perform a
* {@link String#contains(CharSequence)} on each {@link StackTraceElement}
* line.
* @return the filtered trace
@ -325,15 +353,15 @@ public class ReflectionUtilities {
/**
* A convenience method to create a throwable, filtering any lines that contain the given
* non-regex patterns. This can be useful for emitting diagnostic stack traces.
*
* @param patterns the non-regex patterns used to perform a
*
* @param patterns the non-regex patterns used to perform a
* {@link String#contains(CharSequence)} on each {@link StackTraceElement}
* line.
* @return the new throwable
*/
public static Throwable createFilteredThrowable(String... patterns) {
Throwable t = createThrowableWithStackOlderThan();
Throwable t = createThrowableWithStackOlderThan(new ArrayList<>());
StackTraceElement[] trace = t.getStackTrace();
StackTraceElement[] filtered = filterStackTrace(trace, patterns);
t.setStackTrace(filtered);
@ -341,40 +369,39 @@ public class ReflectionUtilities {
}
/**
* A convenience method to create a throwable, filtering boiler-plate Java-related
* lines (e.g., AWT, Swing, Security, etc).
* A convenience method to create a throwable, filtering boiler-plate Java-related
* lines (e.g., AWT, Swing, Security, etc).
* This can be useful for emitting diagnostic stack traces with reduced noise.
*
*
* @return the new throwable
*/
public static Throwable createJavaFilteredThrowable() {
Throwable t = createThrowableWithStackOlderThan();
Throwable t = createThrowableWithStackOlderThan(List.of());
return filterJavaThrowable(t);
}
/**
* A convenience method to create a throwable, filtering boiler-plate Java-related
* lines (e.g., AWT, Swing, Security, etc).
* This can be useful for emitting diagnostic stack traces with reduced noise.
*
* A convenience method to create a throwable, filtering boiler-plate Java-related
* lines (e.g., AWT, Swing, Security, etc).
* This can be useful for emitting diagnostic stack traces with reduced noise.
*
* <p>This method differs from {@link #createJavaFilteredThrowable()} in that this method
* returns a String, which is useful when printing log messages without having to directly
* print the stack trace.
*
*
* @return the new throwable
*/
public static String createJavaFilteredThrowableString() {
Throwable t = createThrowableWithStackOlderThan();
Throwable t = createThrowableWithStackOlderThan(List.of());
Throwable filtered = filterJavaThrowable(t);
return stackTraceToString(filtered);
}
/**
* A convenience method to take a throwable, filter boiler-plate Java-related
* lines (e.g., AWT, Swing, Security, etc).
* A convenience method to take a throwable, filter boiler-plate Java-related
* lines (e.g., AWT, Swing, Security, etc).
* This can be useful for emitting diagnostic stack traces with reduced noise.
*
*
* @param t the throwable to filter
* @return the throwable
*/
@ -418,15 +445,15 @@ public class ReflectionUtilities {
}
/**
* Returns an ordered set of interfaces and classes that are shared amongst the items in
* Returns an ordered set of interfaces and classes that are shared amongst the items in
* the list.
* <p>
* The order of the items is as they are first encountered, favoring interfaces before
* The order of the items is as they are first encountered, favoring interfaces before
* classes. Further, interface hierarchies are examined before concrete parent extensions.
* <p>
* If the given items have no parents in common, then the result will be a list with
* only <code>Object.class</code>.
*
*
* @param list the items to examine
* @return the set of items
*/
@ -461,15 +488,15 @@ public class ReflectionUtilities {
}
/**
* Returns an ordered set of parent interfaces and classes that are shared
* Returns an ordered set of parent interfaces and classes that are shared
* amongst the items in the list.
* <p>
* The order of the items is as they are first encountered, favoring interfaces before
* The order of the items is as they are first encountered, favoring interfaces before
* classes. Further, interface hierarchies are examined before concrete parent extensions.
* <p>
* If the given items have no parents in common, then the result will be a list with
* only <code>Object.class</code>.
*
*
* @param list the items to examine
* @return the set of items
*/
@ -494,9 +521,9 @@ public class ReflectionUtilities {
}
/**
* Turns the given {@link Throwable} into a String version of its
* Turns the given {@link Throwable} into a String version of its
* {@link Throwable#printStackTrace()} method.
*
*
* @param t the throwable
* @return the string
*/
@ -505,9 +532,9 @@ public class ReflectionUtilities {
}
/**
* Turns the given {@link Throwable} into a String version of its
* Turns the given {@link Throwable} into a String version of its
* {@link Throwable#printStackTrace()} method.
*
*
* @param message the preferred message to use. If null, the throwable message will be used
* @param t the throwable
* @return the string
@ -543,11 +570,11 @@ public class ReflectionUtilities {
/**
* Returns an order set of all interfaces implemented and classes extended for the entire
* type structure of the given class.
* type structure of the given class.
* <p>
* If <code>Object.class</code> is passed to this method, then it will be returned in the
* If <code>Object.class</code> is passed to this method, then it will be returned in the
* result of this method.
*
*
* @param c the class to introspect
* @return the set of parents
*/
@ -581,7 +608,7 @@ public class ReflectionUtilities {
/**
* Returns the type arguments for the given base class and extension.
*
*
* <p>Caveat: this lookup will only work if the given child class is a concrete class that
* has its type arguments specified. For example, these cases will work:
* <pre>
@ -592,17 +619,17 @@ public class ReflectionUtilities {
*
* // class definition
* public class MyList implements List&lt;String&gt; {
* </pre>
*
* </pre>
*
* Whereas this case will not work:
* <pre>
* // local variable with the type specified
* List&lt;String&gt; myList = new ArrayList&lt;String&gt;();
* </pre>
*
*
* <p>Note: a null entry in the result list will exist for any type that was unrecoverable
*
*
*
*
* @param <T> the type of the base and child class
* @param baseClass the base class
* @param childClass the child class
@ -618,7 +645,7 @@ public class ReflectionUtilities {
Type baseClassAsType =
walkClassHierarchyAndResolveTypes(baseClass, resolvedTypesDictionary, childClass);
// try to resolve type arguments defined by 'baseClass' to the raw runtime class
// try to resolve type arguments defined by 'baseClass' to the raw runtime class
Type[] baseClassDeclaredTypeArguments = getDeclaredTypeArguments(baseClassAsType);
return resolveBaseClassTypeArguments(resolvedTypesDictionary,
baseClassDeclaredTypeArguments);