GP-1981 - Checkpoint - Help System Fixes; fixed loading order bug

This commit is contained in:
dragonmacher
2022-07-20 18:04:27 -04:00
committed by ghidragon
parent 164fe7b5b6
commit 7974e2de06
65 changed files with 2948 additions and 3860 deletions
@@ -37,6 +37,11 @@ color.bg.markerservice = color.bg
color.bg.search.highlight = rgb(255,255,200)
color.bg.search.current-line.highlight = yellow
color.bg.tree.renderer.icon.fill = #9F9FFF
color.bg.tree.renderer.icon.line = #8282FF
[Dark Defaults]
color.bg = rgb(40, 42, 46) // TODO this should be in a more generic module
@@ -19,10 +19,15 @@ import java.awt.*;
import javax.swing.Icon;
import docking.theme.GColor;
import docking.theme.GThemeDefaults.Colors.Palette;
class BackgroundIcon implements Icon {
private static Color VERSION_ICON_COLOR_DARK = new Color(0x82, 0x82, 0xff);
private static Color VERSION_ICON_COLOR_LIGHT = new Color(0x9f, 0x9f, 0xff);
private static Color VERSION_ICON_COLOR_LINE = new GColor("color.bg.tree.renderer.icon.line");
private static Color VERSION_ICON_COLOR_LIGHT = new GColor("color.bg.tree.renderer.icon.fill");
private static Color ALPHA = Palette.NO_COLOR;
private int width;
private int height;
@@ -49,14 +54,14 @@ class BackgroundIcon implements Icon {
if (isVersioned) {
g.setColor(VERSION_ICON_COLOR_LIGHT);
g.fillRect(x + 1, y + 1, width - 2, height - 2);
g.setColor(VERSION_ICON_COLOR_DARK);
g.setColor(VERSION_ICON_COLOR_LINE);
g.drawLine(x + 1, y, x + width - 2, y);
g.drawLine(x + width - 1, y + 1, x + width - 1, y + height - 2);
g.drawLine(x + 1, y + height - 1, x + width - 2, y + height - 1);
g.drawLine(x, y + 1, x, y + height - 2);
}
else {
g.setColor(c.getBackground());
g.setColor(ALPHA);
g.fillRect(x, y, width, height);
}
}
@@ -335,7 +335,7 @@ public class DataTypeArchiveGTree extends GTree {
// work around an issue on some platforms where the label is painting a color that
// does not match the tree
label.setBackground(
isSelected ? getBackgroundSelectionColor() : tree.getBackground());
isSelected ? getBackgroundSelectionColor() : getBackgroundNonSelectionColor());
}
MultiIcon multiIcon = new MultiIcon(
@@ -362,7 +362,6 @@ public class DataTypeArchiveGTree extends GTree {
}
setIcon(multiIcon);
return label;
}
@@ -15,23 +15,15 @@
*/
package ghidra.app.plugin.gui;
import java.util.*;
import java.util.stream.Collectors;
import docking.action.builder.ActionBuilder;
import docking.options.editor.StringWithChoicesEditor;
import docking.theme.GTheme;
import docking.theme.Gui;
import docking.theme.gui.GThemeDialog;
import docking.tool.ToolConstants;
import docking.theme.gui.ThemeDialog;
import ghidra.app.CorePluginPackage;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.framework.main.FrontEndOnly;
import ghidra.framework.main.FrontEndTool;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.util.*;
import ghidra.util.SystemUtilities;
//@formatter:off
@PluginInfo(
@@ -43,93 +35,26 @@ import ghidra.util.*;
"This plugin is available only in the Ghidra Project Window."
)
//@formatter:on
public class ThemeManagerPlugin extends Plugin implements FrontEndOnly, OptionsChangeListener {
public final static String THEME_OPTIONS_NAME = "Theme";
private final static String OPTIONS_TITLE = ToolConstants.TOOL_OPTIONS;
private boolean issuedRestartNotification;
// private static boolean issuedPreferredDarkThemeLafNotification;
public class ThemeManagerPlugin extends Plugin implements FrontEndOnly {
public ThemeManagerPlugin(PluginTool tool) {
super(tool);
SystemUtilities.assertTrue(tool instanceof FrontEndTool,
"Plugin added to the wrong type of tool");
initThemeOptions();
}
@Override
protected void init() {
new ActionBuilder("Show Properties", getName()).menuPath("Edit", "Theme Properties")
new ActionBuilder("", getName()).menuPath("Edit", "Theme")
.onAction(e -> showThemeProperties())
.buildAndInstall(tool);
}
private void showThemeProperties() {
GThemeDialog dialog = new GThemeDialog();
tool.showDialog(dialog);
ThemeDialog.editTheme();
}
private void initThemeOptions() {
ToolOptions opt = tool.getOptions(OPTIONS_TITLE);
GTheme activeTheme = Gui.getActiveTheme();
Set<GTheme> themes = Gui.getSupportedThemes();
List<String> themeNames =
themes.stream().map(t -> t.getName()).collect(Collectors.toList());
Collections.sort(themeNames);
opt.registerOption(THEME_OPTIONS_NAME, OptionType.STRING_TYPE, activeTheme.getName(),
new HelpLocation(ToolConstants.TOOL_HELP_TOPIC, "Look_And_Feel"),
"Set the look and feel for Ghidra. After you change the " +
"look and feel, you will have to restart Ghidra to see the effect.",
new StringWithChoicesEditor(themeNames));
opt.addOptionsChangeListener(this);
}
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
if (optionName.equals(THEME_OPTIONS_NAME)) {
String newThemeName = (String) newValue;
if (!newThemeName.equals(Gui.getActiveTheme().getName())) {
issueRestartNeededMessage();
}
saveLookAndFeel((String) newValue);
}
}
private void saveLookAndFeel(String themeName) {
Set<GTheme> allThemes = Gui.getAllThemes();
for (GTheme theme : allThemes) {
if (theme.getName().equals(themeName)) {
Gui.saveThemeToPreferneces(theme);
}
}
}
private void issueRestartNeededMessage() {
if (issuedRestartNotification) {
return;
}
issuedRestartNotification = true;
Msg.showInfo(getClass(), null, "Look And Feel Updated",
"The new Look and Feel will take effect \nafter you exit and restart Ghidra.");
}
@Override
public void dispose() {
ToolOptions opt = tool.getOptions(OPTIONS_TITLE);
opt.removeOptionsChangeListener(this);
super.dispose();
}
}
@@ -26,6 +26,7 @@ import org.junit.*;
import docking.ActionContext;
import docking.action.DockingActionIf;
import docking.theme.GThemeDefaults.Colors.Palette;
import edu.uci.ics.jung.graph.Graph;
import ghidra.app.plugin.core.clear.ClearPlugin;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
@@ -485,7 +486,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
FGVertex v2 = vertex("01002d0f");
// color just one of the vertices
Color newColor = Color.RED;
Color newColor = Palette.RED;
color(v1, newColor);
GroupedFunctionGraphVertex group = group("A", v1, v2);
@@ -527,7 +528,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
FGVertex v2 = vertex("01002d0f");
// color just one of the vertices
Color newColor = Color.RED;
Color newColor = Palette.RED;
color(v1, newColor);
color(v2, newColor);
@@ -564,7 +565,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
//
// Change the group color
//
Color newGroupColor = Color.CYAN;
Color newGroupColor = Palette.CYAN;
color(group, newGroupColor);
//
@@ -591,7 +592,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
FGVertex v2 = vertex("01002d0f");
// color just one of the vertices
Color newColor = Color.RED;
Color newColor = Palette.RED;
color(v1, newColor);
GroupedFunctionGraphVertex group = group("A", v1, v2);
@@ -599,7 +600,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
//
// Change the group color
//
Color newGroupColor = Color.CYAN;
Color newGroupColor = Palette.CYAN;
color(group, newGroupColor);
//
@@ -625,7 +626,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
FGVertex v2 = vertex("01002d0f");
// color just one of the vertices
Color newColor = Color.RED;
Color newColor = Palette.RED;
color(v1, newColor);
color(v2, newColor);
@@ -634,7 +635,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
//
// Change the group color
//
Color newGroupColor = Color.CYAN;
Color newGroupColor = Palette.CYAN;
color(group, newGroupColor);
//
@@ -663,7 +664,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
//
// Change the group color
//
Color newGroupColor = Color.CYAN;
Color newGroupColor = Palette.CYAN;
color(group, newGroupColor);
//
@@ -738,7 +739,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
//
// Color just one of the vertices
//
Color newColor = Color.RED;
Color newColor = Palette.RED;
color(v1, newColor);
//
@@ -782,7 +783,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
//
// Color just one of the vertices
//
Color newColor = Color.RED;
Color newColor = Palette.RED;
color(v1, newColor);
//
@@ -32,6 +32,7 @@ import docking.ActionContext;
import docking.ComponentProvider;
import docking.action.DockingAction;
import docking.dnd.GClipboard;
import docking.theme.GThemeDefaults.Colors.Palette;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.visualization.VisualizationModel;
import edu.uci.ics.jung.visualization.VisualizationViewer;
@@ -619,7 +620,7 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest {
Color appliedBackgroundColor =
colorizingService.getBackgroundColor(focusedVertex.getVertexAddress());
Color testColor = Color.RED;
Color testColor = Palette.RED;
assertTrue("Unexpected start color--must change the test!",
!testColor.equals(appliedBackgroundColor));
@@ -15,8 +15,7 @@
*/
package ghidra.app.plugin.core.functiongraph;
import static ghidra.graph.viewer.GraphViewerUtils.getGraphScale;
import static ghidra.graph.viewer.GraphViewerUtils.getPointInViewSpaceForVertex;
import static ghidra.graph.viewer.GraphViewerUtils.*;
import static org.junit.Assert.*;
import java.awt.Color;
@@ -28,6 +27,7 @@ import javax.swing.JComponent;
import org.junit.*;
import docking.action.DockingActionIf;
import docking.theme.GThemeDefaults.Colors.Palette;
import edu.uci.ics.jung.graph.Graph;
import generic.test.TestUtils;
import ghidra.app.cmd.label.AddLabelCmd;
@@ -251,7 +251,7 @@ public class FunctionGraphPlugin2Test extends AbstractFunctionGraphTest {
ListingPanel listingPanel =
(ListingPanel) TestUtils.getInstanceField("listingPanel", panel);
Color startBackgrond = listingPanel.getTextBackgroundColor();
Color testColor = Color.RED;
Color testColor = Palette.RED;
assertTrue("Unexpected start color--must change the test!",
!testColor.equals(startBackgrond));
@@ -277,7 +277,7 @@ public class FunctionGraphPlugin2Test extends AbstractFunctionGraphTest {
Color appliedBackgroundColor =
colorizingService.getBackgroundColor(vertex.getVertexAddress());
Color testColor = Color.RED;
Color testColor = Palette.RED;
assertTrue("Unexpected start color--must change the test!",
!testColor.equals(appliedBackgroundColor));
@@ -21,6 +21,7 @@ src/main/help/help/shared/tip.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (
src/main/help/help/shared/undo.png||GHIDRA||||END|
src/main/help/help/shared/warning.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/help/help/topics/PlacheholderTopic/Placeholder.htm||GHIDRA||||END|
data/docking.palette.material.theme.properties||GHIDRA||||END|
data/docking.palette.theme.properties||GHIDRA||||END|
data/docking.theme.properties||GHIDRA||||END|
src/main/java/docking/dnd/package.html||GHIDRA||reviewed||END|
@@ -0,0 +1,14 @@
// TODO using this now as a placeholder for a palette/swatch
[Defaults]
color.palette.material.primary = #6200EE
color.palette.material.secondary = #03DAC6
color.palette.material.secondary.variant = #018786
[Dark Defaults]
color.palette.material.primary = #BB86FC
color.palette.material.secondary = #03DAC6
color.palette.material.secondary.variant = #018786
@@ -8,6 +8,7 @@ color.palette.crimson = crimson
color.palette.cyan = cyan
color.palette.darkblue = DarkBlue
color.palette.darkkhaki = DarkKhaki
color.palette.darkred = DarkRed
color.palette.dodgerblue = DodgerBlue
color.palette.gold = gold
color.palette.gray = gray
@@ -0,0 +1,118 @@
/* ###
* 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;
import java.awt.*;
import javax.swing.JButton;
import docking.help.HelpDescriptor;
import docking.help.HelpService;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
public class DefaultHelpService implements HelpService {
@Override
public void showHelp(Object helpObj, boolean infoOnly, Component parent) {
if (infoOnly) {
displayHelpInfo(helpObj);
return;
}
}
@Override
public void showHelp(java.net.URL url) {
// no-op
}
@Override
public void showHelp(HelpLocation location) {
// no-op
}
@Override
public void excludeFromHelp(Object helpObject) {
// no-op
}
@Override
public boolean isExcludedFromHelp(Object helpObject) {
return false;
}
@Override
public void clearHelp(Object helpObject) {
// no-op
}
@Override
public void registerHelp(Object helpObj, HelpLocation helpLocation) {
// no-op
}
@Override
public HelpLocation getHelpLocation(Object object) {
return null;
}
@Override
public boolean helpExists() {
return false;
}
@Override
public void reload() {
// no-op
}
private void displayHelpInfo(Object helpObj) {
String msg = getHelpInfo(helpObj);
Msg.showInfo(this, null, "Help Info", msg);
}
private String getHelpInfo(Object helpObj) {
if (helpObj == null) {
return "Help Object is null";
}
StringBuilder buffy = new StringBuilder();
buffy.append("HELP OBJECT: " + helpObj.getClass().getName());
buffy.append("\n");
if (helpObj instanceof HelpDescriptor) {
HelpDescriptor helpDescriptor = (HelpDescriptor) helpObj;
buffy.append(helpDescriptor.getHelpInfo());
}
else if (helpObj instanceof JButton) {
JButton button = (JButton) helpObj;
buffy.append(" BUTTON: " + button.getText());
buffy.append("\n");
Component c = button;
while (c != null && !(c instanceof Window)) {
c = c.getParent();
}
if (c instanceof Dialog) {
buffy.append(" DIALOG: " + ((Dialog) c).getTitle());
buffy.append("\n");
}
if (c instanceof Frame) {
buffy.append(" FRAME: " + ((Frame) c).getTitle());
buffy.append("\n");
}
}
return buffy.toString();
}
}
@@ -16,7 +16,6 @@
package docking.action;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -28,15 +27,16 @@ import javax.swing.tree.TreePath;
import org.apache.commons.lang3.StringUtils;
import docking.*;
import docking.ActionContext;
import docking.DockingWindowManager;
import ghidra.util.Msg;
import ghidra.util.ReservedKeyBindings;
public class ComponentThemeInspectorAction extends DockingAction {
public ComponentThemeInspectorAction() {
super("Component Theme Inspector", DockingWindowManager.DOCKING_WINDOWS_OWNER, false);
createReservedKeyBinding(
KeyStroke.getKeyStroke(KeyEvent.VK_F9, DockingUtils.CONTROL_KEY_MODIFIER_MASK));
createReservedKeyBinding(ReservedKeyBindings.COMPONENT_THEME_INFO_KEY);
// System action; no help needed
DockingWindowManager.getHelpService().excludeFromHelp(this);
@@ -82,6 +82,7 @@ public class ShowFocusInfoAction extends DockingAction {
Object mouseOverObject = DockingWindowManager.getMouseOverObject();
if (mouseOverObject instanceof Component) {
log.info("Mouse-over Object: " + printComp((Component) mouseOverObject));
log.info("Focusable?: " + ((Component) mouseOverObject).isFocusable());
}
log.info("");
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -28,11 +28,9 @@ import java.util.Map.Entry;
import javax.help.*;
import javax.help.Map.ID;
import javax.swing.JButton;
import javax.swing.UIManager;
import docking.ComponentProvider;
import docking.action.DockingActionIf;
import docking.theme.GColor;
import generic.concurrent.GThreadPool;
import generic.util.WindowUtilities;
import ghidra.util.*;
@@ -89,8 +87,6 @@ public class HelpManager implements HelpService {
mainHB = mainHS.createHelpBroker();
mainHS.setTitle(GHIDRA_HELP_TITLE);
setColorResources();
isValidHelp = isValidHelp();
}
@@ -196,6 +192,18 @@ public class HelpManager implements HelpService {
return mainHS;
}
@Override
public void reload() {
if (!(mainHB instanceof GHelpBroker)) {
// not our broker installed; can't force a reload
return;
}
GHelpBroker gHelpBroker = (GHelpBroker) mainHB;
gHelpBroker.reload();
}
@Override
public void showHelp(URL url) {
if (!isValidHelp) {
@@ -693,15 +701,6 @@ public class HelpManager implements HelpService {
return null;
}
/**
* Set the color resources on the JEditorPane for selection so that
* you can see the highlights when you do a search in the JavaHelp.
*/
private void setColorResources() {
UIManager.put("EditorPane.selectionBackground", new GColor("color.bg.selection.help"));
UIManager.put("EditorPane.selectionForeground", UIManager.get("EditorPane.foreground"));
}
private void displayHelpInfo(Object helpObj, HelpLocation loc, Window parent) {
String msg = getHelpInfo(helpObj, loc);
Msg.showInfo(this, parent, "Help Info", msg);
@@ -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 docking.help;
import java.awt.Component;
import java.net.URL;
import ghidra.util.HelpLocation;
/**
* <code>HelpService</code> defines a service for displaying Help content by an ID or URL.
*/
public interface HelpService {
public static final String DUMMY_HELP_SET_NAME = "Dummy_HelpSet.hs";
/**
* Display the Help content identified by the help object.
*
* @param helpObject the object to which help was previously registered
* @param infoOnly display {@link HelpLocation} information only, not the help UI
* @param parent requesting component
*
* @see #registerHelp(Object, HelpLocation)
*/
public void showHelp(Object helpObject, boolean infoOnly, Component parent);
/**
* Display the help page for the given URL. This is a specialty method for displaying
* help when a specific file is desired, like an introduction page. Showing help for
* objects within the system is accomplished by calling
* {@link #showHelp(Object, boolean, Component)}.
*
* @param url the URL to display
* @see #showHelp(Object, boolean, Component)
*/
public void showHelp(URL url);
/**
* Display the help page for the given help location.
*
* @param location the location to display.
* @see #showHelp(Object, boolean, Component)
*/
public void showHelp(HelpLocation location);
/**
* Signals to the help system to ignore the given object when searching for and validating
* help. Once this method has been called, no help can be registered for the given object.
*
* @param helpObject the object to exclude from the help system.
*/
public void excludeFromHelp(Object helpObject);
/**
* Returns true if the given object is meant to be ignored by the help system
*
* @param helpObject the object to check
* @return true if ignored
* @see #excludeFromHelp(Object)
*/
public boolean isExcludedFromHelp(Object helpObject);
/**
* Register help for a specific object.
*
* <P>Do not call this method will a <code>null</code> help location. Instead, to signal that
* an item has no help, call {@link #excludeFromHelp(Object)}.
*
* @param helpObject the object to associate the specified help location with
* @param helpLocation help content location
*/
public void registerHelp(Object helpObject, HelpLocation helpLocation);
/**
* Removes this object from the help system. This method is useful, for example,
* when a single Java {@link Component} will have different help locations
* assigned over its lifecycle.
*
* @param helpObject the object for which to clear help
*/
public void clearHelp(Object helpObject);
/**
* Returns the registered (via {@link #registerHelp(Object, HelpLocation)} help
* location for the given object; null if there is no registered
* help.
*
* @param object The object for which to find a registered HelpLocation.
* @return the registered HelpLocation
* @see #registerHelp(Object, HelpLocation)
*/
public HelpLocation getHelpLocation(Object object);
/**
* Returns true if the help system has been initialized properly; false if help does not
* exist or is not working.
*
* @return true if the help system has found the applications help content and has finished
* initializing
*/
public boolean helpExists();
/**
* Called when a major system even happens, such as changing the system theme.
*/
public void reload();
}
@@ -18,6 +18,6 @@ package docking.theme;
public class DefaultTheme extends DiscoverableGTheme {
public DefaultTheme() {
super("Default", LookAndFeelType.SYSTEM);
super("Default", LafType.SYSTEM);
}
}
@@ -20,12 +20,8 @@ import ghidra.util.classfinder.ExtensionPoint;
public abstract class DiscoverableGTheme extends GTheme implements ExtensionPoint {
static final String CLASS_PREFIX = "Class:";
protected DiscoverableGTheme(String name, LookAndFeelType lookAndFeel) {
super(name, lookAndFeel, false);
}
protected DiscoverableGTheme(String name, LookAndFeelType lookAndFeel, boolean isDark) {
super(name, lookAndFeel, isDark);
protected DiscoverableGTheme(String name, LafType lookAndFeel) {
super(name, lookAndFeel);
}
@Override
@@ -15,8 +15,13 @@
*/
package docking.theme;
import java.io.File;
import java.io.IOException;
import java.awt.Color;
import java.awt.Font;
import java.io.*;
import java.util.Collections;
import java.util.List;
import ghidra.util.WebColors;
public class FileGTheme extends GTheme {
public static final String FILE_PREFIX = "File:";
@@ -26,8 +31,13 @@ public class FileGTheme extends GTheme {
this(file, new ThemeReader(file));
}
public FileGTheme(File file, String name, LafType laf) {
super(name, laf);
this.file = file;
}
FileGTheme(File file, ThemeReader reader) {
super(reader.getThemeName(), reader.getLookAndFeelType(), reader.isDark());
super(reader.getThemeName(), reader.getLookAndFeelType());
this.file = file;
reader.loadValues(this);
}
@@ -37,4 +47,93 @@ public class FileGTheme extends GTheme {
return FILE_PREFIX + file.getAbsolutePath();
}
public boolean canSave() {
return file.canWrite();
}
public File getFile() {
return file;
}
public void save() throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
List<ColorValue> colors = getColors();
Collections.sort(colors);
List<FontValue> fonts = getFonts();
Collections.sort(fonts);
List<IconValue> icons = getIcons();
Collections.sort(icons);
writer.write(THEME_NAME_KEY + " = " + getName());
writer.newLine();
writer.write(THEME_LOOK_AND_FEEL_KEY + " = " + getLookAndFeelType().getName());
writer.newLine();
for (ColorValue colorValue : colors) {
String outputId = colorValue.toExternalId(colorValue.getId());
writer.write(outputId + " = " + getValueOutput(colorValue));
writer.newLine();
}
for (FontValue fontValue : fonts) {
String outputId = fontValue.toExternalId(fontValue.getId());
writer.write(outputId + " = " + getValueOutput(fontValue));
writer.newLine();
}
for (IconValue iconValue : icons) {
String outputId = iconValue.toExternalId(iconValue.getId());
writer.write(outputId + " = " + getValueOutput(iconValue));
writer.newLine();
}
}
}
private String getValueOutput(ColorValue colorValue) {
if (colorValue.getReferenceId() != null) {
return colorValue.toExternalId(colorValue.getReferenceId());
}
Color color = colorValue.getRawValue();
String outputString = WebColors.toString(color, false);
String colorName = WebColors.toWebColorName(color);
if (colorName != null) {
outputString += " // " + colorName;
}
return outputString;
}
private String getValueOutput(IconValue iconValue) {
if (iconValue.getReferenceId() != null) {
return iconValue.toExternalId(iconValue.getReferenceId());
}
return iconValue.getRawValue();
}
private String getValueOutput(FontValue fontValue) {
if (fontValue.getReferenceId() != null) {
return fontValue.toExternalId(fontValue.getReferenceId());
}
Font font = fontValue.getRawValue();
return String.format("%s-%s-%s", font.getName(), getStyleString(font), font.getSize());
}
private String getStyleString(Font font) {
boolean bold = font.isBold();
boolean italic = font.isItalic();
if (bold && italic) {
return "BOLDITALIC";
}
if (bold) {
return "BOLD";
}
if (italic) {
return "ITALIC";
}
return "PLAIN";
}
}
@@ -17,26 +17,25 @@ package docking.theme;
import java.awt.Color;
import java.awt.Font;
import java.io.*;
import java.util.*;
import ghidra.util.WebColors;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
/**
* Class to store all the configurable appearance properties (Colors, Fonts, Icons, Look and Feel)
* in an application.
*/
public class GTheme extends GThemeValueMap {
public static String FILE_EXTENSION = ".theme";
static final String THEME_NAME_KEY = "name";
static final String THEME_LOOK_AND_FEEL_KEY = "lookAndFeel";
static final String THEME_IS_DARK_KEY = "dark";
private final String name;
private final LookAndFeelType lookAndFeel;
private final boolean isDark;
private final LafType lookAndFeel;
public GTheme(String name) {
this(name, LookAndFeelType.SYSTEM, false);
this(name, LafType.SYSTEM);
}
@@ -44,13 +43,10 @@ public class GTheme extends GThemeValueMap {
* Creates a new empty GTheme with the given name
* @param name the name for the new GTheme
* @param lookAndFeel the look and feel type used by this theme
* @param isDark true if this theme uses dark backgrounds instead of the standard
* light backgrounds
*/
protected GTheme(String name, LookAndFeelType lookAndFeel, boolean isDark) {
protected GTheme(String name, LafType lookAndFeel) {
this.name = name;
this.lookAndFeel = lookAndFeel;
this.isDark = isDark;
}
/**
@@ -65,7 +61,7 @@ public class GTheme extends GThemeValueMap {
* Returns the name of the LookAndFeel associated with this GTheme
* @return the name of the LookAndFeel associated with this GTheme
*/
public LookAndFeelType getLookAndFeelType() {
public LafType getLookAndFeelType() {
return lookAndFeel;
}
@@ -74,7 +70,7 @@ public class GTheme extends GThemeValueMap {
* @return true if this theme should use dark defaults
*/
public boolean isDark() {
return isDark;
return lookAndFeel.isDark();
}
/**
@@ -161,123 +157,21 @@ public class GTheme extends GThemeValueMap {
return false;
}
GTheme other = (GTheme) obj;
return Objects.equals(name, other.name) && Objects.equals(lookAndFeel, other.lookAndFeel) &&
Objects.equals(isDark, other.isDark);
}
/**
* Creates a new file based GTheme with the same values as this GTheme
* @param saveToFile file to associate and save this GTheme to
* @return the new theme
* @throws IOException if a general I/O exception occurs
*/
public GTheme saveToFile(File saveToFile) throws IOException {
return doSaveToFile(saveToFile, this);
}
/**
* Creates a new file based GTheme with the same values as this GTheme and includes default
* values not modified by this theme.
* @param saveToFile file to associate and save this GTheme to
* @param defaults the collection of default values to include in the output file
* @return the new theme
* @throws IOException if a general I/O exception occurs
*/
public GTheme saveToFile(File saveToFile, GThemeValueMap defaults) throws IOException {
GThemeValueMap combined = new GThemeValueMap();
combined.load(defaults);
combined.load(this);
return doSaveToFile(saveToFile, combined);
}
private GTheme doSaveToFile(File saveToFile, GThemeValueMap values) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(saveToFile))) {
List<ColorValue> colors = values.getColors();
Collections.sort(colors);
List<FontValue> fonts = values.getFonts();
Collections.sort(fonts);
List<IconValue> icons = values.getIcons();
Collections.sort(icons);
writer.write(THEME_NAME_KEY + " = " + name);
writer.newLine();
writer.write(THEME_LOOK_AND_FEEL_KEY + " = " + lookAndFeel.getName());
writer.newLine();
if (isDark()) {
writer.write(THEME_IS_DARK_KEY + " = true");
writer.newLine();
}
for (ColorValue colorValue : colors) {
String outputId = colorValue.toExternalId(colorValue.getId());
writer.write(outputId + " = " + getValueOutput(colorValue));
writer.newLine();
}
for (FontValue fontValue : fonts) {
String outputId = fontValue.toExternalId(fontValue.getId());
writer.write(outputId + " = " + getValueOutput(fontValue));
writer.newLine();
}
for (IconValue iconValue : icons) {
String outputId = iconValue.toExternalId(iconValue.getId());
writer.write(outputId + " = " + getValueOutput(iconValue));
writer.newLine();
}
}
return new FileGTheme(saveToFile);
}
private String getValueOutput(IconValue iconValue) {
if (iconValue.getReferenceId() != null) {
return iconValue.toExternalId(iconValue.getReferenceId());
}
return iconValue.getRawValue();
}
private String getValueOutput(FontValue fontValue) {
if (fontValue.getReferenceId() != null) {
return fontValue.toExternalId(fontValue.getReferenceId());
}
Font font = fontValue.getRawValue();
return String.format("%s-%s-%s", font.getName(), getStyleString(font), font.getSize());
}
private String getStyleString(Font font) {
boolean bold = font.isBold();
boolean italic = font.isItalic();
if (bold && italic) {
return "BOLDITALIC";
}
if (bold) {
return "BOLD";
}
if (italic) {
return "ITALIC";
}
return "PLAIN";
}
private String getValueOutput(ColorValue colorValue) {
if (colorValue.getReferenceId() != null) {
return colorValue.toExternalId(colorValue.getReferenceId());
}
Color color = colorValue.getRawValue();
String outputString = WebColors.toString(color, false);
String colorName = WebColors.toWebColorName(color);
if (colorName != null) {
outputString += " // " + colorName;
}
return outputString;
return Objects.equals(name, other.name) && Objects.equals(lookAndFeel, other.lookAndFeel);
}
public boolean hasSupportedLookAndFeel() {
return lookAndFeel.isSupported();
}
public FileGTheme saveToFile(File file, boolean includeDefaults) throws IOException {
FileGTheme fileTheme = new FileGTheme(file, name, lookAndFeel);
if (includeDefaults) {
fileTheme.load(Gui.getDefaults());
}
fileTheme.load(this);
fileTheme.save();
return fileTheme;
}
}
@@ -65,6 +65,7 @@ public class GThemeDefaults {
public static final GColor GOLD = new GColor("color.palette.gold");
public static final GColor GRAY = new GColor("color.palette.gray");
public static final GColor GREEN = new GColor("color.palette.green");
public static final GColor LIGHT_GRAY = new GColor("color.palette.lightgray");
public static final GColor LIME = new GColor("color.palette.lime");
public static final GColor ORANGE = new GColor("color.palette.orange");
public static final GColor PINK = new GColor("color.palette.pink");
@@ -99,4 +99,32 @@ public class GThemeValueMap {
fontMap.clear();
iconMap.clear();
}
public boolean isEmpty() {
return colorMap.isEmpty() && fontMap.isEmpty() && iconMap.isEmpty();
}
public void removeColor(String id) {
colorMap.remove(id);
}
public GThemeValueMap removeSameValues(GThemeValueMap defaults) {
GThemeValueMap map = new GThemeValueMap();
for (ColorValue color : colorMap.values()) {
if (!color.equals(defaults.getColor(color.getId()))) {
map.addColor(color);
}
}
for (FontValue font : fontMap.values()) {
if (!font.equals(defaults.getFont(font.getId()))) {
map.addFont(font);
}
}
for (IconValue icon : iconMap.values()) {
if (!icon.equals(defaults.getIcon(icon.getId()))) {
map.addIconPath(icon);
}
}
return map;
}
}
@@ -23,7 +23,11 @@ import java.util.List;
import javax.swing.*;
import javax.swing.plaf.UIResource;
import com.formdev.flatlaf.*;
import docking.framework.ApplicationInformationDisplayFactory;
import docking.help.Help;
import docking.theme.builtin.JavaColorMapping;
import ghidra.framework.Application;
import ghidra.framework.preferences.Preferences;
import ghidra.util.Msg;
@@ -41,13 +45,11 @@ public class Gui {
private static GTheme activeTheme = new DefaultTheme();
private static Set<GTheme> allThemes;
private static GThemeValueMap ghidraCoreDefaults = new GThemeValueMap();
private static GThemeValueMap originalJavaDefaults;
// private static GThemeValueMap convertedJavaDefaults;
private static GThemeValueMap ghidraLightDefaults = new GThemeValueMap();
private static GThemeValueMap ghidraDarkDefaults = new GThemeValueMap();
private static GThemeValueMap javaDefaults = new GThemeValueMap();
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<>();
@@ -63,91 +65,75 @@ public class Gui {
}
public static void initialize() {
loadThemeDefaults();
installFlatLookAndFeels();
loadGhidraDefaults();
setTheme(getThemeFromPreferences());
// LookAndFeelUtils.installGlobalOverrides();
platformSpecificFixups();
}
private static void loadThemeDefaults() {
themePropertiesLoader.load();
ghidraCoreDefaults = themePropertiesLoader.getDefaults();
darkDefaults = themePropertiesLoader.getDarkDefaults();
private static void installFlatLookAndFeels() {
UIManager.installLookAndFeel(LafType.FLAT_LIGHT.getName(), FlatLightLaf.class.getName());
UIManager.installLookAndFeel(LafType.FLAT_DARK.getName(), FlatDarkLaf.class.getName());
UIManager.installLookAndFeel(LafType.FLAT_DARCULA.getName(),
FlatDarculaLaf.class.getName());
}
public static void reloadThemeDefaults() {
loadThemeDefaults();
currentValues = buildCurrentValues(activeTheme);
refresh();
private static void loadGhidraDefaults() {
themePropertiesLoader.load();
ghidraLightDefaults = themePropertiesLoader.getDefaults();
ghidraDarkDefaults = themePropertiesLoader.getDarkDefaults();
}
public static void reloadGhidraDefaults() {
loadGhidraDefaults();
buildCurrentValues();
}
public static void restoreThemeValues() {
buildCurrentValues();
}
public static void setTheme(GTheme theme) {
if (theme.hasSupportedLookAndFeel()) {
activeTheme = theme;
LookAndFeelType lookAndFeel = theme.getLookAndFeelType();
LafType lookAndFeel = theme.getLookAndFeelType();
try {
lookAndFeel.install();
saveThemeToPreferences(theme);
fixupJavaDefaults();
// The help may produce errors when switching the theme, such as if there is an
// active search in the help. We have added this call to allow the help system
// to cleanup some internal state.
Help.getHelpService().reload();
buildCurrentValues();
updateUIs();
}
catch (Exception e) {
Msg.error(Gui.class, "Error setting LookAndFeel: " + lookAndFeel.getName());
Msg.error(Gui.class, "Error setting LookAndFeel: " + lookAndFeel.getName(), e);
}
refresh();
}
}
private static void refresh() {
GColor.refreshAll();
public static void addTheme(GTheme newTheme) {
allThemes.remove(newTheme);
allThemes.add(newTheme);
}
private static void updateUIs() {
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 originalJavaDefaults.containsColor(id);
return javaDefaults.containsColor(id);
}
public static GThemeValueMap getAllValues() {
return new GThemeValueMap(currentValues);
}
public static GThemeValueMap getAllDefaultValues() {
GThemeValueMap currentDefaults = new GThemeValueMap();
currentDefaults.load(originalJavaDefaults);
currentDefaults.load(ghidraCoreDefaults);
if (activeTheme.isDark()) {
currentDefaults.load(darkDefaults);
}
return currentDefaults;
}
public static Set<GTheme> getAllThemes() {
if (allThemes == null) {
allThemes = findThemes();
@@ -196,7 +182,7 @@ public class Gui {
return new GIcon(id);
}
public static void saveThemeToPreferneces(GTheme theme) {
public static void saveThemeToPreferences(GTheme theme) {
Preferences.setProperty(THEME_PREFFERENCE_KEY, theme.getThemeLocater());
Preferences.store();
}
@@ -205,7 +191,7 @@ public class Gui {
return activeTheme;
}
public static LookAndFeelType getLookAndFeelType() {
public static LafType getLookAndFeelType() {
return activeTheme.getLookAndFeelType();
}
@@ -269,16 +255,18 @@ public class Gui {
return t;
}
private static GThemeValueMap buildCurrentValues(GTheme theme) {
private static void buildCurrentValues() {
GThemeValueMap map = new GThemeValueMap();
map.load(originalJavaDefaults);
map.load(ghidraCoreDefaults);
if (theme.isDark()) {
map.load(darkDefaults);
map.load(javaDefaults);
map.load(ghidraLightDefaults);
if (activeTheme.isDark()) {
map.load(ghidraDarkDefaults);
}
map.load(theme);
return map;
map.load(activeTheme);
currentValues = map;
GColor.refreshAll();
repaintAll();
}
private static Color getUIColor(String id) {
@@ -306,7 +294,7 @@ public class Gui {
List<File> fileList = new ArrayList<>();
File dir = Application.getUserSettingsDirectory();
FileFilter themeFileFilter = file -> file.getName().endsWith(".theme");
FileFilter themeFileFilter = file -> file.getName().endsWith(GTheme.FILE_EXTENSION);
fileList.addAll(Arrays.asList(dir.listFiles(themeFileFilter)));
List<GTheme> list = new ArrayList<>();
@@ -359,29 +347,19 @@ public class Gui {
return new DefaultTheme();
}
public static GThemeValueMap getCoreDefaults() {
GThemeValueMap map = new GThemeValueMap(ghidraCoreDefaults);
map.load(originalJavaDefaults);
return map;
}
public static GThemeValueMap getDarkDefaults() {
GThemeValueMap map = new GThemeValueMap(ghidraCoreDefaults);
map.load(darkDefaults);
return map;
}
public static void setColor(String id, Color color) {
setColor(new ColorValue(id, color));
}
public static void setColor(ColorValue colorValue) {
currentValues.addColor(colorValue);
System.out.println("Change color: " + colorValue);
GColor.refreshAll();
repaintAll();
}
private static void repaintAll() {
for (Window window : Window.getWindows()) {
window.repaint();
// SynthLookAndFeel.updateStyles(window);
}
}
@@ -394,13 +372,45 @@ public class Gui {
return gColor;
}
public static void setJavaDefaults(GThemeValueMap javaDefaults) {
originalJavaDefaults = javaDefaults;
currentValues = buildCurrentValues(activeTheme);
public static void setJavaDefaults(GThemeValueMap map) {
javaDefaults = map;
buildCurrentValues();
}
public static void fixupJavaDefaults() {
List<ColorValue> colors = javaDefaults.getColors();
JavaColorMapping mapping = new JavaColorMapping();
for (ColorValue value : colors) {
ColorValue mapped = mapping.map(javaDefaults, value);
if (mapped != null) {
javaDefaults.addColor(mapped);
}
}
}
public static GThemeValueMap getJavaDefaults() {
return originalJavaDefaults;
GThemeValueMap map = new GThemeValueMap();
map.load(javaDefaults);
return map;
}
public static GThemeValueMap getGhidraDarkDefaults() {
GThemeValueMap map = new GThemeValueMap(ghidraLightDefaults);
map.load(ghidraDarkDefaults);
return map;
}
public static GThemeValueMap getGhidraLightDefaults() {
GThemeValueMap map = new GThemeValueMap(ghidraLightDefaults);
return map;
}
public static GThemeValueMap getDefaults() {
GThemeValueMap currentDefaults = new GThemeValueMap(javaDefaults);
currentDefaults.load(ghidraLightDefaults);
if (activeTheme.isDark()) {
currentDefaults.load(ghidraDarkDefaults);
}
return currentDefaults;
}
}
@@ -0,0 +1,123 @@
/* ###
* 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.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import docking.theme.laf.*;
import ghidra.framework.OperatingSystem;
import ghidra.framework.Platform;
import ghidra.util.exception.AssertException;
public enum LafType {
METAL("Metal", false),
NIMBUS("Nimbus", false),
GTK("GTK+", false),
MOTIF("CDE/Motif", false),
FLAT_LIGHT("Flat Light", false),
FLAT_DARK("Flat Dark", true),
FLAT_DARCULA("Flat Darcula", true),
WINDOWS("Windows", false),
WINDOWS_CLASSIC("Windows Classic", false),
MAC("Mac OS X", false),
SYSTEM("System", false);
private String name;
private boolean isDark;
private LafType(String name, boolean isDark) {
this.name = name;
this.isDark = isDark;
}
public String getName() {
return name;
}
public boolean isDark() {
return isDark;
}
public static LafType fromName(String name) {
for (LafType type : values()) {
if (type.getName().equals(name)) {
return type;
}
}
return null;
}
private static LookAndFeelInstaller getSystemLookAndFeelInstaller() {
OperatingSystem OS = Platform.CURRENT_PLATFORM.getOperatingSystem();
if (OS == OperatingSystem.LINUX) {
return getInstaller(NIMBUS);
}
else if (OS == OperatingSystem.MAC_OS_X) {
return getInstaller(MAC);
}
else if (OS == OperatingSystem.WINDOWS) {
return getInstaller(WINDOWS);
}
return getInstaller(NIMBUS);
}
public boolean isSupported() {
if (this == SYSTEM) {
return true;
}
LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels();
for (LookAndFeelInfo info : installedLookAndFeels) {
if (name.equals(info.getName())) {
return true;
}
}
return false;
}
public void install() throws Exception {
getInstaller(this).install();
}
private static LookAndFeelInstaller getInstaller(LafType lookAndFeel) {
switch (lookAndFeel) {
case FLAT_DARCULA:
return new FlatLookAndFeelInstaller(FLAT_DARCULA);
case FLAT_DARK:
return new FlatLookAndFeelInstaller(FLAT_DARK);
case FLAT_LIGHT:
return new FlatLookAndFeelInstaller(FLAT_LIGHT);
case GTK:
return new GTKLookAndFeelInstaller();
case MAC:
return new LookAndFeelInstaller(MAC);
case METAL:
return new LookAndFeelInstaller(METAL);
case MOTIF:
return new MotifLookAndFeelInstaller(); // Motif has some specific ui fix ups
case NIMBUS:
return new NimbusLookAndFeelInstaller(); // Nimbus installs a special way
case SYSTEM:
return getSystemLookAndFeelInstaller();
case WINDOWS:
return new LookAndFeelInstaller(WINDOWS);
case WINDOWS_CLASSIC:
return new LookAndFeelInstaller(WINDOWS_CLASSIC);
default:
throw new AssertException("No lookAndFeelInstaller defined for " + lookAndFeel);
}
}
}
@@ -1,80 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking.theme;
import com.formdev.flatlaf.*;
import docking.theme.laf.*;
import ghidra.framework.OperatingSystem;
import ghidra.framework.Platform;
public enum LookAndFeelType {
METAL("Metal", new MetalLookAndFeelInstaller()),
NIMBUS("Nimbus", new NimbusLookAndFeelInstaller()),
GTK("GTK+", new GTKLookAndFeelInstaller()),
MOTIF("CDE/Motif", new MotifLookAndFeelInstaller()),
FLAT_LIGHT("Flat Light", new FlatLookAndFeelInstaller(new FlatLightLaf())),
FLAT_DARK("Flat Dark", new FlatLookAndFeelInstaller(new FlatDarkLaf())),
FLAT_DARCULA("Flat Light", new FlatLookAndFeelInstaller(new FlatDarculaLaf())),
WINDOWS("Windows", new WindowsLookAndFeelInstaller()),
WINDOWS_CLASSIC("Windows Classic", new WindowsClassicLookAndFeelInstaller()),
MAC("Mac OS X", new MacLookAndFeelInstaller()),
SYSTEM("System", getSystemLookAndFeelInstaller());
private String name;
private LookAndFeelInstaller installer;
private LookAndFeelType(String name, LookAndFeelInstaller installer) {
this.name = name;
this.installer = installer;
}
public String getName() {
return name;
}
public static LookAndFeelType fromName(String name) {
for (LookAndFeelType type : values()) {
if (type.getName().equals(name)) {
return type;
}
}
return null;
}
private static LookAndFeelInstaller getSystemLookAndFeelInstaller() {
OperatingSystem OS = Platform.CURRENT_PLATFORM.getOperatingSystem();
if (OS == OperatingSystem.LINUX) {
return NIMBUS.installer;
}
else if (OS == OperatingSystem.MAC_OS_X) {
return MAC.installer;
}
else if (OS == OperatingSystem.WINDOWS) {
return WINDOWS.installer;
}
return NIMBUS.installer;
}
public boolean isSupported() {
return installer.isSupportedForCurrentPlatform();
}
public void install() throws Exception {
installer.install();
}
}
@@ -22,8 +22,7 @@ public class ThemeReader extends ThemePropertyFileReader {
private Section themeSection;
private String themeName;
private LookAndFeelType lookAndFeel;
private boolean isDark;
private LafType lookAndFeel;
public ThemeReader(File file) throws IOException {
super(file);
@@ -37,12 +36,11 @@ public class ThemeReader extends ThemePropertyFileReader {
throw new IOException("Missing theme name!");
}
String lookAndFeelName = section.getValue(GTheme.THEME_LOOK_AND_FEEL_KEY);
lookAndFeel = LookAndFeelType.fromName(lookAndFeelName);
lookAndFeel = LafType.fromName(lookAndFeelName);
if (lookAndFeel == null) {
throw new IOException(
"Invalid or missing lookAndFeel name: \"" + lookAndFeelName + "\"");
}
isDark = Boolean.parseBoolean(section.getValue(GTheme.THEME_IS_DARK_KEY));
}
void loadValues(GTheme theme) {
@@ -50,7 +48,6 @@ public class ThemeReader extends ThemePropertyFileReader {
// processValues expects only colors, fonts, and icons
themeSection.remove(GTheme.THEME_NAME_KEY);
themeSection.remove(GTheme.THEME_LOOK_AND_FEEL_KEY);
themeSection.remove(GTheme.THEME_IS_DARK_KEY);
processValues(theme, themeSection);
}
@@ -59,11 +56,7 @@ public class ThemeReader extends ThemePropertyFileReader {
return themeName;
}
public boolean isDark() {
return isDark;
}
public LookAndFeelType getLookAndFeelType() {
public LafType getLookAndFeelType() {
return lookAndFeel;
}
@@ -16,12 +16,12 @@
package docking.theme.builtin;
import docking.theme.DiscoverableGTheme;
import docking.theme.LookAndFeelType;
import docking.theme.LafType;
public class CDEMotifTheme extends DiscoverableGTheme {
public CDEMotifTheme() {
super("Motif", LookAndFeelType.MOTIF);
super("Motif", LafType.MOTIF);
}
}
@@ -16,10 +16,10 @@
package docking.theme.builtin;
import docking.theme.DiscoverableGTheme;
import docking.theme.LookAndFeelType;
import docking.theme.LafType;
public class FlatDarculaTheme extends DiscoverableGTheme {
public FlatDarculaTheme() {
super("Flat Darcula", LookAndFeelType.FLAT_DARCULA, true);
super("Flat Darcula", LafType.FLAT_DARCULA);
}
}
@@ -16,10 +16,10 @@
package docking.theme.builtin;
import docking.theme.DiscoverableGTheme;
import docking.theme.LookAndFeelType;
import docking.theme.LafType;
public class FlatDarkTheme extends DiscoverableGTheme {
public FlatDarkTheme() {
super("Flat Dark", LookAndFeelType.FLAT_DARK, true);
super("Flat Dark", LafType.FLAT_DARK);
}
}
@@ -16,12 +16,12 @@
package docking.theme.builtin;
import docking.theme.DiscoverableGTheme;
import docking.theme.LookAndFeelType;
import docking.theme.LafType;
public class FlatLightTheme extends DiscoverableGTheme {
public FlatLightTheme() {
super("Flat Light", LookAndFeelType.FLAT_LIGHT);
super("Flat Light", LafType.FLAT_LIGHT);
}
}
@@ -16,12 +16,12 @@
package docking.theme.builtin;
import docking.theme.DiscoverableGTheme;
import docking.theme.LookAndFeelType;
import docking.theme.LafType;
public class GTKTheme extends DiscoverableGTheme {
public GTKTheme() {
super("GDK+", LookAndFeelType.GTK);
super("GTK+", LafType.GTK);
}
}
@@ -0,0 +1,246 @@
/* ###
* 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 java.awt.Color;
import java.util.HashMap;
import java.util.Map;
import docking.theme.ColorValue;
import docking.theme.GThemeValueMap;
/**
* Maps Java UIDefaults color ids to parent color ids
*/
public class JavaColorMapping {
private Map<String, String> map = new HashMap<>();
public JavaColorMapping() {
// color relationships mined from BasicLookAndFeel
map.put("Button.background", "control");
map.put("Button.foreground", "controlText");
map.put("Button.shadow", "controlShadow");
map.put("Button.darkShadow", "controlDkShadow");
map.put("Button.light", "controlHighlight");
map.put("Button.highlight", "controlLtHighlight");
map.put("ToggleButton.background", "control");
map.put("ToggleButton.foreground", "controlText");
map.put("ToggleButton.shadow", "controlShadow");
map.put("ToggleButton.darkShadow", "controlDkShadow");
map.put("ToggleButton.light", "controlHighlight");
map.put("ToggleButton.highlight", "controlLtHighlight");
map.put("RadioButton.background", "control");
map.put("RadioButton.foreground", "controlText");
map.put("RadioButton.shadow", "controlShadow");
map.put("RadioButton.darkShadow", "controlDkShadow");
map.put("RadioButton.light", "controlHighlight");
map.put("RadioButton.highlight", "controlLtHighlight");
map.put("CheckBox.background", "control");
map.put("CheckBox.foreground", "controlText");
map.put("ColorChooser.background", "control");
map.put("ColorChooser.foreground", "controlText");
map.put("ColorChooser.swatchesDefaultRecentColor", "control");
map.put("ComboBox.background", "window");
map.put("ComboBox.foreground", "textText");
map.put("ComboBox.buttonBackground", "control");
map.put("ComboBox.buttonShadow", "controlShadow");
map.put("ComboBox.buttonDarkShadow", "controlDkShadow");
map.put("ComboBox.buttonHighlight", "controlLtHighlight");
map.put("ComboBox.selectionBackground", "textHighlight");
map.put("ComboBox.selectionForeground", "textHighlightText");
map.put("ComboBox.disabledBackground", "control");
map.put("ComboBox.disabledForeground", "textHInactiveText");
map.put("InternalFrame.borderColor", "control");
map.put("InternalFrame.borderShadow", "controlShadow");
map.put("InternalFrame.borderDarkShadow", "controlDkShadow");
map.put("InternalFrame.borderHighlight", "controlLtHighlight");
map.put("InternalFrame.borderLight", "controlHighlight");
map.put("InternalFrame.activeTitleBackground", "activeCaption");
map.put("InternalFrame.activeTitleForeground", "activeCaptionText");
map.put("InternalFrame.inactiveTitleBackground", "inactiveCaption");
map.put("InternalFrame.inactiveTitleForeground", "inactiveCaptionText");
map.put("Label.background", "control");
map.put("Label.foreground", "controlText");
map.put("Label.disabledShadow", "controlShadow");
map.put("List.background", "window");
map.put("List.foreground", "textText");
map.put("List.selectionBackground", "textHighlight");
map.put("List.selectionForeground", "textHighlightText");
map.put("List.dropLineColor", "controlShadow");
map.put("MenuBar.background", "menu");
map.put("MenuBar.foreground", "menuText");
map.put("MenuBar.shadow", "controlShadow");
map.put("MenuBar.highlight", "controlLtHighlight");
map.put("MenuItem.background", "menu");
map.put("MenuItem.foreground", "menuText");
map.put("MenuItem.selectionForeground", "textHighlightText");
map.put("MenuItem.selectionBackground", "textHighlight");
map.put("MenuItem.acceleratorForeground", "menuText");
map.put("MenuItem.acceleratorSelectionForeground", "textHighlightText");
map.put("RadioButtonMenuItem.background", "menu");
map.put("RadioButtonMenuItem.foreground", "menuText");
map.put("RadioButtonMenuItem.selectionForeground", "textHighlightText");
map.put("RadioButtonMenuItem.selectionBackground", "textHighlight");
map.put("RadioButtonMenuItem.acceleratorForeground", "menuText");
map.put("RadioButtonMenuItem.acceleratorSelectionForeground", "textHighlightText");
map.put("CheckBoxMenuItem.background", "menu");
map.put("CheckBoxMenuItem.foreground", "menuText");
map.put("CheckBoxMenuItem.selectionForeground", "textHighlightText");
map.put("CheckBoxMenuItem.selectionBackground", "textHighlight");
map.put("CheckBoxMenuItem.acceleratorForeground", "menuText");
map.put("CheckBoxMenuItem.acceleratorSelectionForeground", "textHighlightText");
map.put("Menu.background", "menu");
map.put("Menu.foreground", "menuText");
map.put("Menu.selectionForeground", "textHighlightText");
map.put("Menu.selectionBackground", "textHighlight");
map.put("Menu.acceleratorForeground", "menuText");
map.put("Menu.acceleratorSelectionForeground", "textHighlightText");
map.put("PopupMenu.background", "menu");
map.put("PopupMenu.foreground", "menuText");
map.put("OptionPane.background", "control");
map.put("OptionPane.foreground", "controlText");
map.put("OptionPane.messageForeground", "controlText");
map.put("Panel.background", "control");
map.put("Panel.foreground", "textText");
map.put("ProgressBar.foreground", "textHighlight");
map.put("ProgressBar.background", "control");
map.put("ProgressBar.selectionForeground", "control");
map.put("ProgressBar.selectionBackground", "textHighlight");
map.put("Separator.background", "controlLtHighlight");
map.put("Separator.foreground", "controlShadow");
map.put("ScrollBar.foreground", "control");
map.put("ScrollBar.track", "scrollbar");
map.put("ScrollBar.trackHighlight", "controlDkShadow");
map.put("ScrollBar.thumb", "control");
map.put("ScrollBar.thumbHighlight", "controlLtHighlight");
map.put("ScrollBar.thumbDarkShadow", "controlDkShadow");
map.put("ScrollBar.thumbShadow", "controlShadow");
map.put("ScrollPane.background", "control");
map.put("ScrollPane.foreground", "controlText");
map.put("Viewport.background", "control");
map.put("Viewport.foreground", "textText");
map.put("Slider.foreground", "control");
map.put("Slider.background", "control");
map.put("Slider.highlight", "controlLtHighlight");
map.put("Slider.shadow", "controlShadow");
map.put("Slider.focus", "controlDkShadow");
map.put("Spinner.background", "control");
map.put("Spinner.foreground", "control");
map.put("SplitPane.background", "control");
map.put("SplitPane.highlight", "controlLtHighlight");
map.put("SplitPane.shadow", "controlShadow");
map.put("SplitPane.darkShadow", "controlDkShadow");
map.put("TabbedPane.background", "control");
map.put("TabbedPane.foreground", "controlText");
map.put("TabbedPane.highlight", "controlLtHighlight");
map.put("TabbedPane.light", "controlHighlight");
map.put("TabbedPane.shadow", "controlShadow");
map.put("TabbedPane.darkShadow", "controlDkShadow");
map.put("TabbedPane.focus", "controlText");
map.put("Table.foreground", "controlText");
map.put("Table.background", "window");
map.put("Table.selectionForeground", "textHighlightText");
map.put("Table.selectionBackground", "textHighlight");
map.put("Table.dropLineColor", "controlShadow");
map.put("Table.focusCellBackground", "window");
map.put("Table.focusCellForeground", "controlText");
map.put("TableHeader.foreground", "controlText");
map.put("TableHeader.background", "control");
map.put("TableHeader.focusCellBackground", "text");
map.put("TextField.background", "window");
map.put("TextField.foreground", "textText");
map.put("TextField.shadow", "controlShadow");
map.put("TextField.darkShadow", "controlDkShadow");
map.put("TextField.light", "controlHighlight");
map.put("TextField.highlight", "controlLtHighlight");
map.put("TextField.inactiveForeground", "textHInactiveText");
map.put("TextField.inactiveBackground", "control");
map.put("TextField.selectionBackground", "textHighlight");
map.put("TextField.selectionForeground", "textHighlightText");
map.put("TextField.caretForeground", "textText");
map.put("FormattedTextField.background", "window");
map.put("FormattedTextField.foreground", "textText");
map.put("FormattedTextField.inactiveForeground", "textHInactiveText");
map.put("FormattedTextField.inactiveBackground", "control");
map.put("FormattedTextField.selectionBackground", "textHighlight");
map.put("FormattedTextField.selectionForeground", "textHighlightText");
map.put("FormattedTextField.caretForeground", "textText");
map.put("PasswordField.background", "window");
map.put("PasswordField.foreground", "textText");
map.put("PasswordField.inactiveForeground", "textHInactiveText");
map.put("PasswordField.inactiveBackground", "control");
map.put("PasswordField.selectionBackground", "textHighlight");
map.put("PasswordField.selectionForeground", "textHighlightText");
map.put("PasswordField.caretForeground", "textText");
map.put("TextArea.background", "window");
map.put("TextArea.foreground", "textText");
map.put("TextArea.inactiveForeground", "textHInactiveText");
map.put("TextArea.selectionBackground", "textHighlight");
map.put("TextArea.selectionForeground", "textHighlightText");
map.put("TextArea.caretForeground", "textText");
map.put("TextPane.foreground", "textText");
map.put("TextPane.selectionBackground", "textHighlight");
map.put("TextPane.selectionForeground", "textHighlightText");
map.put("TextPane.caretForeground", "textText");
map.put("TextPane.inactiveForeground", "textHInactiveText");
map.put("EditorPane.foreground", "textText");
map.put("EditorPane.selectionBackground", "textHighlight");
map.put("EditorPane.selectionForeground", "textHighlightText");
map.put("EditorPane.caretForeground", "textText");
map.put("EditorPane.inactiveForeground", "textHInactiveText");
map.put("TitledBorder.titleColor", "controlText");
map.put("ToolBar.background", "control");
map.put("ToolBar.foreground", "controlText");
map.put("ToolBar.shadow", "controlShadow");
map.put("ToolBar.darkShadow", "controlDkShadow");
map.put("ToolBar.light", "controlHighlight");
map.put("ToolBar.highlight", "controlLtHighlight");
map.put("ToolBar.dockingBackground", "control");
map.put("ToolBar.floatingBackground", "control");
map.put("ToolTip.background", "info");
map.put("ToolTip.foreground", "infoText");
map.put("Tree.background", "window");
map.put("Tree.foreground", "textText");
map.put("Tree.textForeground", "textText");
map.put("Tree.textBackground", "text");
map.put("Tree.selectionForeground", "textHighlightText");
map.put("Tree.selectionBackground", "textHighlight");
map.put("Tree.dropLineColor", "controlShadow");
}
public ColorValue map(GThemeValueMap values, ColorValue value) {
String id = value.getId();
String refId = map.get(id);
if (refId == null) {
return null;
}
ColorValue refValue = values.getColor(refId);
if (refValue == null) {
return null;
}
Color originalColor = value.get(values);
Color refColor = refValue.get(values);
if (originalColor == null || refColor == null) {
return null;
}
if (originalColor.getRGB() == refColor.getRGB()) {
return new ColorValue(id, refId);
}
return null;
}
}
@@ -16,11 +16,11 @@
package docking.theme.builtin;
import docking.theme.DiscoverableGTheme;
import docking.theme.LookAndFeelType;
import docking.theme.LafType;
public class MacTheme extends DiscoverableGTheme {
public MacTheme() {
super("Mac OS X", LookAndFeelType.MAC);
super("Mac OS X", LafType.MAC);
}
}
@@ -16,12 +16,12 @@
package docking.theme.builtin;
import docking.theme.DiscoverableGTheme;
import docking.theme.LookAndFeelType;
import docking.theme.LafType;
public class MetalTheme extends DiscoverableGTheme {
public MetalTheme() {
super("Metal", LookAndFeelType.METAL);
super("Metal", LafType.METAL);
}
}
@@ -16,12 +16,12 @@
package docking.theme.builtin;
import docking.theme.DiscoverableGTheme;
import docking.theme.LookAndFeelType;
import docking.theme.LafType;
public class NimbusTheme extends DiscoverableGTheme {
public NimbusTheme() {
super("Nimbus", LookAndFeelType.NIMBUS);
super("Nimbus", LafType.NIMBUS);
}
}
@@ -16,11 +16,11 @@
package docking.theme.builtin;
import docking.theme.DiscoverableGTheme;
import docking.theme.LookAndFeelType;
import docking.theme.LafType;
public class WindowsClassicTheme extends DiscoverableGTheme {
public WindowsClassicTheme() {
super("Windows Classic", LookAndFeelType.WINDOWS_CLASSIC);
super("Windows Classic", LafType.WINDOWS_CLASSIC);
}
}
@@ -16,11 +16,11 @@
package docking.theme.builtin;
import docking.theme.DiscoverableGTheme;
import docking.theme.LookAndFeelType;
import docking.theme.LafType;
public class WindowsTheme extends DiscoverableGTheme {
public WindowsTheme() {
super("Windows", LookAndFeelType.WINDOWS);
super("Windows", LafType.WINDOWS);
}
}
@@ -1,200 +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.gui;
import java.awt.*;
import java.awt.event.*;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.*;
import docking.DialogComponentProvider;
import docking.theme.*;
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();
Set<GTheme> supportedThemes = Gui.getSupportedThemes();
List<String> themeNames =
supportedThemes.stream().map(t -> t.getName()).collect(Collectors.toList());
Collections.sort(themeNames);
GhidraComboBox<String> combo = new GhidraComboBox<>(themeNames);
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();
}
void colorEditorClosed() {
dialog = null;
}
}
@@ -26,18 +26,17 @@ 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 {
public class ThemeColorEditorDialog extends DialogComponentProvider {
private ColorValue originalColorValue;
private ColorValue startingColorValue;
private ColorValue currentColorValue;
private GThemeDialog themeDialog;
private ThemeDialog themeDialog;
private GhidraColorChooser colorChooser;
private ChangeListener colorChangeListener = e -> colorChanged();
public GThemeColorEditorDialog(GThemeDialog themeDialog) {
public ThemeColorEditorDialog(ThemeDialog themeDialog) {
super("Theme Color Editor", false);
this.themeDialog = themeDialog;
addWorkPanel(buildColorPanel());
@@ -46,14 +45,11 @@ public class GThemeColorEditorDialog extends DialogComponentProvider {
}
public void editColor(ColorValue colorValue) {
if (currentColorValue != null && !currentColorValue.equals(originalColorValue)) {
themeDialog.colorChangeAccepted();
}
this.originalColorValue = colorValue;
this.startingColorValue = colorValue;
this.currentColorValue = colorValue;
setTitle("Edit Color For: " + colorValue.getId());
Color color = Gui.getRawColor(originalColorValue.getId());
Color color = Gui.getRawColor(startingColorValue.getId());
colorChooser.getSelectionModel().removeChangeListener(colorChangeListener);
colorChooser.setColor(color);
colorChooser.getSelectionModel().addChangeListener(colorChangeListener);
@@ -74,11 +70,8 @@ public class GThemeColorEditorDialog extends DialogComponentProvider {
@Override
protected void okCallback() {
if (!currentColorValue.equals(originalColorValue)) {
themeDialog.colorChangeAccepted();
}
currentColorValue = null;
originalColorValue = null;
startingColorValue = null;
close();
themeDialog.colorEditorClosed();
}
@@ -87,19 +80,21 @@ public class GThemeColorEditorDialog extends DialogComponentProvider {
protected void cancelCallback() {
restoreOriginalColor();
currentColorValue = null;
originalColorValue = null;
startingColorValue = null;
close();
themeDialog.colorEditorClosed();
}
private void restoreOriginalColor() {
Gui.setColor(originalColorValue);
themeDialog.colorChanged(currentColorValue, startingColorValue);
currentColorValue = startingColorValue;
}
private void colorChanged() {
Color newColor = colorChooser.getColor();
currentColorValue = new ColorValue(originalColorValue.getId(), newColor);
Swing.runLater(() -> Gui.setColor(currentColorValue));
ColorValue newColorValue = new ColorValue(startingColorValue.getId(), newColor);
themeDialog.colorChanged(currentColorValue, newColorValue);
currentColorValue = newColorValue;
}
}
@@ -28,31 +28,43 @@ 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;
private GThemeValueMap currentValues;
private GThemeValueMap themeValues;
private GThemeValueMap defaultValues;
private GThemeValueMap lightDefaultValues;
private GThemeValueMap darkDefaultValues;
public ThemeColorTableModel() {
super(new ServiceProviderStub());
loadValues();
load();
}
public void reload() {
loadValues();
public void reloadCurrent() {
currentValues = Gui.getAllValues();
colors = currentValues.getColors();
fireTableDataChanged();
}
private void loadValues() {
values = Gui.getAllValues();
coreDefaults = Gui.getCoreDefaults();
darkDefaults = Gui.getDarkDefaults();
colors = values.getColors();
public void reloadAll() {
load();
fireTableDataChanged();
}
public void load() {
currentValues = Gui.getAllValues();
colors = currentValues.getColors();
themeValues = new GThemeValueMap(currentValues);
defaultValues = Gui.getDefaults();
lightDefaultValues = Gui.getGhidraLightDefaults();
darkDefaultValues = Gui.getGhidraDarkDefaults();
}
@Override
@@ -69,10 +81,11 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel<ColorValue, O
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", () -> currentValues));
descriptor.addVisibleColumn(new ValueColumn("Theme Color", () -> themeValues));
descriptor.addVisibleColumn(new ValueColumn("Default Color", () -> defaultValues));
descriptor.addHiddenColumn(new ValueColumn("Light Defaults", () -> lightDefaultValues));
descriptor.addHiddenColumn(new ValueColumn("Dark Defaults", () -> darkDefaultValues));
return descriptor;
}
@@ -100,7 +113,7 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel<ColorValue, O
}
}
class ValueColumn extends AbstractDynamicTableColumn<ColorValue, ColorValue, Object> {
class ValueColumn extends AbstractDynamicTableColumn<ColorValue, ResolvedColor, Object> {
private ThemeColorRenderer renderer;
private String name;
private Supplier<GThemeValueMap> valueSupplier;
@@ -108,7 +121,7 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel<ColorValue, O
ValueColumn(String name, Supplier<GThemeValueMap> supplier) {
this.name = name;
this.valueSupplier = supplier;
renderer = new ThemeColorRenderer(supplier);
renderer = new ThemeColorRenderer();
}
@Override
@@ -117,28 +130,35 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel<ColorValue, O
}
@Override
public ColorValue getValue(ColorValue themeColor, Settings settings, Object data,
public ResolvedColor getValue(ColorValue themeColor, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
return themeColor;
GThemeValueMap valueMap = valueSupplier.get();
String id = themeColor.getId();
ColorValue colorValue = valueMap.getColor(id);
if (colorValue == null) {
return null;
}
Color color = colorValue.get(valueMap);
return new ResolvedColor(id, colorValue.getReferenceId(), color);
}
@Override
public GColumnRenderer<ColorValue> getColumnRenderer() {
public GColumnRenderer<ResolvedColor> getColumnRenderer() {
return renderer;
}
public Comparator<ColorValue> getComparator() {
public Comparator<ResolvedColor> getComparator() {
return (v1, v2) -> {
GThemeValueMap valueMap = valueSupplier.get();
ColorValue v1Color = valueMap.getColor(v1.getId());
ColorValue v2Color = valueMap.getColor(v2.getId());
if (v1Color == null && v2Color == null) {
if (v1 == null && v2 == null) {
return 0;
}
if (v1Color == null) {
if (v1 == null) {
return 1;
}
return v1Color.compareValue(v2Color);
if (v2 == null) {
return -1;
}
return ColorUtils.COMPARATOR.compare(v1.color, v2.color);
};
}
@@ -146,74 +166,47 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel<ColorValue, O
public int getColumnPreferredWidth() {
return 300;
}
}
class IsLafPropertyColumn extends AbstractDynamicTableColumn<ColorValue, Boolean, Object> {
private class ThemeColorRenderer extends AbstractGColumnRenderer<ResolvedColor> {
@Override
public String getColumnName() {
return "Is Laf";
}
@Override
public Boolean getValue(ColorValue themeColor, Settings settings, Object data,
ServiceProvider provider) throws IllegalArgumentException {
return Gui.isJavaDefinedColor(themeColor.getId());
}
@Override
public int getColumnPreferredWidth() {
return 20;
}
}
private class ThemeColorRenderer extends AbstractGColumnRenderer<ColorValue> {
private Supplier<GThemeValueMap> mapSupplier;
public ThemeColorRenderer(Supplier<GThemeValueMap> mapSupplier) {
this.mapSupplier = mapSupplier;
public ThemeColorRenderer() {
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();
ResolvedColor resolved = (ResolvedColor) data.getValue();
ColorValue colorValue = valueMap.getColor(id);
Color color;
String text;
if (colorValue != null) {
color = colorValue.get(valueMap);
if (colorValue.getReferenceId() != null) {
text = colorValue.getReferenceId();
}
else {
text = WebColors.toString(color, false);
String name = WebColors.toWebColorName(color);
if (name != null) {
text += " [" + name + "]";
}
}
}
else {
color = GThemeDefaults.Colors.BACKGROUND;
text = "<No Value>";
}
String text = getValueText(resolved);
Color color = resolved == null ? GThemeDefaults.Colors.BACKGROUND : resolved.color;
label.setText(text);
label.setIcon(new SwatchIcon(color, label.getForeground()));
// label.setBackground(color);
// label.setForeground(ColorUtils.contrastForegroundColor(color));
label.setOpaque(true);
return label;
}
private String getValueText(ResolvedColor resolvedColor) {
if (resolvedColor == null) {
return "<No Value>";
}
if (resolvedColor.refId != null) {
return resolvedColor.refId;
}
Color color = resolvedColor.color;
String text = WebColors.toString(color, false);
String name = WebColors.toWebColorName(color);
if (name != null) {
text += " [" + name + "]";
}
return text;
}
@Override
public String getFilterString(ColorValue t, Settings settings) {
return t.getId();
public String getFilterString(ResolvedColor colorValue, Settings settings) {
return getValueText(colorValue);
}
}
@@ -244,6 +237,17 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel<ColorValue, O
public int getIconHeight() {
return 16;
}
}
class ResolvedColor {
String id;
String refId;
Color color;
ResolvedColor(String id, String refId, Color color) {
this.id = id;
this.refId = refId;
this.color = color;
}
}
}
@@ -0,0 +1,390 @@
/* ###
* 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.Component;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import javax.swing.*;
import javax.swing.table.TableColumn;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.theme.*;
import docking.widgets.OptionDialog;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.dialogs.InputDialog;
import docking.widgets.table.GFilterTable;
import docking.widgets.table.GTable;
import ghidra.framework.Application;
import ghidra.util.Msg;
import ghidra.util.Swing;
import resources.Icons;
public class ThemeDialog extends DialogComponentProvider {
private static ThemeDialog INSTANCE;
private ThemeColorTableModel colorTableModel;
private ThemeColorEditorDialog dialog;
// stores the original value for ids whose value has changed
private GThemeValueMap changedValuesMap = new GThemeValueMap();
private JButton saveButton;
private JButton restoreButton;
private GhidraComboBox<String> combo;
private ItemListener comboListener = this::themeComboChanged;
public ThemeDialog() {
super("Theme Dialog", false);
addWorkPanel(createMainPanel());
addDismissButton();
addButton(createSaveButton());
addButton(createRestoreButton());
setPreferredSize(1100, 500);
setRememberSize(false);
updateButtons();
}
@Override
protected void dismissCallback() {
if (hasChanges()) {
int result = OptionDialog.showYesNoCancelDialog(null, "Close Theme Dialog",
"You have changed the theme.\n Do you want save your changes?");
if (result == OptionDialog.CANCEL_OPTION) {
return;
}
if (result == OptionDialog.YES_OPTION) {
if (!save()) {
return;
}
}
else {
Gui.reloadGhidraDefaults();
}
}
INSTANCE = null;
close();
}
protected void saveCallback() {
save();
updateCombo();
}
private void restoreCallback() {
if (hasChanges()) {
int result = OptionDialog.showYesNoDialog(null, "Restore Theme Values",
"Are you sure you want to discard all your changes?");
if (result == OptionDialog.NO_OPTION) {
return;
}
}
Gui.restoreThemeValues();
reset();
}
private void reloadDefaultsCallback(ActionEvent e) {
if (hasChanges()) {
int result = OptionDialog.showYesNoDialog(null, "Reload Ghidra Default Values",
"This will discard all your theme changes. Continue?");
if (result == OptionDialog.NO_OPTION) {
return;
}
}
Gui.reloadGhidraDefaults();
reset();
}
private void reset() {
changedValuesMap.clear();
colorTableModel.reloadAll();
updateButtons();
}
/**
* Saves all current theme changes
* @return true if the operation was not cancelled.
*/
private boolean save() {
GTheme activeTheme = Gui.getActiveTheme();
if (activeTheme instanceof FileGTheme) {
FileGTheme fileTheme = (FileGTheme) activeTheme;
if (fileTheme.canSave()) {
int result = OptionDialog.showYesNoCancelDialog(null, "Overwrite Existing Theme",
"Do you want to overwrite the existing theme file?");
if (result == OptionDialog.CANCEL_OPTION) {
return false;
}
if (result == OptionDialog.YES_OPTION) {
return saveCurrentValuesToTheme(fileTheme, false);
}
}
}
// save to new Theme file
InputDialog inputDialog = new InputDialog("Create Theme", "New Theme Name");
DockingWindowManager.showDialog(inputDialog);
String themeName = inputDialog.getValue();
if (themeName == null) {
return false;
}
File file = getSaveFile(themeName);
LafType laf = activeTheme.getLookAndFeelType();
return saveCurrentValuesToTheme(new FileGTheme(file, themeName, laf), false);
}
private boolean saveCurrentValuesToTheme(FileGTheme newTheme, boolean includeDefaults) {
newTheme.clear();
GThemeValueMap allValues = Gui.getAllValues();
if (includeDefaults) {
newTheme.load(allValues);
}
else {
Gui.getAllValues();
newTheme.load(allValues.removeSameValues(Gui.getDefaults()));
}
try {
newTheme.save();
Gui.addTheme(newTheme);
Gui.setTheme(newTheme);
}
catch (IOException e) {
Msg.showError(this, null, "I/O Error",
"Error writing theme file: " + newTheme.getFile().getAbsolutePath(), e);
return false;
}
return true;
}
private File getSaveFile(String themeName) {
File dir = Application.getUserSettingsDirectory();
String cleanedName = themeName.replaceAll(" ", "_") + GTheme.FILE_EXTENSION;
return new File(dir, cleanedName);
}
// private void export() {
// 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.getDefaults());
// return true;
// }
// catch (IOException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// return false;
// }
private void themeComboChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
String themeName = (String) e.getItem();
if (hasChanges()) {
Msg.debug(this, "has changes");
}
Swing.runLater(() -> {
Gui.setTheme(Gui.getTheme(themeName));
changedValuesMap.clear();
colorTableModel.reloadAll();
});
}
}
private boolean hasChanges() {
return !changedValuesMap.isEmpty();
}
protected void editColor(ColorValue value) {
if (dialog == null) {
dialog = new ThemeColorEditorDialog(this);
}
dialog.editColor(value);
}
void colorChanged(ColorValue oldValue, ColorValue newValue) {
updateChanagedValueMap(oldValue, newValue);
// run later - don't rock the boat in the middle of a listener callback
Swing.runLater(() -> {
Gui.setColor(newValue);
colorTableModel.reloadCurrent();
});
}
private void updateChanagedValueMap(ColorValue oldValue, ColorValue newValue) {
ColorValue originalValue = changedValuesMap.getColor(oldValue.getId());
if (originalValue == null) {
changedValuesMap.addColor(oldValue);
}
else if (originalValue.equals(newValue)) {
// if restoring the original color, remove it from the map of changes
changedValuesMap.removeColor(oldValue.getId());
}
updateButtons();
}
private void updateButtons() {
boolean hasChanges = hasChanges();
saveButton.setEnabled(hasChanges);
restoreButton.setEnabled(hasChanges);
}
void colorEditorClosed() {
dialog = null;
}
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(buildThemeCombo(), BorderLayout.WEST);
panel.add(buildReloadDefaultsButton(), BorderLayout.EAST);
panel.setName("gthemePanel");
return panel;
}
private Component buildReloadDefaultsButton() {
JButton button = new JButton(Icons.REFRESH_ICON);
button.addActionListener(this::reloadDefaultsCallback);
button.setToolTipText(
"Reload Ghidra Defaults (Only needed if you change a theme.properties file)");
return button;
}
private void updateCombo() {
Set<GTheme> supportedThemes = Gui.getSupportedThemes();
List<String> themeNames =
supportedThemes.stream().map(t -> t.getName()).collect(Collectors.toList());
Collections.sort(themeNames);
combo.removeItemListener(comboListener);
combo.setModel(new DefaultComboBoxModel<String>(new Vector<String>(themeNames)));
combo.setSelectedItem(Gui.getActiveTheme().getName());
combo.addItemListener(comboListener);
}
private Component buildThemeCombo() {
JPanel panel = new JPanel();
Set<GTheme> supportedThemes = Gui.getSupportedThemes();
List<String> themeNames =
supportedThemes.stream().map(t -> t.getName()).collect(Collectors.toList());
Collections.sort(themeNames);
combo = new GhidraComboBox<>(themeNames);
combo.setSelectedItem(Gui.getActiveTheme().getName());
combo.addItemListener(comboListener);
panel.add(new JLabel("Theme: "), BorderLayout.WEST);
panel.add(combo);
panel.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
return panel;
}
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());
Object cellValue = filterTable.getCellValue(e.getPoint());
// editColor(value);
int col = filterTable.getColumn(e.getPoint());
TableColumn column = table.getColumnModel().getColumn(col);
Object identifier = column.getIdentifier();
if ("Current Color".equals(identifier) || "Id".equals(identifier)) {
editColor(value);
}
}
}
});
return filterTable;
}
private JButton createRestoreButton() {
restoreButton = new JButton("Restore");
restoreButton.setMnemonic('R');
restoreButton.setName("Restore");
restoreButton.addActionListener(e -> restoreCallback());
restoreButton.setToolTipText("Restores all values to current theme");
return restoreButton;
}
private JButton createSaveButton() {
saveButton = new JButton("Save");
saveButton.setMnemonic('S');
saveButton.setName("Save");
saveButton.addActionListener(e -> saveCallback());
saveButton.setToolTipText("Saves changed values to a new Theme");
return saveButton;
}
public static void editTheme() {
if (INSTANCE != null) {
INSTANCE.toFront();
return;
}
INSTANCE = new ThemeDialog();
DockingWindowManager.showDialog(INSTANCE);
}
}
@@ -16,25 +16,22 @@
package docking.theme.laf;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import com.formdev.flatlaf.FlatLaf;
import docking.theme.LafType;
public class FlatLookAndFeelInstaller extends LookAndFeelInstaller {
private FlatLaf lookAndFeel;
public FlatLookAndFeelInstaller(FlatLaf lookAndFeel) {
this.lookAndFeel = lookAndFeel;
public FlatLookAndFeelInstaller(LafType lookAndFeelType) {
super(lookAndFeelType);
}
@Override
protected void installLookAndFeel() throws UnsupportedLookAndFeelException {
UIManager.setLookAndFeel(lookAndFeel);
}
protected void fixupLookAndFeelIssues() {
super.fixupLookAndFeelIssues();
@Override
public boolean isSupportedForCurrentPlatform() {
return true;
// We have historically managed button focusability ourselves. Allow this by default so
// features continue to work as expected, such as right-clicking on ToolButtons.
UIManager.put("ToolBar.focusableButtons", Boolean.TRUE);
}
}
@@ -15,68 +15,29 @@
*/
package docking.theme.laf;
import java.awt.Color;
import java.util.List;
import javax.swing.*;
import javax.swing.plaf.synth.SynthLookAndFeel;
import docking.theme.*;
import ghidra.docking.util.LookAndFeelUtils;
import docking.theme.LafType;
public class GTKLookAndFeelInstaller extends LookAndFeelInstaller {
public GTKLookAndFeelInstaller() {
super(LafType.GTK);
}
@Override
protected void installLookAndFeel() throws ClassNotFoundException, InstantiationException,
IllegalAccessException, UnsupportedLookAndFeelException {
super.installLookAndFeel();
LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
WrappingLookAndFeel wrappingLookAndFeel = new WrappingLookAndFeel(lookAndFeel);
UIManager.setLookAndFeel(wrappingLookAndFeel);
}
@Override
protected void installJavaDefaults() {
// do nothing - already handled by wrapped GTK lookAndFeel
}
@Override
protected void installLookAndFeel() throws UnsupportedLookAndFeelException {
String name = LookAndFeelType.GTK.getName();
try {
UIManager.setLookAndFeel(findLookAndFeelClassName(name));
LookAndFeel gtk = UIManager.getLookAndFeel();
UIManager.setLookAndFeel(new WrappingLookAndFeel(gtk));
}
catch (Exception e) {
throw new UnsupportedLookAndFeelException(name + " not supported on this platform");
}
}
@Override
public boolean isSupportedForCurrentPlatform() {
return isSupported(LookAndFeelType.GTK.getName());
}
/**
* Extends the NimbusLookAndFeel to intercept the {@link #getDefaults()}. To get Nimbus
* to use our indirect values, we have to get in early.
*/
static class ExtendedGTKLookAndFeel extends SynthLookAndFeel {
@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;
}
// handled by WrappingLookAndFeel
}
}
@@ -22,15 +22,37 @@ import java.util.Map.Entry;
import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.plaf.UIResource;
import docking.theme.*;
import ghidra.docking.util.LookAndFeelUtils;
import ghidra.util.*;
public abstract class LookAndFeelInstaller {
/**
* Installs a specific {@link LookAndFeel} into the {@link UIManager}. The idea is that there
* is a specific installer for each supported {@link LookAndFeel} to handle unique needs for
* that LookAndFeel. Subclasses can also override {@link #fixupLookAndFeelIssues()} to make
* UI tweaks to specific LookAndFeels.
*/
public class LookAndFeelInstaller {
public void install() throws Exception {
private LafType lookAndFeel;
public LookAndFeelInstaller(LafType lookAndFeel) {
this.lookAndFeel = lookAndFeel;
}
/**
* Installs the {@link LookAndFeel} associated with this installer
* @throws ClassNotFoundException if the <code>LookAndFeel</code>
* class could not be found
* @throws InstantiationException if a new instance of the class
* couldn't be created
* @throws IllegalAccessException if the class or initializer isn't accessible
* @throws UnsupportedLookAndFeelException if
* <code>lnf.isSupportedLookAndFeel()</code> is false
*/
public void install() throws ClassNotFoundException, InstantiationException,
IllegalAccessException, UnsupportedLookAndFeelException {
cleanUiDefaults();
installLookAndFeel();
installJavaDefaults();
@@ -38,21 +60,34 @@ public abstract class LookAndFeelInstaller {
installGlobalProperties();
}
private void installGlobalProperties() {
installGlobalLookAndFeelAttributes();
installGlobalFontSizeOverride();
installCustomLookAndFeelActions();
installPopupMenuSettingsOverride();
/**
* Subclass provide this method to install the specific loo
* @throws ClassNotFoundException if the <code>LookAndFeel</code>
* class could not be found
* @throws InstantiationException if a new instance of the class
* couldn't be created
* @throws IllegalAccessException if the class or initializer isn't accessible
* @throws UnsupportedLookAndFeelException if
* <code>lnf.isSupportedLookAndFeel()</code> is false
*/
protected void installLookAndFeel() throws ClassNotFoundException, InstantiationException,
IllegalAccessException, UnsupportedLookAndFeelException {
String name = lookAndFeel.getName();
UIManager.setLookAndFeel(findLookAndFeelClassName(name));
}
public abstract boolean isSupportedForCurrentPlatform();
protected abstract void installLookAndFeel() throws Exception;
/**
* Subclass can override this method to do specific LookAndFeel fix ups
*/
protected void fixupLookAndFeelIssues() {
// no generic fix-ups at this time.
}
/**
* Installs GColors into the UIDefaults. Subclasses my override this if they need to install
* GColors in a different way.
*/
protected void installJavaDefaults() {
GThemeValueMap javaDefaults = extractJavaDefaults();
Gui.setJavaDefaults(javaDefaults);
@@ -82,36 +117,14 @@ public abstract class LookAndFeelInstaller {
List<String> ids =
LookAndFeelUtils.getLookAndFeelIdsForType(UIManager.getDefaults(), Color.class);
for (String id : ids) {
values.addColor(new ColorValue(id, getNonUiColor(id)));
// only use standard java colors here to avoid weird issues (such as GColor not
// resolving or ColorUIResource not being honored. Later we will go back
// and fix up the java defaults to use standard java color indirection
values.addColor(new ColorValue(id, getNormalizedColor(UIManager.getColor(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);
// }
}
protected String findLookAndFeelClassName(String lookAndFeelName) {
LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels();
for (LookAndFeelInfo info : installedLookAndFeels) {
@@ -233,4 +246,34 @@ public abstract class LookAndFeelInstaller {
inputMap.put(keyStroke, action);
}
}
private void installGlobalProperties() {
installGlobalLookAndFeelAttributes();
installGlobalFontSizeOverride();
installCustomLookAndFeelActions();
installPopupMenuSettingsOverride();
}
private static Color getNormalizedColor(Color color) {
if (color.getClass() == Color.class) {
return color;
}
return new Color(color.getRGB(), true);
}
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);
// }
}
}
@@ -1,35 +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.laf;
import javax.swing.UIManager;
import docking.theme.LookAndFeelType;
public class MacLookAndFeelInstaller extends LookAndFeelInstaller {
@Override
public boolean isSupportedForCurrentPlatform() {
return isSupported(LookAndFeelType.MAC.getName());
}
@Override
protected void installLookAndFeel() throws Exception {
String name = LookAndFeelType.MAC.getName();
UIManager.setLookAndFeel(findLookAndFeelClassName(name));
}
}
@@ -1,34 +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.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());
}
@Override
public boolean isSupportedForCurrentPlatform() {
return true;
}
}

Some files were not shown because too many files have changed in this diff Show More