diff --git a/Ghidra/Framework/Docking/data/docking.theme.properties b/Ghidra/Framework/Docking/data/docking.theme.properties index 6ae7b334bd..fc6bf37645 100644 --- a/Ghidra/Framework/Docking/data/docking.theme.properties +++ b/Ghidra/Framework/Docking/data/docking.theme.properties @@ -165,6 +165,7 @@ 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.fixed.width = font.table.base[monospaced] font.table.header.number = arial-bold-12 font.task.monitor.label.message = sansserif-plain-10 diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeFontTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeFontTableModel.java index a9559f04b0..5a73543303 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeFontTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeFontTableModel.java @@ -4,9 +4,9 @@ * 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. @@ -141,7 +141,7 @@ public class ThemeFontTableModel extends GDynamicColumnTableModel"; } if (resolvedFont.refId() != null) { - return "[" + resolvedFont.refId() + "]"; + return resolvedFont.getValueText(); } Font font = resolvedFont.font(); @@ -193,7 +193,8 @@ public class ThemeFontTableModel extends GDynamicColumnTableModel { @Override protected Font getUnresolvedReferenceValue(String primaryId, String unresolvedId) { - Msg.warn(this, - "Could not resolve indirect font path for \"" + unresolvedId + - "\" for primary id \"" + primaryId + "\", using last resort default"); + Msg.warn(this, "Could not resolve indirect font path for \"" + unresolvedId + + "\" for primary id \"" + primaryId + "\", using last resort default"); return LAST_RESORT_DEFAULT; } + public FontModifier getModifier() { + return modifier; + } + private static String toExternalId(String internalId) { if (internalId.startsWith(FONT_ID_PREFIX)) { return internalId; diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/ComponentFontRegistry.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/ComponentFontRegistry.java index 6387fd966e..615db976ad 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/ComponentFontRegistry.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/ComponentFontRegistry.java @@ -17,20 +17,20 @@ package generic.theme.laf; import java.awt.Component; import java.awt.Font; -import java.util.Iterator; -import java.util.Objects; +import java.lang.ref.WeakReference; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import generic.theme.Gui; -import ghidra.util.datastruct.WeakDataStructureFactory; -import ghidra.util.datastruct.WeakSet; /** * Maintains a weak set of components associated with a given font id. Whenever the font changes * for the font id, this class will update the component's font to the new value. */ public class ComponentFontRegistry { - private WeakSet components = - WeakDataStructureFactory.createCopyOnReadWeakSet(); + + // use a thread-safe set that allows for changes while iterating + private Set components = ConcurrentHashMap.newKeySet(); private String fontId; /** @@ -69,7 +69,7 @@ public class ComponentFontRegistry { Iterator it = components.iterator(); while (it.hasNext()) { StyledComponent sc = it.next(); - if (component == sc.component) { + if (sc.matches(component)) { it.remove(); break; } @@ -81,17 +81,51 @@ public class ComponentFontRegistry { */ public void updateComponentFonts() { Font font = Gui.getFont(fontId); - for (StyledComponent c : components) { - c.setFont(font); + + List copy = new ArrayList<>(components); + for (StyledComponent c : copy) { + if (!c.isValid()) { + // the component has been garbage collected + components.remove(c); + } + else { + c.setFont(font); + } } } - private record StyledComponent(Component component, int fontStyle) { + /** + * A simple container that holds a font style and the component that uses that style. The + * component is managed using a weak reference to help prevent memory leaks. + */ + private class StyledComponent { + + private int fontStyle; + private WeakReference componentReference; + + StyledComponent(Component component, int fontStyle) { + this.fontStyle = fontStyle; + this.componentReference = new WeakReference<>(component); + } + + boolean matches(Component c) { + return componentReference.refersTo(c); + } + + boolean isValid() { + return !componentReference.refersTo(null); + } void setFont(Font font) { + + Component component = componentReference.get(); + if (component == null) { + return; // garbage collected + } + Font existingFont = component.getFont(); Font styledFont = font; - int style = fontStyle(); + 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 @@ -99,7 +133,14 @@ public class ComponentFontRegistry { styledFont = font.deriveFont(style); } - if (!Objects.equals(existingFont, styledFont)) { + if (Objects.equals(existingFont, styledFont)) { + return; + } + + if (component instanceof FontChangeListener fontListener) { + fontListener.fontChanged(fontId, styledFont); + } + else { component.setFont(styledFont); } } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FontChangeListener.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FontChangeListener.java new file mode 100644 index 0000000000..e3f91f177e --- /dev/null +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FontChangeListener.java @@ -0,0 +1,32 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme.laf; + +import java.awt.Font; + +/** + * A simple interface that signals the client has a font that should be updated when the theme is + * updated. + */ +public interface FontChangeListener { + + /** + * Called when the client should update its font to the given font. + * @param fontId the theme font id being updated + * @param f the font + */ + public void fontChanged(String fontId, Font f); +} diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginInstallerDialog.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginInstallerDialog.java index 56faef6675..b414a8fb6d 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginInstallerDialog.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/dialog/PluginInstallerDialog.java @@ -4,9 +4,9 @@ * 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. @@ -230,8 +230,6 @@ public class PluginInstallerDialog extends DialogComponentProvider { private class NameCellRenderer extends GTableCellRenderer { NameCellRenderer() { - defaultFont = getFont(); - boldFont = defaultFont.deriveFont(defaultFont.getStyle() | Font.BOLD); setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0)); }