GP-1981Checkpoint - more Changes

This commit is contained in:
ghidragon
2022-07-28 16:01:12 -04:00
parent 60b1ae8b44
commit 50de9eb7b3
34 changed files with 1297 additions and 419 deletions
@@ -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
@@ -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<String> themeNames = getAllThemeNames();
List<String> 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<String> getAllThemeNames() {
Set<GTheme> allThemes = Gui.getAllThemes();
List<String> 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) {
@@ -27,7 +27,7 @@ public class ColorValue extends ThemeValue<Color> {
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<Color> {
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;
}
}
@@ -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<GColor> inUseColors = WeakDataStructureFactory.createCopyOnReadWeakSet();
private static WeakStore<GColor> 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");
}
}
}
@@ -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);
}
}
@@ -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
@@ -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);
}
}
@@ -200,7 +200,7 @@ public class GTheme extends GThemeValueMap {
List<FontValue> fonts = values.getFonts();
Collections.sort(fonts);
List<IconValue> icons = values.getIconPaths();
List<IconValue> icons = values.getIcons();
Collections.sort(icons);
writer.write(THEME_NAME_KEY + " = " + name);
@@ -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<ColorValue> filterTable = new GFilterTable<>(colorTableModel);
filterTable.getTable().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
filterTable.getTable()
.setDefaultEditor(ColorValue.class,
new ThemeColorEditor(Gui.getAllValues(), colorTableModel));
return filterTable;
}
}
@@ -74,7 +74,7 @@ public class GThemeValueMap {
return new ArrayList<>(fontMap.values());
}
public List<IconValue> getIconPaths() {
public List<IconValue> 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();
}
}
@@ -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<GTheme> 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<String, GColorUIResource> 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<GTheme> first =
getAllThemes().stream().filter(t -> t.getName().equals(themeName)).findFirst();
return first.get();
}
public static List<String> getAllThemeNames() {
List<String> 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<String> 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<GTheme> 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;
}
}
@@ -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));
}
}
}
@@ -29,9 +29,11 @@ public class ThemePropertiesLoader {
ThemePropertiesLoader() {
}
public void initialize() {
public void load() {
List<ResourceFile> themeDefaultFiles =
Application.findFilesByExtensionInApplication(".theme.properties");
defaults.clear();
darkDefaults.clear();
for (ResourceFile resourceFile : themeDefaultFiles) {
Msg.debug(this, "found theme file: " + resourceFile.getAbsolutePath());
@@ -24,12 +24,15 @@ public abstract class ThemeValue<T> implements Comparable<ThemeValue<T>> {
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<T> implements Comparable<ThemeValue<T>> {
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) {
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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));
}
}
@@ -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<String> 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<ColorValue> 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();
}
}
@@ -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<ColorValue, Object> {
private List<ColorValue> 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<ColorValue, O
return colors;
}
public boolean isCellEditable(int row, int column) {
return getColumnName(column).equals("Current Color");
}
@Override
protected TableColumnDescriptor<ColorValue> createTableColumnDescriptor() {
TableColumnDescriptor<ColorValue> 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<ColorValue, O
ServiceProvider provider) throws IllegalArgumentException {
return themeColor.getId();
}
@Override
public int getColumnPreferredWidth() {
return 300;
}
}
class ValueColumn extends AbstractDynamicTableColumn<ColorValue, ColorValue, Object> {
private ThemeColorRenderer renderer = new ThemeColorRenderer(Gui.getAllValues());
private GThemeValueMap valueMap;
private ThemeColorRenderer renderer;
private String name;
private Supplier<GThemeValueMap> valueSupplier;
ValueColumn(String name, GThemeValueMap valueMap) {
ValueColumn(String name, Supplier<GThemeValueMap> 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<ColorValue, O
}
public Comparator<ColorValue> 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<ColorValue, Boolean, Object> {
@@ -133,20 +151,25 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel<ColorValue, O
ServiceProvider provider) throws IllegalArgumentException {
return Gui.isJavaDefinedColor(themeColor.getId());
}
@Override
public int getColumnPreferredWidth() {
return 20;
}
}
private class ThemeColorRenderer extends AbstractGColumnRenderer<ColorValue> {
private GThemeValueMap valueMap;
private Supplier<GThemeValueMap> mapSupplier;
public ThemeColorRenderer(GThemeValueMap valueMap) {
this.valueMap = valueMap;
public ThemeColorRenderer(Supplier<GThemeValueMap> 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<ColorValue, O
text = "<No Value>";
}
label.setText(text);
label.setBackground(color);
label.setForeground(ColorUtils.contrastForegroundColor(color));
label.setIcon(new SwatchIcon(color, label.getForeground()));
// label.setBackground(color);
// label.setForeground(ColorUtils.contrastForegroundColor(color));
label.setOpaque(true);
return label;
}
@@ -184,4 +208,33 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel<ColorValue, O
}
}
static class SwatchIcon implements Icon {
private Color color;
private Color border;
SwatchIcon(Color c, Color border) {
this.color = c;
this.border = border;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(color);
g.fillRect(x, y, 25, 16);
g.setColor(border);
g.drawRect(x, y, 25, 16);
}
@Override
public int getIconWidth() {
return 25;
}
@Override
public int getIconHeight() {
return 16;
}
}
}
@@ -0,0 +1,35 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.theme.laf;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import com.formdev.flatlaf.FlatLaf;
public class FlatLookAndFeelInstaller extends LookAndFeelInstaller {
private FlatLaf lookAndFeel;
public FlatLookAndFeelInstaller(FlatLaf lookAndFeel) {
this.lookAndFeel = lookAndFeel;
}
@Override
protected void installLookAndFeel() throws UnsupportedLookAndFeelException {
UIManager.setLookAndFeel(lookAndFeel);
}
}
@@ -0,0 +1,65 @@
/* ###
* 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.UnsupportedLookAndFeelException;
import javax.swing.plaf.nimbus.NimbusLookAndFeel;
import docking.theme.*;
import ghidra.docking.util.LookAndFeelUtils;
public class GDKLookAndFeelInstaller extends LookAndFeelInstaller {
@Override
protected void installLookAndFeel() throws UnsupportedLookAndFeelException {
}
/**
* Extends the NimbusLookAndFeel to intercept the {@link #getDefaults()}. To get Nimbus
* to use our indirect values, we have to get in early.
*/
static class ExtendedGDKKLookAndFeel extends NimbusLookAndFeel {
@Override
public UIDefaults getDefaults() {
GThemeValueMap javaDefaults = new GThemeValueMap();
UIDefaults defaults = super.getDefaults();
List<String> 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;
}
}
}
@@ -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();
}
}
@@ -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<String> 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);
// }
}
}
@@ -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());
}
}
@@ -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<String> 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;
}
}
}
@@ -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;
@@ -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;
}
@@ -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<String> colorKeys = getLookAndFeelIdsForType(Color.class);
List<String> 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<String> getLookAndFeelIdsForType(Class<?> clazz) {
UIDefaults defaults = UIManager.getDefaults();
public static List<String> getLookAndFeelIdsForType(UIDefaults defaults, Class<?> clazz) {
List<String> colorKeys = new ArrayList<>();
for (Entry<?, ?> entry : defaults.entrySet()) {
Object key = entry.getKey();
List<Object> 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();
}
}
@@ -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
}
@@ -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;
}
}
@@ -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 <T> The type of objects stored in this WeakStore
*/
public class WeakStore<T> {
protected ReferenceQueue<T> refQueue;
private Link<T> first;
private Link<T> 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<T> getValues() {
processQueue();
List<T> values = new ArrayList<>();
for (Link<T> 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<T> 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<T> ref;
while ((ref = (Link<T>) refQueue.poll()) != null) {
remove(ref);
}
}
private void remove(Link<T> 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<T> extends WeakReference<T> {
private Link<T> nextLink;
private Link<T> previousLink;
public Link(Link<T> previous, T value, Link<T> next, ReferenceQueue<T> refQueue) {
super(value, refQueue);
this.nextLink = next;
this.previousLink = previous;
}
}
}
@@ -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);
}
}
@@ -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<Foo> store = new WeakStore<>();
store.add(new Foo("AAA"));
store.add(new Foo("BBB"));
store.add(new Foo("CCC"));
assertEquals(3, store.size());
List<Foo> 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;
}
}
}