diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/DnDTreeCellRenderer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/DnDTreeCellRenderer.java index bc28bf23ab..37ba6e7638 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/DnDTreeCellRenderer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/DnDTreeCellRenderer.java @@ -22,6 +22,7 @@ import java.util.*; import javax.swing.*; import javax.swing.tree.DefaultTreeCellRenderer; +import docking.theme.GColor; import docking.widgets.GComponent; import ghidra.program.model.listing.Group; import resources.ResourceManager; @@ -67,8 +68,8 @@ class DnDTreeCellRenderer extends DefaultTreeCellRenderer { */ DnDTreeCellRenderer() { super(); - defaultNonSelectionColor = getBackgroundNonSelectionColor(); - defaultSelectionColor = getBackgroundSelectionColor(); + defaultNonSelectionColor = new GColor("Tree.textBackground"); + defaultSelectionColor = new GColor("Tree.selectionBackground"); rowForFeedback = -1; // disable HTML rendering diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/gui/ThemeManagerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/gui/ThemeManagerPlugin.java index b17b173f65..0189241c05 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/gui/ThemeManagerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/gui/ThemeManagerPlugin.java @@ -15,32 +15,23 @@ */ package ghidra.app.plugin.gui; -import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import docking.action.builder.ActionBuilder; import docking.options.editor.StringWithChoicesEditor; -import docking.theme.GTheme; -import docking.theme.GThemeDialog; -import docking.theme.Gui; +import docking.theme.*; +import docking.theme.gui.GThemeDialog; import docking.tool.ToolConstants; import ghidra.app.CorePluginPackage; import ghidra.app.plugin.PluginCategoryNames; import ghidra.docking.util.LookAndFeelUtils; import ghidra.framework.main.FrontEndOnly; import ghidra.framework.main.FrontEndTool; -import ghidra.framework.options.OptionType; -import ghidra.framework.options.OptionsChangeListener; -import ghidra.framework.options.ToolOptions; -import ghidra.framework.plugintool.Plugin; -import ghidra.framework.plugintool.PluginInfo; -import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.options.*; +import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.util.PluginStatus; -import ghidra.util.HelpLocation; -import ghidra.util.Msg; -import ghidra.util.SystemUtilities; +import ghidra.util.*; //@formatter:off @PluginInfo( @@ -70,13 +61,11 @@ public class ThemeManagerPlugin extends Plugin implements FrontEndOnly, OptionsC @Override protected void init() { - new ActionBuilder("Dump UI Properties", getName()) - .menuPath("Edit", "Dump UI Properies") + new ActionBuilder("Dump UI Properties", getName()).menuPath("Edit", "Dump UI Properies") .onAction(e -> LookAndFeelUtils.dumpUIProperties()) .buildAndInstall(tool); - new ActionBuilder("Show Properties", getName()) - .menuPath("Edit", "Theme Properties") + new ActionBuilder("Show Properties", getName()).menuPath("Edit", "Theme Properties") .onAction(e -> showThemeProperties()) .buildAndInstall(tool); @@ -92,7 +81,7 @@ public class ThemeManagerPlugin extends Plugin implements FrontEndOnly, OptionsC ToolOptions opt = tool.getOptions(OPTIONS_TITLE); GTheme activeTheme = Gui.getActiveTheme(); - List themeNames = getAllThemeNames(); + List themeNames = Gui.getAllThemeNames(); opt.registerOption(THEME_OPTIONS_NAME, OptionType.STRING_TYPE, activeTheme.getName(), new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, "Look_And_Feel"), @@ -103,14 +92,6 @@ public class ThemeManagerPlugin extends Plugin implements FrontEndOnly, OptionsC opt.addOptionsChangeListener(this); } - private List getAllThemeNames() { - Set allThemes = Gui.getAllThemes(); - List themeNames = - allThemes.stream().map(t -> t.getName()).collect(Collectors.toList()); - Collections.sort(themeNames); - return themeNames; - } - @Override public void optionsChanged(ToolOptions options, String optionName, Object oldValue, Object newValue) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/ColorValue.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/ColorValue.java index 079daa8bb8..b8471f1228 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/ColorValue.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/ColorValue.java @@ -27,7 +27,7 @@ public class ColorValue extends ThemeValue { public static final Color LAST_RESORT_DEFAULT = Color.GRAY; public ColorValue(String id, Color value) { - super(id, null, value); + super(id, getRefId(value), getRawColor(value)); } public ColorValue(String id, String refId) { @@ -96,4 +96,19 @@ public class ColorValue extends ThemeValue { float[] hsb = Color.RGBtoHSB(v.getRed(), v.getGreen(), v.getBlue(), null); return 100 * (int) (10 * hsb[0]) + 10 * (int) (10 * hsb[1]) + (int) (10 * hsb[2]); } + + private static Color getRawColor(Color value) { + if (value instanceof GColor) { + return null; + } + return value; + } + + private static String getRefId(Color value) { + if (value instanceof GColor) { + return ((GColor) value).getId(); + } + return null; + } + } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/GColor.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/GColor.java index 113052e6f4..8d812d7d2d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/GColor.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/GColor.java @@ -20,35 +20,41 @@ import java.awt.color.ColorSpace; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.ColorModel; +import java.util.Objects; -import ghidra.util.datastruct.WeakDataStructureFactory; -import ghidra.util.datastruct.WeakSet; +import ghidra.util.datastruct.WeakStore; public class GColor extends Color implements Refreshable { - private static WeakSet inUseColors = WeakDataStructureFactory.createCopyOnReadWeakSet(); + private static WeakStore inUseColors = new WeakStore<>(); private String id; private Color delegate; public static void refreshAll() { - for (GColor gcolor : inUseColors) { + for (GColor gcolor : inUseColors.getValues()) { gcolor.refresh(); } } public GColor(String id) { + this(id, true); + } + + public GColor(String id, boolean validate) { super(0x808080); this.id = id; - delegate = Gui.getRawColor(id); - if (delegate == null) { - delegate = Color.gray; - } + delegate = Gui.getRawColor(id, validate); inUseColors.add(this); + } public String getId() { return id; } + public boolean isEquivalent(Color color) { + return delegate.getRGB() == color.getRGB(); + } + @Override public int getRed() { return delegate.getRed(); @@ -86,12 +92,7 @@ public class GColor extends Color implements Refreshable { @Override public int hashCode() { - return delegate.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return delegate.equals(obj); + return id.hashCode(); } @Override @@ -99,6 +100,21 @@ public class GColor extends Color implements Refreshable { return getClass().getName() + " [id = " + id + ", " + delegate.toString() + "]"; } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + GColor other = (GColor) obj; + return Objects.equals(id, other.id); + } + @Override public float[] getRGBComponents(float[] compArray) { return delegate.getRGBComponents(compArray); @@ -147,9 +163,12 @@ public class GColor extends Color implements Refreshable { @Override public void refresh() { - Color color = Gui.getRawColor(id); + Color color = Gui.getRawColor(id, false); if (color != null) { delegate = color; } + else { + System.out.println("Hey"); + } } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/GColorUIResource.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/GColorUIResource.java new file mode 100644 index 0000000000..b4343d2a73 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/GColorUIResource.java @@ -0,0 +1,26 @@ +/* ### + * 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.theme; + +import javax.swing.plaf.UIResource; + +public class GColorUIResource extends GColor implements UIResource { + + public GColorUIResource(String id) { + super(id); + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/GFont.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/GFont.java index edbe39d54c..3eb7b2eae5 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/GFont.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/GFont.java @@ -21,8 +21,7 @@ import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.text.AttributedCharacterIterator.Attribute; import java.text.CharacterIterator; -import java.util.Locale; -import java.util.Map; +import java.util.*; public class GFont extends Font implements Refreshable { @@ -38,6 +37,10 @@ public class GFont extends Font implements Refreshable { } } + public boolean isEquivalent(Font font) { + return delegate.equals(font); + } + public String getId() { return id; } @@ -128,12 +131,22 @@ public class GFont extends Font implements Refreshable { @Override public int hashCode() { - return delegate.hashCode(); + return id.hashCode(); } @Override public boolean equals(Object obj) { - return delegate.equals(obj); + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + GFont other = (GFont) obj; + return Objects.equals(id, other.id); } @Override diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/GIcon.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/GIcon.java index d1ff95720f..f17adc03dc 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/GIcon.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/GIcon.java @@ -62,4 +62,24 @@ public class GIcon implements Icon, Refreshable { } } + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + GIcon other = (GIcon) obj; + return id.equals(other.id); + } + } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/GTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/GTheme.java index 46fb8ea3e4..630129c58c 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/GTheme.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/GTheme.java @@ -200,7 +200,7 @@ public class GTheme extends GThemeValueMap { List fonts = values.getFonts(); Collections.sort(fonts); - List icons = values.getIconPaths(); + List icons = values.getIcons(); Collections.sort(icons); writer.write(THEME_NAME_KEY + " = " + name); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeDialog.java deleted file mode 100644 index b4718b3ab9..0000000000 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeDialog.java +++ /dev/null @@ -1,89 +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.theme; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.io.File; -import java.io.IOException; - -import javax.swing.*; - -import docking.DialogComponentProvider; -import docking.widgets.filechooser.GhidraFileChooser; -import docking.widgets.filechooser.GhidraFileChooserMode; -import docking.widgets.table.GFilterTable; -import docking.widgets.table.GTable; -import ghidra.util.filechooser.GhidraFileFilter; - -public class GThemeDialog extends DialogComponentProvider { - - public GThemeDialog() { - super("Theme Dialog", false); - addWorkPanel(createMainPanel()); - addOKButton(); - addCancelButton(); - setOkButtonText("Save"); - - } - - @Override - protected void okCallback() { - GhidraFileChooser chooser = new GhidraFileChooser(getComponent()); - chooser.setTitle("Choose Theme File"); - chooser.setApproveButtonText("Select Output File"); - chooser.setApproveButtonToolTipText("Select File"); - chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY); - chooser.setSelectedFileFilter(GhidraFileFilter.ALL); - File file = chooser.getSelectedFile(); - try { - Gui.getActiveTheme().saveToFile(file, Gui.getAllDefaultValues()); - } - catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - private JComponent createMainPanel() { - JPanel panel = new JPanel(); - - panel.setLayout(new BorderLayout()); - - panel.add(buildTabedTables()); - return panel; - } - - private Component buildTabedTables() { - JTabbedPane tabbedPane = new JTabbedPane(); - tabbedPane.add("Colors", buildColorTable()); - return tabbedPane; - } - - private JComponent buildColorTable() { - ThemeColorTableModel colorTableModel = new ThemeColorTableModel(Gui.getActiveTheme()); - - GTable colorTable = new GTable(colorTableModel); - colorTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - GFilterTable filterTable = new GFilterTable<>(colorTableModel); - filterTable.getTable().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - filterTable.getTable() - .setDefaultEditor(ColorValue.class, - new ThemeColorEditor(Gui.getAllValues(), colorTableModel)); - return filterTable; - } - -} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeValueMap.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeValueMap.java index 0f8cde7992..b6dfca0982 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeValueMap.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/GThemeValueMap.java @@ -74,7 +74,7 @@ public class GThemeValueMap { return new ArrayList<>(fontMap.values()); } - public List getIconPaths() { + public List getIcons() { return new ArrayList<>(iconMap.values()); } @@ -93,4 +93,10 @@ public class GThemeValueMap { public Object size() { return colorMap.size() + fontMap.size() + iconMap.size(); } + + public void clear() { + colorMap.clear(); + fontMap.clear(); + iconMap.clear(); + } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/Gui.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/Gui.java index 6fdaed71e2..454ca05583 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/Gui.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/Gui.java @@ -19,8 +19,10 @@ import java.awt.*; import java.io.*; import java.util.*; import java.util.List; +import java.util.stream.Collectors; import javax.swing.*; +import javax.swing.plaf.UIResource; import docking.framework.ApplicationInformationDisplayFactory; import ghidra.docking.util.LookAndFeelUtils; @@ -42,13 +44,18 @@ public class Gui { private static Set allThemes; private static GThemeValueMap ghidraCoreDefaults = new GThemeValueMap(); - private static GThemeValueMap javaDefaults; + private static GThemeValueMap originalJavaDefaults; +// private static GThemeValueMap convertedJavaDefaults; private static GThemeValueMap currentValues = new GThemeValueMap(); private static GThemeValueMap darkDefaults = new GThemeValueMap(); private static ThemePropertiesLoader themePropertiesLoader = new ThemePropertiesLoader(); + private static Map gColorMap = new HashMap<>(); + + private static JPanel jPanel; + static void setPropertiesLoader(ThemePropertiesLoader loader) { themePropertiesLoader = loader; } @@ -58,7 +65,6 @@ public class Gui { } public static void initialize() { - themePropertiesLoader.initialize(); loadThemeDefaults(); setTheme(getThemeFromPreferences()); LookAndFeelUtils.installGlobalOverrides(); @@ -66,28 +72,60 @@ public class Gui { } private static void loadThemeDefaults() { + themePropertiesLoader.load(); ghidraCoreDefaults = themePropertiesLoader.getDefaults(); darkDefaults = themePropertiesLoader.getDarkDefaults(); } + public static void reloadThemeDefaults() { + loadThemeDefaults(); + currentValues = buildCurrentValues(activeTheme); + refresh(); + } + public static void setTheme(GTheme theme) { activeTheme = theme; LookAndFeelUtils.setLookAndFeel(theme.getLookAndFeelName()); - javaDefaults = mineJavaDefaults(); - currentValues = buildCurrentValues(theme); - installBackIntoJava(); + refresh(); } - private static void installBackIntoJava() { - UIDefaults defaults = UIManager.getDefaults(); - for (ColorValue color : javaDefaults.getColors()) { - String id = color.getId(); - defaults.put(id, new GColor(id)); + private static void refresh() { + GColor.refreshAll(); + for (Window window : Window.getWindows()) { + SwingUtilities.updateComponentTreeUI(window); } } +// private static GThemeValueMap convertJavaDefaults(GThemeValueMap input) { +// GThemeValueMap converted = new GThemeValueMap(); +// for (ColorValue colorValue : input.getColors()) { +// converted.addColor(fromUiResource(colorValue)); +// } +// for (FontValue fontValue : input.getFonts()) { +// converted.addFont(fromUiResource(fontValue)); +// } +// // java icons are not currently supported +// return converted; +// } + + private static FontValue fromUiResource(FontValue fontValue) { + Font font = fontValue.getRawValue(); + if (font instanceof UIResource) { + return new FontValue(fontValue.getId(), font.deriveFont(font.getStyle())); + } + return fontValue; + } + + private static ColorValue fromUiResource(ColorValue colorValue) { + Color color = colorValue.getRawValue(); + if (color instanceof UIResource) { + return new ColorValue(colorValue.getId(), new Color(color.getRGB(), true)); + } + return colorValue; + } + public static boolean isJavaDefinedColor(String id) { - return javaDefaults.containsColor(id); + return originalJavaDefaults.containsColor(id); } public static GThemeValueMap getAllValues() { @@ -96,7 +134,7 @@ public class Gui { public static GThemeValueMap getAllDefaultValues() { GThemeValueMap currentDefaults = new GThemeValueMap(); - currentDefaults.load(javaDefaults); + currentDefaults.load(originalJavaDefaults); currentDefaults.load(ghidraCoreDefaults); if (activeTheme.isDark()) { currentDefaults.load(darkDefaults); @@ -111,6 +149,19 @@ public class Gui { return Collections.unmodifiableSet(allThemes); } + public static GTheme getTheme(String themeName) { + Optional first = + getAllThemes().stream().filter(t -> t.getName().equals(themeName)).findFirst(); + return first.get(); + } + + public static List getAllThemeNames() { + List themeNames = + getAllThemes().stream().map(t -> t.getName()).collect(Collectors.toList()); + Collections.sort(themeNames); + return themeNames; + } + public static Color darker(Color color) { if (activeTheme.isDark()) { return color.brighter(); @@ -157,13 +208,19 @@ public class Gui { } } - static Color getRawColor(String id) { - ColorValue color = currentValues.getColor(id); - if (color == null) { - Throwable t = getFilteredTrace(); + public static Color getRawColor(String id) { + return getRawColor(id, true); + } - Msg.error(Gui.class, "No color value registered for: " + id, t); - return null; + static Color getRawColor(String id, boolean validate) { + ColorValue color = currentValues.getColor(id); + + if (color == null) { + if (validate) { + // Throwable t = getFilteredTrace(); + Msg.error(Gui.class, "No color value registered for: " + id); + } + return Color.CYAN; } return color.get(currentValues); } @@ -203,7 +260,7 @@ public class Gui { private static GThemeValueMap buildCurrentValues(GTheme theme) { GThemeValueMap map = new GThemeValueMap(); - map.load(javaDefaults); + map.load(originalJavaDefaults); map.load(ghidraCoreDefaults); if (theme.isDark()) { map.load(darkDefaults); @@ -212,18 +269,13 @@ public class Gui { return map; } - private static GThemeValueMap mineJavaDefaults() { - GThemeValueMap values = new GThemeValueMap(); - // for now, just doing color properties. - List ids = LookAndFeelUtils.getLookAndFeelIdsForType(Color.class); - for (String id : ids) { - // Create a new color to ensure we are not storing a UIResource; otherwise java - // java ignore the color because the UI widgets take liberties when UIResources - // are being used. - Color lafColor = new Color(UIManager.getColor(id).getRGB(), true); - values.addColor(new ColorValue(id, lafColor)); + private static Color getUIColor(String id) { + // Not sure, but for now, make sure colors are not UIResource + Color color = UIManager.getColor(id); + if (color instanceof UIResource) { + return new Color(color.getRGB(), true); } - return values; + return color; } private static Set findThemes() { @@ -297,7 +349,7 @@ public class Gui { public static GThemeValueMap getCoreDefaults() { GThemeValueMap map = new GThemeValueMap(ghidraCoreDefaults); - map.load(javaDefaults); + map.load(originalJavaDefaults); return map; } @@ -308,9 +360,34 @@ public class Gui { } public static void setColor(String id, Color color) { - currentValues.addColor(new ColorValue(id, color)); - GColor.refreshAll(); + setColor(new ColorValue(id, color)); + } + public static void setColor(ColorValue colorValue) { + currentValues.addColor(colorValue); + System.out.println("Change color: " + colorValue); + GColor.refreshAll(); + for (Window window : Window.getWindows()) { + window.repaint(); + } + } + + public static GColorUIResource getGColorUiResource(String id) { + GColorUIResource gColor = gColorMap.get(id); + if (gColor == null) { + gColor = new GColorUIResource(id); + gColorMap.put(id, gColor); + } + return gColor; + } + + public static void setJavaDefaults(GThemeValueMap javaDefaults) { + originalJavaDefaults = javaDefaults; + currentValues = buildCurrentValues(activeTheme); + } + + public static GThemeValueMap getJavaDefaults() { + return originalJavaDefaults; } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeColorEditor.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeColorEditor.java deleted file mode 100644 index 553d31749b..0000000000 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeColorEditor.java +++ /dev/null @@ -1,165 +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.theme; - -import java.awt.*; -import java.awt.event.MouseEvent; -import java.util.EventObject; - -import javax.swing.*; -import javax.swing.border.BevelBorder; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.table.TableCellEditor; - -import docking.DialogComponentProvider; -import docking.DockingWindowManager; -import docking.options.editor.GhidraColorChooser; -import docking.widgets.label.GDLabel; - -public class ThemeColorEditor extends AbstractCellEditor implements TableCellEditor { - private GhidraColorChooser colorChooser; - private Color lastUserSelectedColor; - private Color color; - - private ColorDialogProvider dialog; - private JTable table; - private ColorValue colorValue; - - private GThemeValueMap values; - private ThemeColorTableModel model; - - public ThemeColorEditor(GThemeValueMap values, ThemeColorTableModel model) { - this.values = values; - this.model = model; - } - - @Override - public Component getTableCellEditorComponent(JTable theTable, Object value, boolean isSelected, - int row, int column) { - - this.table = theTable; - colorValue = (ColorValue) value; - - JLabel label = new GDLabel(); - label.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); - label.setText(colorValue.getId()); - - dialog = new ColorDialogProvider(); - dialog.setRememberSize(false); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - DockingWindowManager.showDialog(dialog); - stopCellEditing(); - } - }); - - return label; - } - - @Override - public void cancelCellEditing() { - dialog.close(); - } - - @Override - public Object getCellEditorValue() { - return null; - } - - @Override - public boolean stopCellEditing() { - ListSelectionModel columnSelectionModel = table.getColumnModel().getSelectionModel(); - columnSelectionModel.setValueIsAdjusting(true); - int columnAnchor = columnSelectionModel.getAnchorSelectionIndex(); - int columnLead = columnSelectionModel.getLeadSelectionIndex(); - - if (color != null) { - Gui.setColor(colorValue.getId(), color); - model.refresh(); - } - dialog.close(); - fireEditingStopped(); - - columnSelectionModel.setAnchorSelectionIndex(columnAnchor); - columnSelectionModel.setLeadSelectionIndex(columnLead); - columnSelectionModel.setValueIsAdjusting(false); - - return true; - } - - // only double-click edits - @Override - public boolean isCellEditable(EventObject anEvent) { - if (anEvent instanceof MouseEvent) { - return ((MouseEvent) anEvent).getClickCount() >= 2; - } - return true; - } - -//================================================================================================== -// Inner Classes -//================================================================================================== - - class ColorDialogProvider extends DialogComponentProvider { - ColorDialogProvider() { - super("Color Editor", true); - - addWorkPanel(new ColorEditorPanel()); - addOKButton(); - addCancelButton(); - } - - @Override - protected void okCallback() { - color = lastUserSelectedColor; - close(); - } - - @Override - protected void cancelCallback() { - color = null; - close(); - } - } - - class ColorEditorPanel extends JPanel { - - ColorEditorPanel() { - - setLayout(new BorderLayout()); - - if (colorChooser == null) { - colorChooser = new GhidraColorChooser(); - } - - add(colorChooser, BorderLayout.CENTER); - colorChooser.getSelectionModel().addChangeListener(new ChangeListener() { - - @Override - public void stateChanged(ChangeEvent e) { - lastUserSelectedColor = colorChooser.getColor(); - // This could be a ColorUIResource, but Options only support storing Color. - lastUserSelectedColor = - new Color(lastUserSelectedColor.getRed(), lastUserSelectedColor.getGreen(), - lastUserSelectedColor.getBlue(), lastUserSelectedColor.getAlpha()); - } - }); - colorChooser.setColor(colorValue.get(values)); - } - } -} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemePropertiesLoader.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemePropertiesLoader.java index 9ee7d65728..dcf02ed217 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemePropertiesLoader.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemePropertiesLoader.java @@ -29,9 +29,11 @@ public class ThemePropertiesLoader { ThemePropertiesLoader() { } - public void initialize() { + public void load() { List themeDefaultFiles = Application.findFilesByExtensionInApplication(".theme.properties"); + defaults.clear(); + darkDefaults.clear(); for (ResourceFile resourceFile : themeDefaultFiles) { Msg.debug(this, "found theme file: " + resourceFile.getAbsolutePath()); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeValue.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeValue.java index 7e514ae4cd..3d3edc7de9 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeValue.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeValue.java @@ -24,12 +24,15 @@ public abstract class ThemeValue implements Comparable> { private final String id; private final T value; private final String refId; - private T cachedValue; +// private T cachedValue; protected ThemeValue(String id, String refId, T value) { this.id = fromExternalId(id); this.refId = (refId == null) ? null : fromExternalId(refId); this.value = value; + if (value instanceof GColor) { + System.out.println("Whoa"); + } } protected abstract String getIdPrefix(); @@ -46,11 +49,11 @@ public abstract class ThemeValue implements Comparable> { return value; } - T get(GThemeValueMap preferredValues) { - if (cachedValue == null) { - cachedValue = doGetValue(preferredValues); - } - return cachedValue; + public T get(GThemeValueMap preferredValues) { +// if (cachedValue == null) { + return doGetValue(preferredValues); +// } +// return cachedValue; } private T doGetValue(GThemeValueMap values) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatDarculaTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatDarculaTheme.java new file mode 100644 index 0000000000..4bdc24c621 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatDarculaTheme.java @@ -0,0 +1,25 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package docking.theme.builtin; + +import docking.theme.DiscoverableGTheme; +import ghidra.docking.util.LookAndFeelUtils; + +public class FlatDarculaTheme extends DiscoverableGTheme { + public FlatDarculaTheme() { + super("Flat Darcula", LookAndFeelUtils.FLAT_DARCULA_LOOK_AND_FEEL, true); + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatDarkTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatDarkTheme.java index dc1e7fe522..f459efa515 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatDarkTheme.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatDarkTheme.java @@ -20,6 +20,6 @@ import ghidra.docking.util.LookAndFeelUtils; public class FlatDarkTheme extends DiscoverableGTheme { public FlatDarkTheme() { - super("Dark Flat Theme", LookAndFeelUtils.FLAT_DARK_LOOK_AND_FEEL, true); + super("Flat Dark", LookAndFeelUtils.FLAT_DARK_LOOK_AND_FEEL, true); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatLightTheme.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatLightTheme.java index 935233cfec..6197016058 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatLightTheme.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/builtin/FlatLightTheme.java @@ -21,7 +21,7 @@ import ghidra.docking.util.LookAndFeelUtils; public class FlatLightTheme extends DiscoverableGTheme { public FlatLightTheme() { - super("Flat", LookAndFeelUtils.FLAT_LIGHT_LOOK_AND_FEEL); + super("Flat Light", LookAndFeelUtils.FLAT_LIGHT_LOOK_AND_FEEL); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/GThemeColorEditorDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/GThemeColorEditorDialog.java new file mode 100644 index 0000000000..63915b2ade --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/GThemeColorEditorDialog.java @@ -0,0 +1,103 @@ +/* ### + * 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.theme.gui; + +import java.awt.BorderLayout; +import java.awt.Color; + +import javax.swing.*; +import javax.swing.event.ChangeListener; + +import docking.DialogComponentProvider; +import docking.DockingWindowManager; +import docking.options.editor.GhidraColorChooser; +import docking.theme.ColorValue; +import docking.theme.Gui; +import ghidra.util.Swing; + +public class GThemeColorEditorDialog extends DialogComponentProvider { + + private ColorValue originalColorValue; + private ColorValue currentColorValue; + + private GThemeDialog themeDialog; + private GhidraColorChooser colorChooser; + private ChangeListener colorChangeListener = e -> colorChanged(); + + public GThemeColorEditorDialog(GThemeDialog themeDialog) { + super("Theme Color Editor", false); + this.themeDialog = themeDialog; + addWorkPanel(buildColorPanel()); + addOKButton(); + addCancelButton(); + } + + public void editColor(ColorValue colorValue) { + if (currentColorValue != null && !currentColorValue.equals(originalColorValue)) { + themeDialog.colorChangeAccepted(); + } + this.originalColorValue = colorValue; + this.currentColorValue = colorValue; + + setTitle("Edit Color For: " + colorValue.getId()); + Color color = Gui.getRawColor(originalColorValue.getId()); + colorChooser.getSelectionModel().removeChangeListener(colorChangeListener); + colorChooser.setColor(color); + colorChooser.getSelectionModel().addChangeListener(colorChangeListener); + + if (!isShowing()) { + DockingWindowManager.showDialog(themeDialog.getComponent(), this); + } + } + + private JComponent buildColorPanel() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + colorChooser = new GhidraColorChooser(); + panel.add(colorChooser); + + return panel; + } + + @Override + protected void okCallback() { + close(); + if (!currentColorValue.equals(originalColorValue)) { + themeDialog.colorChangeAccepted(); + } + currentColorValue = null; + originalColorValue = null; + } + + @Override + protected void cancelCallback() { + retoreOriginalColor(); + close(); + currentColorValue = null; + originalColorValue = null; + } + + private void retoreOriginalColor() { + Gui.setColor(originalColorValue); + } + + private void colorChanged() { + Color newColor = colorChooser.getColor(); + currentColorValue = new ColorValue(originalColorValue.getId(), newColor); + Swing.runLater(() -> Gui.setColor(currentColorValue)); + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/GThemeDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/GThemeDialog.java new file mode 100644 index 0000000000..fba55af36f --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/GThemeDialog.java @@ -0,0 +1,188 @@ +/* ### + * 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.theme.gui; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +import docking.DialogComponentProvider; +import docking.theme.ColorValue; +import docking.theme.Gui; +import docking.widgets.combobox.GhidraComboBox; +import docking.widgets.table.GFilterTable; +import docking.widgets.table.GTable; +import ghidra.util.Swing; +import resources.Icons; + +public class GThemeDialog extends DialogComponentProvider { + + private ThemeColorTableModel colorTableModel; + private GThemeColorEditorDialog dialog; + + public GThemeDialog() { + super("Theme Dialog", false); + addWorkPanel(createMainPanel()); + addOKButton(); + addCancelButton(); + setOkButtonText("Save"); + setPreferredSize(1100, 500); + setRememberSize(false); + + } + + @Override + protected void okCallback() { + for (Window window : Window.getWindows()) { + SwingUtilities.updateComponentTreeUI(window); + } + +// GhidraFileChooser chooser = new GhidraFileChooser(getComponent()); +// chooser.setTitle("Choose Theme File"); +// chooser.setApproveButtonText("Select Output File"); +// chooser.setApproveButtonToolTipText("Select File"); +// chooser.setFileSelectionMode(GhidraFileChooserMode.FILES_ONLY); +// chooser.setSelectedFileFilter(GhidraFileFilter.ALL); +// File file = chooser.getSelectedFile(); +// try { +// Gui.getActiveTheme().saveToFile(file, Gui.getAllDefaultValues()); +// } +// catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } + } + + private JComponent createMainPanel() { + JPanel panel = new JPanel(); + + panel.setLayout(new BorderLayout()); + panel.add(buildControlPanel(), BorderLayout.NORTH); + panel.add(buildTabedTables()); + return panel; + } + + private Component buildControlPanel() { + JPanel panel = new JPanel(new BorderLayout()); + panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); +// panel.add(buildThemeChoiceButtons(), BorderLayout.WEST); + panel.add(buildThemeCombo(), BorderLayout.WEST); + panel.add(buildReloadDefaultsButton(), BorderLayout.EAST); + + return panel; + } + + private Component buildReloadDefaultsButton() { + JButton button = new JButton(Icons.REFRESH_ICON); + button.addActionListener(this::reloadThemeDefaults); + button.setToolTipText("Reload Theme Defaults"); + return button; + } + + private Component buildThemeCombo() { + JPanel panel = new JPanel(); + GhidraComboBox combo = new GhidraComboBox<>(Gui.getAllThemeNames()); + combo.setSelectedItem(Gui.getActiveTheme().getName()); + combo.addItemListener(this::themeComboChanged); + + panel.add(new JLabel("Theme: "), BorderLayout.WEST); + panel.add(combo); + panel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); + return panel; + } + + private Component buildThemeChoiceButtons() { + JPanel panel = new JPanel(new FlowLayout()); + panel.add(createThemeButton("Flat")); + panel.add(createThemeButton("Dark Flat")); + panel.add(createThemeButton("Metal")); + panel.add(createThemeButton("Nimbus")); + panel.add(createThemeButton("GDK+")); + panel.add(createThemeButton("CDE/Motif")); + return panel; + } + + private JButton createThemeButton(String name) { + JButton button = new JButton(name); + button.addActionListener(e -> Gui.setTheme(Gui.getTheme(name))); + return button; + } + + private Component buildTabedTables() { + JTabbedPane tabbedPane = new JTabbedPane(); + tabbedPane.add("Colors", buildColorTable()); + return tabbedPane; + } + + private JComponent buildColorTable() { + colorTableModel = new ThemeColorTableModel(); + + GFilterTable filterTable = new GFilterTable<>(colorTableModel); + GTable table = filterTable.getTable(); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + table.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + ColorValue colorValue = filterTable.getSelectedRowObject(); + if (colorValue != null) { + editColor(colorValue); + } + e.consume(); + } + } + }); + + table.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + ColorValue value = filterTable.getItemAt(e.getPoint()); + editColor(value); + } + } + }); + + return filterTable; + } + + private void themeComboChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + String themeName = (String) e.getItem(); + Swing.runLater(() -> Gui.setTheme(Gui.getTheme(themeName))); + Swing.runLater(() -> colorTableModel.reload()); + } + } + + private void reloadThemeDefaults(ActionEvent e) { + Gui.reloadThemeDefaults(); + colorTableModel.reload(); + } + + protected void editColor(ColorValue value) { + if (dialog == null) { + dialog = new GThemeColorEditorDialog(this); + } + dialog.editColor(value); + } + + void colorChangeAccepted() { + colorTableModel.reload(); + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeColorTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTableModel.java similarity index 65% rename from Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeColorTableModel.java rename to Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTableModel.java index 2b24884982..0c884bd2be 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/ThemeColorTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTableModel.java @@ -13,36 +13,48 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package docking.theme; +package docking.theme.gui; import java.awt.*; import java.util.Comparator; import java.util.List; +import java.util.function.Supplier; +import javax.swing.Icon; import javax.swing.JLabel; +import docking.theme.*; import docking.widgets.table.*; import ghidra.docking.settings.Settings; import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProviderStub; -import ghidra.util.ColorUtils; import ghidra.util.WebColors; import ghidra.util.table.column.AbstractGColumnRenderer; import ghidra.util.table.column.GColumnRenderer; public class ThemeColorTableModel extends GDynamicColumnTableModel { private List colors; + private GThemeValueMap values; + private GThemeValueMap coreDefaults; + private GThemeValueMap darkDefaults; - public ThemeColorTableModel(GTheme theme) { + public ThemeColorTableModel() { super(new ServiceProviderStub()); - colors = Gui.getAllValues().getColors(); + loadValues(); } - public void refresh() { - colors = Gui.getAllValues().getColors(); + public void reload() { + loadValues(); fireTableDataChanged(); } + private void loadValues() { + values = Gui.getAllValues(); + coreDefaults = Gui.getCoreDefaults(); + darkDefaults = Gui.getDarkDefaults(); + colors = values.getColors(); + } + @Override public String getName() { return "Users"; @@ -53,18 +65,14 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel createTableColumnDescriptor() { TableColumnDescriptor descriptor = new TableColumnDescriptor<>(); descriptor.addVisibleColumn(new IdColumn()); + descriptor.addVisibleColumn(new ValueColumn("Current Color", () -> values)); + descriptor.addVisibleColumn(new ValueColumn("Core Defaults", () -> coreDefaults)); + descriptor.addVisibleColumn(new ValueColumn("Dark Defaults", () -> darkDefaults)); descriptor.addVisibleColumn(new IsLafPropertyColumn()); - descriptor.addVisibleColumn(new ValueColumn("Current Color", Gui.getAllValues())); - descriptor.addVisibleColumn(new ValueColumn("Core Defaults", Gui.getAllValues())); - descriptor.addVisibleColumn(new ValueColumn("Dark Defaults", Gui.getDarkDefaults())); return descriptor; } @@ -85,17 +93,22 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel { - private ThemeColorRenderer renderer = new ThemeColorRenderer(Gui.getAllValues()); - private GThemeValueMap valueMap; + private ThemeColorRenderer renderer; private String name; + private Supplier valueSupplier; - ValueColumn(String name, GThemeValueMap valueMap) { + ValueColumn(String name, Supplier supplier) { this.name = name; - this.valueMap = valueMap; - renderer = new ThemeColorRenderer(valueMap); + this.valueSupplier = supplier; + renderer = new ThemeColorRenderer(supplier); } @Override @@ -115,10 +128,15 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel getComparator() { - return (v1, v2) -> valueMap.getColor(v1.getId()) - .compareValue(valueMap.getColor(v2.getId())); + return (v1, v2) -> valueSupplier.get() + .getColor(v1.getId()) + .compareValue(valueSupplier.get().getColor(v2.getId())); } + @Override + public int getColumnPreferredWidth() { + return 300; + } } class IsLafPropertyColumn extends AbstractDynamicTableColumn { @@ -133,20 +151,25 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel { - private GThemeValueMap valueMap; + private Supplier mapSupplier; - public ThemeColorRenderer(GThemeValueMap valueMap) { - this.valueMap = valueMap; + public ThemeColorRenderer(Supplier mapSupplier) { + this.mapSupplier = mapSupplier; setFont(new Font("Monospaced", Font.PLAIN, 12)); } @Override public Component getTableCellRendererComponent(GTableCellRenderingData data) { - + GThemeValueMap valueMap = mapSupplier.get(); JLabel label = (JLabel) super.getTableCellRendererComponent(data); String id = ((ColorValue) data.getValue()).getId(); @@ -172,8 +195,9 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel colorIds = + LookAndFeelUtils.getLookAndFeelIdsForType(defaults, Color.class); + for (String id : colorIds) { + Color color = defaults.getColor(id); + ColorValue value = new ColorValue(id, color); + javaDefaults.addColor(value); + } + Gui.setJavaDefaults(javaDefaults); + for (String id : colorIds) { + defaults.put(id, Gui.getGColorUiResource(id)); + } +// javaDefaults.addColor(new ColorValue("Label.textForground", "Label.foreground")); + defaults.put("Label.textForeground", Gui.getGColorUiResource("Label.foreground")); + GColor.refreshAll(); + return defaults; + } + + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/laf/GenericLookAndFeelInstaller.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/laf/GenericLookAndFeelInstaller.java new file mode 100644 index 0000000000..17506ba713 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/laf/GenericLookAndFeelInstaller.java @@ -0,0 +1,55 @@ +/* ### + * 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.theme.laf; + +import javax.swing.UIManager; +import javax.swing.UIManager.LookAndFeelInfo; + +import ghidra.docking.util.LookAndFeelUtils; +import ghidra.util.Msg; + +public class GenericLookAndFeelInstaller extends LookAndFeelInstaller { + private String name; + + public GenericLookAndFeelInstaller(String name) { + this.name = name; + } + + @Override + protected void installLookAndFeel() throws Exception { + String className = findLookAndFeelClassName(name); + UIManager.setLookAndFeel(className); + } + + private static String findLookAndFeelClassName(String lookAndFeelName) { + if (lookAndFeelName.equalsIgnoreCase(LookAndFeelUtils.SYSTEM_LOOK_AND_FEEL)) { + return UIManager.getSystemLookAndFeelClassName(); + } + + LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels(); + for (LookAndFeelInfo info : installedLookAndFeels) { + String className = info.getClassName(); + if (lookAndFeelName.equals(className) || lookAndFeelName.equals(info.getName())) { + return className; + } + } + + Msg.debug(LookAndFeelUtils.class, + "Unable to find requested Look and Feel: " + lookAndFeelName); + return UIManager.getSystemLookAndFeelClassName(); + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/laf/LookAndFeelInstaller.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/laf/LookAndFeelInstaller.java new file mode 100644 index 0000000000..91d27ca0f9 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/laf/LookAndFeelInstaller.java @@ -0,0 +1,102 @@ +/* ### + * 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.theme.laf; + +import java.awt.Color; +import java.util.List; + +import javax.swing.UIDefaults; +import javax.swing.UIManager; +import javax.swing.plaf.UIResource; + +import docking.theme.*; +import ghidra.docking.util.LookAndFeelUtils; + +public abstract class LookAndFeelInstaller { + + public void install() throws Exception { + cleanUiDefaults(); + installLookAndFeel(); + installJavaDefaults(); + fixupLookAndFeelIssues(); + } + + protected abstract void installLookAndFeel() throws Exception; + + protected void fixupLookAndFeelIssues() { + // no generic fix-ups at this time. + } + + protected void installJavaDefaults() { + GThemeValueMap javaDefaults = extractJavaDefaults(); + Gui.setJavaDefaults(javaDefaults); + installIndirectValues(javaDefaults); + } + + private void installIndirectValues(GThemeValueMap javaDefaults) { + UIDefaults defaults = UIManager.getDefaults(); + for (ColorValue colorValue : javaDefaults.getColors()) { + String id = colorValue.getId(); + GColorUIResource gColor = Gui.getGColorUiResource(id); + defaults.put(id, gColor); + } + for (FontValue fontValue : javaDefaults.getFonts()) { + String id = fontValue.getId(); + GFont gFont = new GFont(id); + if (!gFont.equals(fontValue.getRawValue())) { + // only update if we have changed the default java color + defaults.put(id, gFont); + } + } + } + + protected GThemeValueMap extractJavaDefaults() { + GThemeValueMap values = new GThemeValueMap(); + // for now, just doing color properties. + List ids = + LookAndFeelUtils.getLookAndFeelIdsForType(UIManager.getDefaults(), Color.class); + for (String id : ids) { + values.addColor(new ColorValue(id, getNonUiColor(id))); + } + return values; + } + + private static Color getNonUiColor(String id) { + // Not sure, but for now, make sure colors are not UIResource + Color color = UIManager.getColor(id); + if (color instanceof UIResource) { + return new Color(color.getRGB(), true); + } + return color; + } + + private void cleanUiDefaults() { + GThemeValueMap javaDefaults = Gui.getJavaDefaults(); + if (javaDefaults == null) { + return; + } + UIDefaults defaults = UIManager.getDefaults(); + for (ColorValue colorValue : javaDefaults.getColors()) { + String id = colorValue.getId(); + defaults.put(id, null); + } +// for (FontValue fontValue : javaDefaults.getFonts()) { +// String id = fontValue.getId(); +// defaults.put(id, null); +// } + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/laf/MetalLookAndFeelInstaller.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/laf/MetalLookAndFeelInstaller.java new file mode 100644 index 0000000000..f2e53353ea --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/laf/MetalLookAndFeelInstaller.java @@ -0,0 +1,29 @@ +/* ### + * 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.theme.laf; + +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.plaf.metal.MetalLookAndFeel; + +public class MetalLookAndFeelInstaller extends LookAndFeelInstaller { + + @Override + protected void installLookAndFeel() throws UnsupportedLookAndFeelException { + UIManager.setLookAndFeel(new MetalLookAndFeel()); + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/laf/NimbusLookAndFeelInstaller.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/laf/NimbusLookAndFeelInstaller.java new file mode 100644 index 0000000000..b30c335329 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/laf/NimbusLookAndFeelInstaller.java @@ -0,0 +1,69 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package docking.theme.laf; + +import java.awt.Color; +import java.util.List; + +import javax.swing.*; +import javax.swing.plaf.nimbus.NimbusLookAndFeel; + +import docking.theme.*; +import ghidra.docking.util.LookAndFeelUtils; + +public class NimbusLookAndFeelInstaller extends LookAndFeelInstaller { + + @Override + protected void installLookAndFeel() throws UnsupportedLookAndFeelException { + UIManager.setLookAndFeel(new GNimbusLookAndFeel()); + } + + @Override + protected void installJavaDefaults() { + // do nothing - already handled by extended NimbusLookAndFeel + } + + /** + * Extends the NimbusLookAndFeel to intercept the {@link #getDefaults()}. To get Nimbus + * to use our indirect values, we have to get in early. + */ + static class GNimbusLookAndFeel extends NimbusLookAndFeel { + + @Override + public UIDefaults getDefaults() { + GThemeValueMap javaDefaults = new GThemeValueMap(); + + UIDefaults defaults = super.getDefaults(); + List colorIds = + LookAndFeelUtils.getLookAndFeelIdsForType(defaults, Color.class); + for (String id : colorIds) { + Color color = defaults.getColor(id); + ColorValue value = new ColorValue(id, color); + javaDefaults.addColor(value); + } + Gui.setJavaDefaults(javaDefaults); + for (String id : colorIds) { + defaults.put(id, Gui.getGColorUiResource(id)); + } +// javaDefaults.addColor(new ColorValue("Label.textForground", "Label.foreground")); + defaults.put("Label.textForeground", Gui.getGColorUiResource("Label.foreground")); + GColor.refreshAll(); + return defaults; + } + + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableCellRenderer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableCellRenderer.java index 53331bade1..7a048e7e74 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableCellRenderer.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableCellRenderer.java @@ -22,8 +22,7 @@ import java.text.DecimalFormat; import java.util.HashMap; import java.util.Map; -import javax.swing.JTable; -import javax.swing.SwingConstants; +import javax.swing.*; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableModel; @@ -98,7 +97,10 @@ public class GTableCellRenderer extends AbstractGCellRenderer implements TableCe "Using a GTableCellRenderer in a non-GTable table. (Model class: " + table.getModel().getClass().getName() + ")"); } - + // check if LookAndFeel has changed + if (UIManager.getUI(this) != getUI()) { + updateUI(); + } GTable gTable = (GTable) table; GTableCellRenderingData data = gTable.getRenderingData(column); Object rowObject = null; diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java index d373b28dbc..cc79732777 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/tree/support/GTreeRenderer.java @@ -22,6 +22,7 @@ import javax.swing.JTree; import javax.swing.plaf.ColorUIResource; import javax.swing.tree.DefaultTreeCellRenderer; +import docking.theme.GColor; import docking.widgets.GComponent; import docking.widgets.tree.GTree; import docking.widgets.tree.GTreeNode; @@ -53,7 +54,6 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent paintDropTarget = (value == dropTarget); setBackground(selected1 ? getBackgroundSelectionColor() : getBackgroundNonSelectionColor()); - if (!(value instanceof GTreeNode)) { // not a GTree return this; @@ -84,12 +84,12 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent @Override public void setBackgroundSelectionColor(Color newColor) { - super.setBackgroundSelectionColor(fromUiResource(newColor)); + super.setBackgroundSelectionColor(fromUiResource(newColor, "Tree.selectionBackground")); } @Override public void setBackgroundNonSelectionColor(Color newColor) { - super.setBackgroundNonSelectionColor(fromUiResource(newColor)); + super.setBackgroundNonSelectionColor(fromUiResource(newColor, "Tree.textBackground")); } /** @@ -101,9 +101,9 @@ public class GTreeRenderer extends DefaultTreeCellRenderer implements GComponent * @param c the source color * @return the new color */ - protected Color fromUiResource(Color c) { + protected Color fromUiResource(Color c, String defaultKey) { if (c instanceof ColorUIResource) { - return new Color(c.getRGB()); + return new GColor(defaultKey); } return c; } diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/util/LookAndFeelUtils.java b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/util/LookAndFeelUtils.java index 140c7babd9..1d188ce69d 100644 --- a/Ghidra/Framework/Docking/src/main/java/ghidra/docking/util/LookAndFeelUtils.java +++ b/Ghidra/Framework/Docking/src/main/java/ghidra/docking/util/LookAndFeelUtils.java @@ -24,6 +24,11 @@ import javax.swing.*; import javax.swing.UIManager.LookAndFeelInfo; import javax.swing.plaf.ComponentUI; +import org.apache.commons.collections4.IteratorUtils; + +import com.formdev.flatlaf.*; + +import docking.theme.laf.*; import ghidra.framework.OperatingSystem; import ghidra.framework.Platform; import ghidra.framework.preferences.Preferences; @@ -63,6 +68,11 @@ public class LookAndFeelUtils { * The Flatlaf implementation of dark mode. */ public static final String FLAT_DARK_LOOK_AND_FEEL = "Flat Dark"; + /** + * The Flatlaf implementation of darcula mode. + */ + public static final String FLAT_DARCULA_LOOK_AND_FEEL = "Flat Darcula"; + public static final String WINDOWS = "Windows"; public static final String WINDOWS_CLASSIC = "Windows Classic"; @@ -118,14 +128,32 @@ public class LookAndFeelUtils { installPopupMenuSettingsOverride(); } catch (Exception exc) { - Msg.error(LookAndFeelUtils.class, - "Error loading Look and Feel: " + exc, exc); + Msg.error(LookAndFeelUtils.class, "Error loading Look and Feel: " + exc, exc); } }); } + public static void setLookAndFeel(LookAndFeel laf) { + SystemUtilities.runSwingNow(() -> { + try { + UIManager.setLookAndFeel(laf); + fixupLookAndFeelIssues(); + + // some custom values for any given LAF + installGlobalLookAndFeelAttributes(); + installGlobalFontSizeOverride(); + installCustomLookAndFeelActions(); + installPopupMenuSettingsOverride(); + } + catch (Exception exc) { + Msg.error(LookAndFeelUtils.class, "Error loading Look and Feel: " + exc, exc); + } + }); + + } + public static void dumpUIProperties() { - List colorKeys = getLookAndFeelIdsForType(Color.class); + List colorKeys = getLookAndFeelIdsForType(UIManager.getDefaults(), Color.class); Collections.sort(colorKeys); for (String string : colorKeys) { Msg.debug(LookAndFeelUtils.class, string + "\t\t" + UIManager.get(string)); @@ -133,13 +161,12 @@ public class LookAndFeelUtils { } - public static List getLookAndFeelIdsForType(Class clazz) { - UIDefaults defaults = UIManager.getDefaults(); + public static List getLookAndFeelIdsForType(UIDefaults defaults, Class clazz) { List colorKeys = new ArrayList<>(); - for (Entry entry : defaults.entrySet()) { - Object key = entry.getKey(); + List keyList = IteratorUtils.toList(defaults.keys().asIterator()); + for (Object key : keyList) { if (key instanceof String) { - Object value = entry.getValue(); + Object value = defaults.get(key); if (clazz.isInstance(value)) { colorKeys.add((String) key); } @@ -165,37 +192,28 @@ public class LookAndFeelUtils { return list; } - private static void installLookAndFeelByName(String lookAndFeelName) - throws ClassNotFoundException, InstantiationException, IllegalAccessException, - UnsupportedLookAndFeelException { + private static void installLookAndFeelByName(String lookAndFeelName) throws Exception { - String lookAndFeelClassName = findLookAndFeelClassName(lookAndFeelName); - UIManager.setLookAndFeel(lookAndFeelClassName); + LookAndFeelInstaller installer = getLookAndFeelInstaller(lookAndFeelName); + installer.install(); fixupLookAndFeelIssues(); } - private static String findLookAndFeelClassName(String lookAndFeelName) { - if (lookAndFeelName.equalsIgnoreCase(SYSTEM_LOOK_AND_FEEL)) { - return UIManager.getSystemLookAndFeelClassName(); + private static LookAndFeelInstaller getLookAndFeelInstaller(String lookAndFeelName) { + switch (lookAndFeelName) { + case NIMBUS_LOOK_AND_FEEL: + return new NimbusLookAndFeelInstaller(); + case METAL_LOOK_AND_FEEL: + return new MetalLookAndFeelInstaller(); + case FLAT_LIGHT_LOOK_AND_FEEL: + return new FlatLookAndFeelInstaller(new FlatLightLaf()); + case FLAT_DARK_LOOK_AND_FEEL: + return new FlatLookAndFeelInstaller(new FlatDarkLaf()); + case FLAT_DARCULA_LOOK_AND_FEEL: + return new FlatLookAndFeelInstaller(new FlatDarculaLaf()); + default: + return new GenericLookAndFeelInstaller(lookAndFeelName); } - else if (lookAndFeelName.equalsIgnoreCase(FLAT_LIGHT_LOOK_AND_FEEL)) { - return "com.formdev.flatlaf.FlatLightLaf"; - } - else if (lookAndFeelName.equalsIgnoreCase(FLAT_DARK_LOOK_AND_FEEL)) { - return "com.formdev.flatlaf.FlatDarkLaf"; - } - - LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels(); - for (LookAndFeelInfo info : installedLookAndFeels) { - String className = info.getClassName(); - if (lookAndFeelName.equals(className) || lookAndFeelName.equals(info.getName())) { - return className; - } - } - - Msg.debug(LookAndFeelUtils.class, - "Unable to find requested Look and Feel: " + lookAndFeelName); - return UIManager.getSystemLookAndFeelClassName(); } /** @@ -368,4 +386,28 @@ public class LookAndFeelUtils { return NIMBUS_LOOK_AND_FEEL.equals(lookAndFeel.getName()); } + private static String findLookAndFeelClassName(String lookAndFeelName) { + if (lookAndFeelName.equalsIgnoreCase(SYSTEM_LOOK_AND_FEEL)) { + return UIManager.getSystemLookAndFeelClassName(); + } + else if (lookAndFeelName.equalsIgnoreCase(FLAT_LIGHT_LOOK_AND_FEEL)) { + return "com.formdev.flatlaf.FlatLightLaf"; + } + else if (lookAndFeelName.equalsIgnoreCase(FLAT_DARK_LOOK_AND_FEEL)) { + return "com.formdev.flatlaf.FlatDarkLaf"; + } + + LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels(); + for (LookAndFeelInfo info : installedLookAndFeels) { + String className = info.getClassName(); + if (lookAndFeelName.equals(className) || lookAndFeelName.equals(info.getName())) { + return className; + } + } + + Msg.debug(LookAndFeelUtils.class, + "Unable to find requested Look and Feel: " + lookAndFeelName); + return UIManager.getSystemLookAndFeelClassName(); + } + } diff --git a/Ghidra/Framework/Docking/src/test/java/docking/theme/GuiTest.java b/Ghidra/Framework/Docking/src/test/java/docking/theme/GuiTest.java index d46dd4da13..9dc3fcb93a 100644 --- a/Ghidra/Framework/Docking/src/test/java/docking/theme/GuiTest.java +++ b/Ghidra/Framework/Docking/src/test/java/docking/theme/GuiTest.java @@ -32,7 +32,7 @@ public class GuiTest extends AbstractDockingTest { public void setUp() { Gui.setPropertiesLoader(new ThemePropertiesLoader() { @Override - public void initialize() { + public void load() { // do nothing } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/WebColors.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/WebColors.java index 3b6a19c83f..c3a6b3ba49 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/WebColors.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/WebColors.java @@ -382,11 +382,19 @@ public abstract class WebColors { int red = Integer.parseInt(split[0]); int green = Integer.parseInt(split[1]); int blue = Integer.parseInt(split[2]); - float alpha = Float.parseFloat(split[3]); - return new Color(red, green, blue, (int) (alpha * 255 + 0.5)); + int alpha = parseAlpha(split[3]); + return new Color(red, green, blue, alpha); } catch (IllegalArgumentException e) { return null; } } + + private static int parseAlpha(String string) { + if (string.contains(".")) { + float value = Float.parseFloat(string); + return (int) (value * 0xff + 0.5) & 0xff; + } + return Integer.parseInt(string) & 0xff; + } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakStore.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakStore.java new file mode 100644 index 0000000000..82127cc3db --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/WeakStore.java @@ -0,0 +1,121 @@ +/* ### + * 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 ghidra.util.datastruct; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.*; + +/** + * Class for storing weak reference to object instances. Objects of type T can be place + * in this store and they will remain there until there are no references to that object + * remaining. Note that this is not a Set and you can have multiple instances that are + * "equal" in this store.The main purpose of this store is to be able to get all objects + * in the store that are still reference; typically to refresh or "kick" them in some manner. + * Could be useful for a thread safe weak listener list. + * + * @param The type of objects stored in this WeakStore + */ +public class WeakStore { + protected ReferenceQueue refQueue; + private Link first; + private Link last; + private int size = 0; + + public WeakStore() { + refQueue = new ReferenceQueue<>(); + } + + /** + * Returns the number of objects of type T remaining in the store. Those that are remaining + * are either still referenced + * @return the number of objects still in the store that haven't yet been garbage collected + */ + public synchronized int size() { + processQueue(); + return size; + } + + /** + * returns a list of all the objects in this store + * @return a list of all the objects in this store + */ + public synchronized List getValues() { + processQueue(); + List values = new ArrayList<>(); + for (Link l = first; l != null; l = l.nextLink) { + T value = l.get(); + if (value != null) { + values.add(value); + } + } + return values; + } + + /** + * Adds the given value to the store + * @param value the instance being added to the store + */ + public synchronized void add(T value) { + Objects.requireNonNull(value); + + processQueue(); + Link newLink = new Link<>(last, value, null, refQueue); + if (last == null) { + first = newLink; + } + else { + last.nextLink = newLink; + } + last = newLink; + size++; + } + + @SuppressWarnings("unchecked") + private void processQueue() { + Link ref; + while ((ref = (Link) refQueue.poll()) != null) { + remove(ref); + } + } + + private void remove(Link link) { + if (link.previousLink == null) { + first = link.nextLink; + } + else { + link.previousLink.nextLink = link.nextLink; + } + if (link.nextLink == null) { + last = link.previousLink; + } + else { + link.nextLink.previousLink = link.previousLink; + } + size--; + } + + private static class Link extends WeakReference { + private Link nextLink; + private Link previousLink; + + public Link(Link previous, T value, Link next, ReferenceQueue refQueue) { + super(value, refQueue); + this.nextLink = next; + this.previousLink = previous; + } + } +} diff --git a/Ghidra/Framework/Generic/src/test/java/ghidra/util/WebColorsTest.java b/Ghidra/Framework/Generic/src/test/java/ghidra/util/WebColorsTest.java index 0e61a39b74..50784cdf8c 100644 --- a/Ghidra/Framework/Generic/src/test/java/ghidra/util/WebColorsTest.java +++ b/Ghidra/Framework/Generic/src/test/java/ghidra/util/WebColorsTest.java @@ -35,7 +35,7 @@ public class WebColorsTest { @Test public void testColorToStringFromColorWithNoDefinedEntry() { - assertEquals("#0123EF", WebColors.toString(new Color(0x01, 0x23, 0xEF))); + assertEquals("#0123ef", WebColors.toString(new Color(0x01, 0x23, 0xef))); } @Test @@ -45,10 +45,20 @@ public class WebColorsTest { assertEquals(WebColors.NAVY, WebColors.getColor("#000080")); assertEquals(WebColors.NAVY, WebColors.getColor("rgb(0,0,128)")); assertEquals(WebColors.NAVY, WebColors.getColor("rgba(0,0,128,1.0)")); + assertEquals(WebColors.NAVY, WebColors.getColor("rgba(0,0,128, 255)")); assertEquals(new Color(0x123456), WebColors.getColor("0x123456")); assertEquals(new Color(0x80102030, true), WebColors.getColor("rgba(16, 32, 48, 0.5)")); assertNull(WebColors.getColor("asdfasdfas")); } + + @Test + public void testColorWithAlphaRoundTrip() { + Color c = new Color(0x44112233, true); + assertEquals(0x44, c.getAlpha()); + String string = WebColors.toString(c, false); + Color parsed = WebColors.getColor(string); + assertEquals(c, parsed); + } } diff --git a/Ghidra/Framework/Generic/src/test/java/ghidra/util/datastruct/WeakStoreTest.java b/Ghidra/Framework/Generic/src/test/java/ghidra/util/datastruct/WeakStoreTest.java new file mode 100644 index 0000000000..9963cb7d3a --- /dev/null +++ b/Ghidra/Framework/Generic/src/test/java/ghidra/util/datastruct/WeakStoreTest.java @@ -0,0 +1,62 @@ +/* ### + * 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 ghidra.util.datastruct; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; + +public class WeakStoreTest { + @Test + public void testStore() throws InterruptedException { + WeakStore store = new WeakStore<>(); + store.add(new Foo("AAA")); + store.add(new Foo("BBB")); + store.add(new Foo("CCC")); + + assertEquals(3, store.size()); + + List values = store.getValues(); + + assertEquals("AAA", values.get(0).getName()); + assertEquals("BBB", values.get(1).getName()); + assertEquals("CCC", values.get(2).getName()); + values = null; + + for (int i = 0; i < 20; i++) { + System.gc(); + if (store.size() == 0) { + break; + } + } + assertEquals(0, store.size()); + } + + static class Foo { + String name; + + Foo(String name) { + this.name = name; + } + + String getName() { + return name; + } + } + +}