From e5f1563c08680344771cde3cf6c4d9eb083dd07c Mon Sep 17 00:00:00 2001 From: ghidragon <106987263+ghidragon@users.noreply.github.com> Date: Fri, 13 Jan 2023 17:58:27 -0500 Subject: [PATCH] GP-2995 creating a better mapping of look and feel values to more standard ids and easier ways to override values for a specific LaF --- .../Base/data/base.listing.theme.properties | 6 +- .../Features/Base/data/base.theme.properties | 10 +- .../core/hover/AbstractReferenceHover.java | 5 +- .../interpreter/CodeCompletionWindow.java | 2 +- .../core/programtree/DnDTreeCellRenderer.java | 13 +- .../functiongraph/graph/vertex/FGVertex.java | 2 +- .../Docking/data/docking.theme.properties | 6 +- .../src/main/java/docking/EditWindow.java | 2 +- .../src/main/java/docking/SplitPanel.java | 2 +- .../docking/theme/gui/GThemeValuesCache.java | 80 ++ .../docking/theme/gui/ThemeColorTable.java | 6 +- .../theme/gui/ThemeColorTableModel.java | 30 +- .../java/docking/theme/gui/ThemeDialog.java | 14 +- .../docking/theme/gui/ThemeFontTable.java | 4 +- .../theme/gui/ThemeFontTableModel.java | 81 +- .../docking/theme/gui/ThemeIconTable.java | 4 +- .../theme/gui/ThemeIconTableModel.java | 38 +- .../widgets/tree/support/GTreeRenderer.java | 2 + Ghidra/Framework/Gui/certification.manifest | 1 - .../data/gui.laf.overrides.theme.properties | 55 -- .../Framework/Gui/data/gui.theme.properties | 30 +- ...der.java => ApplicationThemeDefaults.java} | 23 +- .../theme/ApplicationThemeManager.java | 22 +- .../main/java/generic/theme/ColorValue.java | 13 +- .../src/main/java/generic/theme/GColor.java | 15 +- .../java/generic/theme/GColorUIResource.java | 6 +- .../java/generic/theme/GThemeDefaults.java | 29 +- .../java/generic/theme/GThemeValueMap.java | 48 ++ ...er.java => PropertyFileThemeDefaults.java} | 10 +- .../java/generic/theme/StubThemeManager.java | 10 +- .../java/generic/theme/SystemThemeIds.java | 68 ++ .../main/java/generic/theme/ThemeManager.java | 54 +- .../theme/laf/CustomNimbusLookAndFeel.java | 85 +++ .../theme/laf/FlatDarkUiDefaultsMapper.java | 68 ++ .../theme/laf/FlatLookAndFeelManager.java | 15 +- .../generic/theme/laf/FlatThemeGrouper.java | 62 -- .../theme/laf/FlatUiDefaultsMapper.java | 65 ++ .../generic/theme/laf/GNimbusLookAndFeel.java | 120 --- .../theme/laf/GtkLookAndFeelManager.java | 7 + .../theme/laf/GtkUiDefaultsMapper.java | 41 + .../generic/theme/laf/LookAndFeelManager.java | 187 +---- .../theme/laf/MacLookAndFeelManager.java | 6 +- .../generic/theme/laf/MacThemeGrouper.java | 38 - .../theme/laf/MacUiDefaultsMapper.java | 25 + .../theme/laf/MetalLookAndFeelManager.java | 7 + .../theme/laf/MetalUiDefaultsMapper.java | 41 + .../theme/laf/MotifLookAndFeelManager.java | 21 +- ...rouper.java => MotifUiDefaultsMapper.java} | 17 +- .../theme/laf/NimbusLookAndFeelManager.java | 44 +- .../generic/theme/laf/NimbusThemeGrouper.java | 43 -- .../theme/laf/NimbusUiDefaultsMapper.java | 96 +++ .../java/generic/theme/laf/ThemeGrouper.java | 452 ----------- .../generic/theme/laf/UiDefaultsMapper.java | 716 ++++++++++++++++++ .../laf/WindowsClassicLookAndFeelManager.java | 7 + .../laf/WindowsClassicUiDefaultsMapper.java | 26 + .../theme/laf/WindowsLookAndFeelManager.java | 7 + .../theme/laf/WindowsUiDefaultsMapper.java | 26 + .../theme/ApplicationThemeManagerTest.java | 22 +- .../generic/theme/laf/ThemeGrouperTest.java | 104 --- .../theme/laf/UIDefaultsMapperTest.java | 183 +++++ 60 files changed, 1939 insertions(+), 1283 deletions(-) create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/theme/gui/GThemeValuesCache.java delete mode 100644 Ghidra/Framework/Gui/data/gui.laf.overrides.theme.properties rename Ghidra/Framework/Gui/src/main/java/generic/theme/{ThemeDefaultsProvider.java => ApplicationThemeDefaults.java} (52%) rename Ghidra/Framework/Gui/src/main/java/generic/theme/{ApplicationThemeDefaultsProvider.java => PropertyFileThemeDefaults.java} (89%) create mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/SystemThemeIds.java create mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/CustomNimbusLookAndFeel.java create mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatDarkUiDefaultsMapper.java delete mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatThemeGrouper.java create mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatUiDefaultsMapper.java delete mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/GNimbusLookAndFeel.java create mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/GtkUiDefaultsMapper.java delete mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MacThemeGrouper.java create mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MacUiDefaultsMapper.java create mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MetalUiDefaultsMapper.java rename Ghidra/Framework/Gui/src/main/java/generic/theme/laf/{MotifThemeGrouper.java => MotifUiDefaultsMapper.java} (66%) delete mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/NimbusThemeGrouper.java create mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/NimbusUiDefaultsMapper.java delete mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/ThemeGrouper.java create mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/UiDefaultsMapper.java create mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsClassicUiDefaultsMapper.java create mode 100644 Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsUiDefaultsMapper.java delete mode 100644 Ghidra/Framework/Gui/src/test/java/generic/theme/laf/ThemeGrouperTest.java create mode 100644 Ghidra/Framework/Gui/src/test/java/generic/theme/laf/UIDefaultsMapperTest.java diff --git a/Ghidra/Features/Base/data/base.listing.theme.properties b/Ghidra/Features/Base/data/base.listing.theme.properties index 0a8ede3bfe..0ce9c5d7fc 100644 --- a/Ghidra/Features/Base/data/base.listing.theme.properties +++ b/Ghidra/Features/Base/data/base.listing.theme.properties @@ -5,12 +5,12 @@ color.bg.currentline.listing = color.bg.currentline color.bg.selection.listing = color.bg.selection color.bg.highlight.listing = color.bg.highlight -color.bg.listing.tabs.selected = [color]textHighlight -color.bg.listing.tabs.unselected = system.color.bg.application +color.bg.listing.tabs.selected = [color]system.color.bg.selected.view +color.bg.listing.tabs.unselected = [color]system.color.bg.control color.bg.listing.tabs.highlighted = #ABC8FF color.bg.listing.tabs.list = rgb(255, 255, 230) color.bg.listing.tabs.more.tabs.hover = rgb(255, 226, 213) -color.fg.listing.tabs.text.selected = [color]textHighlightText +color.fg.listing.tabs.text.selected = [color]system.color.fg.selected.view color.fg.listing.tabs.text.unselected = color.fg color.fg.listing.tabs.list = black diff --git a/Ghidra/Features/Base/data/base.theme.properties b/Ghidra/Features/Base/data/base.theme.properties index 9666a21797..7ea430fc96 100644 --- a/Ghidra/Features/Base/data/base.theme.properties +++ b/Ghidra/Features/Base/data/base.theme.properties @@ -6,8 +6,8 @@ color.flowtype.fall.through = red color.flowtype.jump.conditional = #007C00 // dark green color.flowtype.jump.unconditional = blue -color.bg.table.selection.bundle = [color]textHighlight -color.fg.table.selection.bundle = [color]textHighlightText +color.bg.table.selection.bundle = [color]system.color.bg.selected.view +color.fg.table.selection.bundle = [color]system.color.fg.selected.view color.fg.table.bundle.disabled = darkGray color.fg.table.bundle.busy = gray color.fg.table.bundle.inactive = black @@ -75,7 +75,7 @@ color.bg.plugin.datamgr.icon.highlight = rgb(204, 204, 255) color.fg.plugin.disassembledview.address = color.fg color.bg.plugin.editors.compositeeditor.text = color.fg -color.bg.plugin.editors.compositeeditor.line = system.color.border +color.bg.plugin.editors.compositeeditor.line = [color]system.color.bg.border color.bg.plugin.editors.compositeeditor.line.interior = #D4D4D4 color.bg.plugin.editors.compositeeditor.byte.header = #DFDFDF color.bg.plugin.editors.compositeeditor.bit.undefined = #F8F8F8 @@ -182,8 +182,6 @@ color.flowtype.fall.through = rgb(164, 66, 66) color.flowtype.jump.conditional = rgb(95, 129, 157) color.flowtype.jump.unconditional = rgb(140, 148, 64) -color.bg.table.selection.bundle = [color]textHighlight -color.fg.table.selection.bundle = [color]textHighlightText color.fg.table.bundle.disabled = lightGray color.fg.table.bundle.busy = gray color.fg.table.bundle.inactive = lightGray @@ -211,7 +209,7 @@ color.bg.plugin.datamgr.edge.default = deepskyblue color.bg.plugin.datamgr.edge.composite = plum color.bg.plugin.datamgr.edge.reference = deepskyblue -color.bg.plugin.editors.compositeeditor.byte.header = [color]text +color.bg.plugin.editors.compositeeditor.byte.header = [color]system.color.bg.view color.fg.plugin.equate.enum = deepskyblue diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractReferenceHover.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractReferenceHover.java index cdcd95932b..6324e7c689 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractReferenceHover.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractReferenceHover.java @@ -46,7 +46,7 @@ import ghidra.util.bean.opteditor.OptionsVetoException; public abstract class AbstractReferenceHover extends AbstractConfigurableHover { private static final int WINDOW_OFFSET = 50; - private static final Color BACKGROUND_COLOR = Colors.BACKGROUND_TOOLTIP; + private static final Color BACKGROUND_COLOR = Colors.BG_TOOLTIP; private static final Color FG_COLOR_NOT_IN_MEMORY = new GColor("color.fg.hint"); private CodeFormatService codeFormatService; @@ -128,8 +128,7 @@ public abstract class AbstractReferenceHover extends AbstractConfigurableHover { String widthOptionName = optionName + Options.DELIMITER + "Dialog Width"; String heightOptionName = optionName + Options.DELIMITER + "Dialog Height"; - if (optionName.equals(widthOptionName) || - optionName.equals(heightOptionName)) { + if (optionName.equals(widthOptionName) || optionName.equals(heightOptionName)) { int dialogWidth = options.getInt(widthOptionName, 600); if (dialogWidth <= 0) { throw new OptionsVetoException( diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/CodeCompletionWindow.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/CodeCompletionWindow.java index 299cd4734b..00a4efe5d4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/CodeCompletionWindow.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/interpreter/CodeCompletionWindow.java @@ -38,7 +38,7 @@ import ghidra.app.plugin.core.console.CodeCompletion; public class CodeCompletionWindow extends JDialog { private static final long serialVersionUID = 1L; /* from ReferenceHoverPlugin */ - private static final Color BACKGROUND_COLOR = Colors.BACKGROUND_TOOLTIP; + private static final Color BACKGROUND_COLOR = Colors.BG_TOOLTIP; protected final InterpreterPanel console; protected final JTextPane outputTextField; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/DnDTreeCellRenderer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/DnDTreeCellRenderer.java index 43c2e48d82..dfc655a028 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/DnDTreeCellRenderer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/programtree/DnDTreeCellRenderer.java @@ -36,6 +36,7 @@ import resources.ResourceManager; class DnDTreeCellRenderer extends DefaultTreeCellRenderer { private static final Color BACKGROUND_UNSELECTED = new GColor("color.bg.tree"); private static final Color BACKGROUND_SELECTED = new GColor("color.bg.tree.selected"); + private static final Color FOREGROUND_SELECTED = new GColor("color.fg.tree.selected"); private static final String DISABLED_DOCS = "DisabledDocument.gif"; private static final String DISABLED_FRAGMENT = "DisabledFragment"; @@ -68,6 +69,7 @@ class DnDTreeCellRenderer extends DefaultTreeCellRenderer { private Color defaultNonSelectionColor; private Color selectionForDragColor; private Color nonSelectionForDragColor; + private Color defaultTextSelectionColor; private int rowForFeedback; /** @@ -77,6 +79,7 @@ class DnDTreeCellRenderer extends DefaultTreeCellRenderer { super(); defaultNonSelectionColor = BACKGROUND_UNSELECTED; defaultSelectionColor = BACKGROUND_SELECTED; + defaultTextSelectionColor = FOREGROUND_SELECTED; rowForFeedback = -1; // disable HTML rendering @@ -168,12 +171,14 @@ class DnDTreeCellRenderer extends DefaultTreeCellRenderer { } else { setBackgroundSelectionColor(defaultSelectionColor); + setTextSelectionColor(defaultTextSelectionColor); setBackgroundNonSelectionColor(defaultNonSelectionColor); } setToolTipText(null); } else { setBackgroundSelectionColor(defaultSelectionColor); + setTextSelectionColor(defaultTextSelectionColor); setBackgroundNonSelectionColor(defaultNonSelectionColor); setToolTipText(dtree.getToolTipText(node)); } @@ -315,11 +320,9 @@ class DnDTreeCellRenderer extends DefaultTreeCellRenderer { private void loadImages() { // try to load icon images iconMap = new HashMap<>(); - String[] iconIds = - { DOCS, FRAGMENT, EMPTY_FRAGMENT, VIEWED_FRAGMENT, VIEWED_EMPTY_FRAGMENT, - VIEWED_CLOSED_FOLDER, VIEWED_OPEN_FOLDER, VIEWED_CLOSED_FOLDER_WITH_DESC, - CLOSED_FOLDER, OPEN_FOLDER, - }; + String[] iconIds = { DOCS, FRAGMENT, EMPTY_FRAGMENT, VIEWED_FRAGMENT, VIEWED_EMPTY_FRAGMENT, + VIEWED_CLOSED_FOLDER, VIEWED_OPEN_FOLDER, VIEWED_CLOSED_FOLDER_WITH_DESC, CLOSED_FOLDER, + OPEN_FOLDER, }; String[] disabledNames = { DISABLED_DOCS, DISABLED_FRAGMENT, DISABLED_EMPTY_FRAGMENT, DISABLED_VIEWED_EMPTY_FRAGMENT, DISABLED_VIEWED_FRAGMENT, DISABLED_VIEWED_CLOSED_FOLDER, DISABLED_VIEWED_OPEN_FOLDER, DISABLED_VIEWED_CLOSED_FOLDER_WITH_DESC, diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/FGVertex.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/FGVertex.java index afe41332a8..ec0d5737fd 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/FGVertex.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/FGVertex.java @@ -40,7 +40,7 @@ import ghidra.program.util.ProgramSelection; */ public interface FGVertex extends VisualVertex { - static final Color TOOLTIP_BACKGROUND_COLOR = Colors.BACKGROUND_TOOLTIP; + static final Color TOOLTIP_BACKGROUND_COLOR = Colors.BG_TOOLTIP; public FGVertex cloneVertex(FGController newController); diff --git a/Ghidra/Framework/Docking/data/docking.theme.properties b/Ghidra/Framework/Docking/data/docking.theme.properties index e76b6fe7da..d2e3cac836 100644 --- a/Ghidra/Framework/Docking/data/docking.theme.properties +++ b/Ghidra/Framework/Docking/data/docking.theme.properties @@ -4,9 +4,9 @@ color.bg.splashscreen = black color.fg.splashscreen = gray -color.bg.header.active = [color]textHighlight +color.bg.header.active = [color]system.color.bg.selected.view color.bg.header.inactive = #A1A1A1 -color.fg.header.active = [color]textHighlightText +color.fg.header.active = [color]system.color.fg.selected.view color.fg.header.inactive = black color.header.drag.cursor = black @@ -170,7 +170,7 @@ color.bg.fieldpanel.selection.and.highlight = #344028 // yellow greenish // docking buttons color.fg.button = darkGray -color.bg.filechooser.shortcut = system.color.bg.widget +color.bg.filechooser.shortcut = [color]system.color.bg.view diff --git a/Ghidra/Framework/Docking/src/main/java/docking/EditWindow.java b/Ghidra/Framework/Docking/src/main/java/docking/EditWindow.java index 9512e3eb65..61ab837ab4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/EditWindow.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/EditWindow.java @@ -133,7 +133,7 @@ public class EditWindow extends JWindow { private void create() { textField = new JTextField(" "); JPanel panel = new JPanel(new BorderLayout()); - Color bgColor = Colors.BACKGROUND_TOOLTIP; + Color bgColor = Colors.BG_TOOLTIP; panel.setBackground(bgColor); panel.add(textField, BorderLayout.CENTER); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/SplitPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/SplitPanel.java index 9a3d9cea87..f36458f820 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/SplitPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/SplitPanel.java @@ -40,7 +40,7 @@ public class SplitPanel extends JPanel { this.rightComp = rightComp; this.isHorizontal = isHorizontal; divider = new Divider(); - divider.setBackground(new GColor("SplitPane.background")); + divider.setBackground(new GColor("laf.color.SplitPane.background")); add(leftComp); add(divider); add(rightComp); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/GThemeValuesCache.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/GThemeValuesCache.java new file mode 100644 index 0000000000..db830a797d --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/GThemeValuesCache.java @@ -0,0 +1,80 @@ +/* ### + * 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 generic.theme.GThemeValueMap; +import generic.theme.ThemeManager; + +/** + * Shares values for the three theme value tables so they all don't have their own copies + */ +public class GThemeValuesCache { + + private ThemeManager themeManager; + private GThemeValueMap currentValues; + private GThemeValueMap themeValues; + private GThemeValueMap defaultValues; + private GThemeValueMap lightValues; + private GThemeValueMap darkValues; + + public GThemeValuesCache(ThemeManager themeManager) { + this.themeManager = themeManager; + } + + public void clear() { + currentValues = null; + themeValues = null; + defaultValues = null; + lightValues = null; + darkValues = null; + } + + public GThemeValueMap getCurrentValues() { + if (currentValues == null) { + currentValues = themeManager.getCurrentValues(); + } + return currentValues; + } + + public GThemeValueMap getThemeValues() { + if (themeValues == null) { + themeValues = themeManager.getThemeValues(); + } + return themeValues; + } + + public GThemeValueMap getDefaultValues() { + if (defaultValues == null) { + defaultValues = themeManager.getDefaults(); + } + return defaultValues; + } + + public GThemeValueMap getLightValues() { + if (lightValues == null) { + lightValues = themeManager.getApplicationLightDefaults(); + } + return lightValues; + } + + public GThemeValueMap getDarkValues() { + if (darkValues == null) { + darkValues = themeManager.getApplicationDarkDefaults(); + } + return darkValues; + } + +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTable.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTable.java index 12a328bdbc..89d8fceb37 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTable.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTable.java @@ -43,14 +43,14 @@ public class ThemeColorTable extends JPanel implements ActionContextProvider { private GFilterTable filterTable; private ThemeManager themeManager; - public ThemeColorTable(ThemeManager themeManager) { + public ThemeColorTable(ThemeManager themeManager, GThemeValuesCache valuesProvider) { super(new BorderLayout()); this.themeManager = themeManager; - colorTableModel = new ThemeColorTableModel(themeManager); + colorTableModel = new ThemeColorTableModel(valuesProvider); filterTable = new GFilterTable<>(colorTableModel); table = filterTable.getTable(); - table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + table.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); table.addKeyListener(new KeyAdapter() { @Override diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTableModel.java index 99ad128350..e8dc1ba7d1 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeColorTableModel.java @@ -43,11 +43,11 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel { + private class IdColumn extends AbstractDynamicTableColumn { @Override public String getColumnName() { @@ -129,7 +129,8 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel { + private class ValueColumn + extends AbstractDynamicTableColumn { private ThemeColorRenderer renderer; private String name; private Supplier valueSupplier; @@ -236,7 +237,7 @@ public class ThemeColorTableModel extends GDynamicColumnTableModel filterTable; private ThemeManager themeManager; - public ThemeFontTable(ThemeManager themeManager) { + public ThemeFontTable(ThemeManager themeManager, GThemeValuesCache valuesProvider) { super(new BorderLayout()); this.themeManager = themeManager; - fontTableModel = new ThemeFontTableModel(themeManager); + fontTableModel = new ThemeFontTableModel(valuesProvider); filterTable = new GFilterTable<>(fontTableModel); table = filterTable.getTable(); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeFontTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeFontTableModel.java index 35740f365b..a846a3be5c 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeFontTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeFontTableModel.java @@ -24,7 +24,8 @@ import java.util.function.Supplier; import javax.swing.JLabel; import docking.widgets.table.*; -import generic.theme.*; +import generic.theme.FontValue; +import generic.theme.GThemeValueMap; import ghidra.docking.settings.Settings; import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProviderStub; @@ -39,11 +40,11 @@ public class ThemeFontTableModel extends GDynamicColumnTableModel"; } - if (fontValue.getReferenceId() != null) { - return "[" + fontValue.getReferenceId() + "]"; + if (resolvedFont.refId() != null) { + return "[" + resolvedFont.refId() + "]"; } - Font font = fontValue.getRawValue(); + Font font = resolvedFont.font(); return FontValue.fontToString(font); } - class IdColumn extends AbstractDynamicTableColumn { + private class IdColumn extends AbstractDynamicTableColumn { @Override public String getColumnName() { @@ -128,7 +138,8 @@ public class ThemeFontTableModel extends GDynamicColumnTableModel { + private class FontValueColumn + extends AbstractDynamicTableColumn { private ThemeFontRenderer renderer; private String name; private Supplier valueSupplier; @@ -145,19 +156,23 @@ public class ThemeFontTableModel extends GDynamicColumnTableModel getColumnRenderer() { + public GColumnRenderer getColumnRenderer() { return renderer; } - public Comparator getComparator() { + public Comparator getComparator() { return (v1, v2) -> { if (v1 == null && v2 == null) { return 0; @@ -179,33 +194,31 @@ public class ThemeFontTableModel extends GDynamicColumnTableModel { + private class ThemeFontRenderer extends AbstractGColumnRenderer { @Override public Component getTableCellRendererComponent(GTableCellRenderingData data) { JLabel label = (JLabel) super.getTableCellRendererComponent(data); - FontValue fontValue = (FontValue) data.getValue(); + ResolvedFont resolvedFont = (ResolvedFont) data.getValue(); - String text = getValueText(fontValue); + String text = getValueText(resolvedFont); label.setText(text); label.setOpaque(true); + + Font font = resolvedFont.font(); + if (font != null) { + setToolTipText(FontValue.fontToString(font)); + } + return label; } @Override - public String getFilterString(FontValue fontValue, Settings settings) { - return getValueText(fontValue); + public String getFilterString(ResolvedFont resolvedFont, Settings settings) { + return getValueText(resolvedFont); } } - /** - * Returns the original value for the id as defined by the current theme - * @param id the resource id to get a font value for - * @return the original value for the id as defined by the current theme - */ - public FontValue getThemeValue(String id) { - return themeValues.getFont(id); - } - + private record ResolvedFont(String id, String refId, Font font) {/**/} } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeIconTable.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeIconTable.java index 15ff8f6e02..d89ecc1ac0 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeIconTable.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeIconTable.java @@ -41,10 +41,10 @@ public class ThemeIconTable extends JPanel implements ActionContextProvider { private GFilterTable filterTable; private ThemeManager themeManager; - public ThemeIconTable(ThemeManager themeManager) { + public ThemeIconTable(ThemeManager themeManager, GThemeValuesCache valuesProvider) { super(new BorderLayout()); this.themeManager = themeManager; - iconTableModel = new ThemeIconTableModel(themeManager); + iconTableModel = new ThemeIconTableModel(valuesProvider); filterTable = new GFilterTable<>(iconTableModel); table = filterTable.getTable(); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeIconTableModel.java b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeIconTableModel.java index 13b60bc181..f081de8f99 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeIconTableModel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/theme/gui/ThemeIconTableModel.java @@ -39,11 +39,11 @@ public class ThemeIconTableModel extends GDynamicColumnTableModel { + /** + * Returns the original value for the id as defined by the current theme + * @param id the resource id to get a font value for + * @return the original value for the id as defined by the current theme + */ + public IconValue getThemeValue(String id) { + return themeValues.getIcon(id); + } + + private class IdColumn extends AbstractDynamicTableColumn { @Override public String getColumnName() { @@ -116,7 +125,8 @@ public class ThemeIconTableModel extends GDynamicColumnTableModel { + private class IconValueColumn + extends AbstractDynamicTableColumn { private ThemeIconRenderer renderer; private String name; private Supplier valueSupplier; @@ -232,14 +242,6 @@ public class ThemeIconTableModel extends GDynamicColumnTableModelFor example, 'system.color.border' defaults to 'controlShadow', but maps to 'nimbusBorder' - * in the Nimbus Look and Feel. - * - * @param map the map - */ - public void setSystemDefaults(GThemeValueMap map) { - systemValues = map; - } - /** * Sets the map of Java default UI values. These are the UI values defined by the current Java * Look and Feel. @@ -409,4 +396,9 @@ public class ApplicationThemeManager extends ThemeManager { GColor.refreshAll(currentValues); GIcon.refreshAll(currentValues); } + + private void cleanUiDefaults() { + UIDefaults defaults = UIManager.getDefaults(); + defaults.clear(); + } } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/ColorValue.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/ColorValue.java index 746e95b14f..0069e2c85c 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/ColorValue.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/ColorValue.java @@ -30,7 +30,6 @@ import utilities.util.reflection.ReflectionUtilities; public class ColorValue extends ThemeValue { private static final String COLOR_ID_PREFIX = "color."; private static final String EXTERNAL_PREFIX = "[color]"; - private static final String SYSTEM_COLOR_PREFIX = "system.color"; public static final Color LAST_RESORT_DEFAULT = new Color(128, 128, 128); @@ -72,8 +71,7 @@ public class ColorValue extends ThemeValue { * @return true if the given key string is a valid external key for a color value */ public static boolean isColorKey(String key) { - return key.startsWith(COLOR_ID_PREFIX) || key.startsWith(EXTERNAL_PREFIX) || - key.startsWith(SYSTEM_COLOR_PREFIX); + return key.startsWith(COLOR_ID_PREFIX) || key.startsWith(EXTERNAL_PREFIX); } /** @@ -107,15 +105,14 @@ public class ColorValue extends ThemeValue { "Application", "ghidra.GhidraRun", "java.lang.Class", "java.lang.Thread"); t.setStackTrace(filtered); - Msg.error(this, - "Could not resolve indirect color path for \"" + unresolvedId + - "\" for primary id \"" + primaryId + "\", using last resort default", - t); + Msg.error(this, "Could not resolve indirect color path for \"" + unresolvedId + + "\" for primary id \"" + primaryId + "\", using last resort default", t); + return LAST_RESORT_DEFAULT; } private static String toExternalId(String internalId) { - if (internalId.startsWith(COLOR_ID_PREFIX) || internalId.startsWith(SYSTEM_COLOR_PREFIX)) { + if (internalId.startsWith(COLOR_ID_PREFIX)) { return internalId; } return EXTERNAL_PREFIX + internalId; diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/GColor.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/GColor.java index 653a85c9c0..55d16233e6 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/GColor.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/GColor.java @@ -37,6 +37,8 @@ import ghidra.util.datastruct.WeakStore; * set the default value by adding this line "color.mywidget.bg = white". */ public class GColor extends Color { + private static final int MISSING_COLOR_RGB = 0x808080; + // keeps a weak reference to all uses of GColor, so their cached color value can be refreshed private static WeakStore inUseColors = new WeakStore<>(); @@ -50,11 +52,22 @@ public class GColor extends Color { * @param id the id used to lookup the current value for this color */ public GColor(String id) { - super(0x808080); + super(MISSING_COLOR_RGB); this.id = id; delegate = Gui.getColor(id); inUseColors.add(this); + } + /** + * Copy constructor. Used primarily to convert a GColorUiResource to a GColor without having to + * lookup the color which can cause errors during theme transitions. + * @param gColor the gColor to copy + */ + protected GColor(GColor gColor) { + super(MISSING_COLOR_RGB); + this.id = gColor.id; + delegate = gColor.delegate; + inUseColors.add(this); } private GColor(String id, int alpha) { diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/GColorUIResource.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/GColorUIResource.java index afc07e55aa..5bad9df0c5 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/GColorUIResource.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/GColorUIResource.java @@ -15,8 +15,6 @@ */ package generic.theme; -import java.awt.Color; - import javax.swing.UIDefaults; import javax.swing.plaf.UIResource; @@ -36,8 +34,8 @@ public class GColorUIResource extends GColor implements UIResource { * Returns a non-UIResource GColor for this GColorUiResource's id * @return a non-UIResource GColor for this GColorUiResource's id */ - public Color toGColor() { - return new GColor(getId()); + public GColor toGColor() { + return new GColor(this); } } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeDefaults.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeDefaults.java index 468aaba96a..a3ea36ecae 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeDefaults.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeDefaults.java @@ -15,6 +15,8 @@ */ package generic.theme; +import static generic.theme.SystemThemeIds.*; + import java.awt.Color; /** TODO doc how clients should use this in their code, with @@ -24,18 +26,9 @@ import java.awt.Color; * Colors.Java.BORDER */ public class GThemeDefaults { - public static class Ids { - public static class Java { - public static final String BORDER = "system.color.border"; - } - public static class Fonts { - public static final String STANDARD = "font.standard"; - public static final String BOLD = "font.bold"; - public static final String ITALIC = "font.italic"; - public static final String BOLD_ITALIC = "font.bold.italic"; public static final String MONOSPACED = "font.monospaced"; } } @@ -44,11 +37,23 @@ public class GThemeDefaults { * Colors mapped to system values */ public static class Colors { + //@formatter:off + + // standard color concepts defined by LookAndFeel + public static final GColor BG_CONTROL = new GColor(BG_CONTROL_ID); + public static final GColor BG_VIEW = new GColor(BG_VIEW_ID); + public static final GColor BG_TOOLTIP = new GColor(BG_TOOLTIP_ID); + public static final GColor BG_VIEW_SELECTED = new GColor(BG_VIEW_SELECTED_ID); + public static final GColor BG_BORDER = new GColor(BG_BORDER_ID); + + public static final GColor FG_CONTROL = new GColor(FG_CONTROL_ID); + public static final GColor FG_VIEW = new GColor(FG_VIEW_ID); + public static final GColor FG_TOOLTIP = new GColor(FG_TOOLTIP_ID); + public static final GColor FG_VIEW_SELECTED = new GColor(FG_VIEW_SELECTED_ID); + public static final GColor FG_DISABLED = new GColor(FG_DISABLED_ID); // generic color concepts - //@formatter:off public static final GColor BACKGROUND = new GColor("color.bg"); - public static final GColor BACKGROUND_TOOLTIP = new GColor("color.bg.tooltip"); public static final GColor CURSOR = new GColor("color.cursor.focused"); public static final GColor DISABLED = new GColor("color.palette.disabled"); public static final GColor ERROR = new GColor("color.fg.error"); // TODO replace most uses of this with Messages.ERROR @@ -57,7 +62,7 @@ public class GThemeDefaults { //@formatter:on public static class Java { - public static final GColor BORDER = new GColor(Ids.Java.BORDER); + public static final GColor BORDER = BG_BORDER; } public static class Tables { diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeValueMap.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeValueMap.java index fb20579c6d..43d601b784 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeValueMap.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/GThemeValueMap.java @@ -15,6 +15,8 @@ */ package generic.theme; +import java.awt.Color; +import java.awt.Font; import java.io.File; import java.net.URL; import java.util.*; @@ -340,4 +342,50 @@ public class GThemeValueMap { public Set getIconIds() { return iconMap.keySet(); } + + /** + * Returns the resolved color, following indirections as need to get the color ultimately + * assigned to the given id. + * @param id the id for which to get a color + * @return the resolved color, following indirections as need to get the color ultimately + * assigned to the given id. + */ + public Color getResolvedColor(String id) { + ColorValue colorValue = colorMap.get(id); + if (colorValue != null) { + return colorValue.get(this); + } + return null; + } + + /** + * Returns the resolved font, following indirections as need to get the font ultimately + * assigned to the given id. + * @param id the id for which to get a font + * @return the resolved font, following indirections as need to get the font ultimately + * assigned to the given id + */ + public Font getResolvedFont(String id) { + FontValue fontValue = fontMap.get(id); + if (fontValue != null) { + return fontValue.get(this); + } + return null; + } + + /** + * Returns the resolved icon, following indirections as need to get the icon ultimately + * assigned to the given id. + * @param id the id for which to get an icon + * @return the resolved icon, following indirections as need to get the icon ultimately + * assigned to the given id + */ + public Icon getResolvedIcon(String id) { + IconValue iconValue = iconMap.get(id); + if (iconValue != null) { + return iconValue.get(this); + } + return null; + } + } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/ApplicationThemeDefaultsProvider.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/PropertyFileThemeDefaults.java similarity index 89% rename from Ghidra/Framework/Gui/src/main/java/generic/theme/ApplicationThemeDefaultsProvider.java rename to Ghidra/Framework/Gui/src/main/java/generic/theme/PropertyFileThemeDefaults.java index 09b746fc63..874e178d29 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/ApplicationThemeDefaultsProvider.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/PropertyFileThemeDefaults.java @@ -26,13 +26,13 @@ import ghidra.util.Msg; * Loads all the system theme.property files that contain all the default color, font, and * icon values. */ -public class ApplicationThemeDefaultsProvider implements ThemeDefaultsProvider { +public class PropertyFileThemeDefaults implements ApplicationThemeDefaults { private GThemeValueMap defaults = new GThemeValueMap(); private GThemeValueMap darkDefaults = new GThemeValueMap(); private Map lafDefaultsMap = new HashMap<>(); - ApplicationThemeDefaultsProvider() { + PropertyFileThemeDefaults() { loadThemeDefaultFiles(); } @@ -69,17 +69,17 @@ public class ApplicationThemeDefaultsProvider implements ThemeDefaultsProvider { } @Override - public GThemeValueMap getDefaults() { + public GThemeValueMap getLightValues() { return defaults; } @Override - public GThemeValueMap getDarkDefaults() { + public GThemeValueMap getDarkValues() { return darkDefaults; } @Override - public GThemeValueMap getLookAndFeelDefaults(LafType lafType) { + public GThemeValueMap getLookAndFeelValues(LafType lafType) { return lafDefaultsMap.get(lafType); } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/StubThemeManager.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/StubThemeManager.java index f583ea32a7..8c6feb0e66 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/StubThemeManager.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/StubThemeManager.java @@ -215,21 +215,21 @@ public class StubThemeManager extends ThemeManager { } @Override - protected ThemeDefaultsProvider getThemeDefaultsProvider() { - return new ThemeDefaultsProvider() { + protected ApplicationThemeDefaults getApplicationDefaults() { + return new ApplicationThemeDefaults() { @Override - public GThemeValueMap getDefaults() { + public GThemeValueMap getLightValues() { return null; } @Override - public GThemeValueMap getDarkDefaults() { + public GThemeValueMap getDarkValues() { return null; } @Override - public GThemeValueMap getLookAndFeelDefaults(LafType lafType) { + public GThemeValueMap getLookAndFeelValues(LafType lafType) { return null; } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/SystemThemeIds.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/SystemThemeIds.java new file mode 100644 index 0000000000..4d7a858f88 --- /dev/null +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/SystemThemeIds.java @@ -0,0 +1,68 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme; + +import generic.theme.laf.UiDefaultsMapper; + +/** + * These are the standard system ids defined to represent general LookAndFeel color and font + * concepts. Various LaF have different names for these concepts and even defines additional + * concepts. These are the ones we use regardless of the LookAndFeel being used. When we + * load a specific LookAndFeel, a {@link UiDefaultsMapper}, specific to that LaF is used to map + * its system ids to our standard system ids. Also, {@link GThemeDefaults} uses these system + * ids to define colors that can be used throughout the application without using these ids + * directly. + *

+ * The ids are assigned to categories as follows: + *

    + *
  • CONTROL- these ids are used for colors and fonts for general system components such as + * Buttons, Checkboxes, or anything that doesn't fit into one of the other areas
  • + *
  • VIEW - these ids are used for the colors and fonts used for widgets that display data + * such as Trees, Tables, TextFieds, and Lists
  • + *
  • MENU - these ids are used by menu components such as Menus and MenuItems.
  • + *
  • TOOLTIP - these ids are used just by the tooltip component + *
+ *

+ * For each of those categories the ids specify a specific property for those components. + *

    + *
  • BG - the background color + *
  • FG - the foreground color + *
  • BG_SELECTED - the background color when the component is selected + *
  • FG_SELECTED - the foreground color when the component is selected + *
  • FG_DISABLED - the foreground color when the component is disabled + *
  • BG_BORDER - the border color + *
  • FONT - the font + * + *
+ */ +public class SystemThemeIds { + public static final String FONT_CONTROL_ID = "system.font.control"; + public static final String FONT_VIEW_ID = "system.font.view"; + public static final String FONT_MENU_ID = "system.font.menu"; + + public static final String BG_CONTROL_ID = "system.color.bg.control"; + public static final String BG_VIEW_ID = "system.color.bg.view"; + public static final String BG_TOOLTIP_ID = "system.color.bg.tooltip"; + public static final String BG_VIEW_SELECTED_ID = "system.color.bg.selected.view"; + public static final String BG_BORDER_ID = "system.color.bg.border"; + + public static final String FG_CONTROL_ID = "system.color.fg.control"; + public static final String FG_VIEW_ID = "system.color.fg.view"; + public static final String FG_TOOLTIP_ID = "system.color.fg.tooltip"; + public static final String FG_VIEW_SELECTED_ID = "system.color.fg.selected.view"; + public static final String FG_DISABLED_ID = "system.color.fg.disabled"; + +} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/ThemeManager.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/ThemeManager.java index 7da3583276..a3a4437128 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/ThemeManager.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/ThemeManager.java @@ -30,7 +30,6 @@ import ghidra.util.datastruct.WeakDataStructureFactory; import ghidra.util.datastruct.WeakSet; import resources.ResourceManager; import utilities.util.reflection.ReflectionUtilities; -import utility.function.Callback; /** * This class manages application themes and their values. The ThemeManager is an abstract @@ -67,10 +66,9 @@ public abstract class ThemeManager { protected GTheme activeTheme = getDefaultTheme(); protected GThemeValueMap javaDefaults = new GThemeValueMap(); - protected GThemeValueMap systemValues = new GThemeValueMap(); protected GThemeValueMap currentValues = new GThemeValueMap(); - protected ThemeDefaultsProvider themeDefaultsProvider; + protected ApplicationThemeDefaults applicationDefaults; // these notifications are only when the user is manipulating theme values, so rare and at // user speed, so using copy on read @@ -86,11 +84,11 @@ public abstract class ThemeManager { // default behavior is only install to INSTANCE if first time INSTANCE = this; } - themeDefaultsProvider = getThemeDefaultsProvider(); + applicationDefaults = getApplicationDefaults(); } - protected ThemeDefaultsProvider getThemeDefaultsProvider() { - return new ApplicationThemeDefaultsProvider(); + protected ApplicationThemeDefaults getApplicationDefaults() { + return new PropertyFileThemeDefaults(); } protected void installInGui() { @@ -101,12 +99,11 @@ public abstract class ThemeManager { GThemeValueMap map = new GThemeValueMap(); map.load(javaDefaults); - map.load(systemValues); - map.load(themeDefaultsProvider.getDefaults()); + map.load(applicationDefaults.getLightValues()); if (activeTheme.useDarkDefaults()) { - map.load(themeDefaultsProvider.getDarkDefaults()); + map.load(applicationDefaults.getDarkValues()); } - map.load(themeDefaultsProvider.getLookAndFeelDefaults(getLookAndFeelType())); + map.load(applicationDefaults.getLookAndFeelValues(getLookAndFeelType())); map.load(activeTheme); currentValues = map; } @@ -270,12 +267,11 @@ public abstract class ThemeManager { public GThemeValueMap getThemeValues() { GThemeValueMap map = new GThemeValueMap(); map.load(javaDefaults); - map.load(systemValues); - map.load(themeDefaultsProvider.getDefaults()); + map.load(applicationDefaults.getLightValues()); if (activeTheme.useDarkDefaults()) { - map.load(themeDefaultsProvider.getDarkDefaults()); + map.load(applicationDefaults.getDarkValues()); } - map.load(themeDefaultsProvider.getLookAndFeelDefaults(getLookAndFeelType())); + map.load(applicationDefaults.getLookAndFeelValues(getLookAndFeelType())); map.load(activeTheme); return map; } @@ -418,9 +414,9 @@ public abstract class ThemeManager { * theme.properties files */ public GThemeValueMap getApplicationDarkDefaults() { - GThemeValueMap map = new GThemeValueMap(themeDefaultsProvider.getDefaults()); - map.load(themeDefaultsProvider.getDarkDefaults()); - map.load(themeDefaultsProvider.getLookAndFeelDefaults(getLookAndFeelType())); + GThemeValueMap map = new GThemeValueMap(applicationDefaults.getLightValues()); + map.load(applicationDefaults.getDarkValues()); + map.load(applicationDefaults.getLookAndFeelValues(getLookAndFeelType())); return map; } @@ -431,10 +427,25 @@ public abstract class ThemeManager { * theme.properties files */ public GThemeValueMap getApplicationLightDefaults() { - GThemeValueMap map = new GThemeValueMap(themeDefaultsProvider.getDefaults()); + GThemeValueMap map = new GThemeValueMap(applicationDefaults.getLightValues()); + map.load(applicationDefaults.getLookAndFeelValues(getLookAndFeelType())); return map; } + /** + * Returns application defaults values (does not include java default values) + * @return application defaults values (does not include java default values) + */ + public GThemeValueMap getApplicationOverrides() { + GThemeValueMap currentDefaults = new GThemeValueMap(); + currentDefaults.load(applicationDefaults.getLightValues()); + if (activeTheme.useDarkDefaults()) { + currentDefaults.load(applicationDefaults.getDarkValues()); + } + currentDefaults.load(applicationDefaults.getLookAndFeelValues(getLookAndFeelType())); + return currentDefaults; + } + /** * Returns a {@link GThemeValueMap} containing all default values for the current theme. It * is a combination of application defined defaults and java {@link LookAndFeel} defaults. @@ -442,12 +453,11 @@ public abstract class ThemeManager { */ public GThemeValueMap getDefaults() { GThemeValueMap currentDefaults = new GThemeValueMap(javaDefaults); - currentDefaults.load(systemValues); - currentDefaults.load(themeDefaultsProvider.getDefaults()); + currentDefaults.load(applicationDefaults.getLightValues()); if (activeTheme.useDarkDefaults()) { - currentDefaults.load(themeDefaultsProvider.getDarkDefaults()); + currentDefaults.load(applicationDefaults.getDarkValues()); } - currentDefaults.load(themeDefaultsProvider.getLookAndFeelDefaults(getLookAndFeelType())); + currentDefaults.load(applicationDefaults.getLookAndFeelValues(getLookAndFeelType())); return currentDefaults; } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/CustomNimbusLookAndFeel.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/CustomNimbusLookAndFeel.java new file mode 100644 index 0000000000..46ed99320b --- /dev/null +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/CustomNimbusLookAndFeel.java @@ -0,0 +1,85 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme.laf; + +import java.util.Map; + +import javax.swing.LookAndFeel; +import javax.swing.UIDefaults; +import javax.swing.plaf.nimbus.NimbusLookAndFeel; + +import generic.theme.ApplicationThemeManager; +import generic.theme.GThemeValueMap; +import generic.theme.laf.nimbus.SelectedTreePainter; + +/** + * Extends the {@link NimbusLookAndFeel} (Nimbus) to intercept {@link #getDefaults()}. Nimbus + * does not honor changes to the UIDefaults after it is installed as the active + * {@link LookAndFeel}, so we have to make the changes at the time the UIDefaults are installed. + *

+ * To get around this issue, we extend Nimbus so that we can install our GColors and + * overridden properties as Nimbus is being installed, specifically during the call to the + * getDefaults() method. For all other Look And Feels, the GColors and overridden properties are + * changed in the UIDefaults after the Look And Feel is installed, so they don't need to extend the + * Look and Feel class. + *

+ * Also, unlike other LaFs, Nimbus needs to be reinstalled every time we need to make a change to + * any of the UIDefaults values, since it does not respond to changes other than when first + * installed. + */ +public class CustomNimbusLookAndFeel extends NimbusLookAndFeel { + private ApplicationThemeManager themeManager; + private Map normalizedIdToLafIdMap; + + CustomNimbusLookAndFeel(ApplicationThemeManager themeManager) { + this.themeManager = themeManager; + } + + @Override + public UIDefaults getDefaults() { + UIDefaults defaults = super.getDefaults(); + + installCustomPainters(defaults); + + // normally all of this wiring is handled by the LookAndFeelManager (see above) + UiDefaultsMapper uiDefaultsMapper = new NimbusUiDefaultsMapper(defaults); + installJavaDefaultsIntoThemeManager(uiDefaultsMapper); + uiDefaultsMapper.installValuesIntoUIDefaults(getApplicationOverrides()); + + normalizedIdToLafIdMap = uiDefaultsMapper.getNormalizedIdToLafIdMap(); + return defaults; + } + + protected void installJavaDefaultsIntoThemeManager(UiDefaultsMapper uiDefaultsMapper) { + GThemeValueMap javaDefaults = uiDefaultsMapper.getJavaDefaults(); + themeManager.setJavaDefaults(javaDefaults); + } + + private void installCustomPainters(UIDefaults defaults) { + defaults.put("Tree:TreeCell[Enabled+Selected].backgroundPainter", + new SelectedTreePainter()); + defaults.put("Tree:TreeCell[Focused+Selected].backgroundPainter", + new SelectedTreePainter()); + } + + public Map getNormalizedIdToLafIdMap() { + return normalizedIdToLafIdMap; + } + + protected GThemeValueMap getApplicationOverrides() { + return themeManager.getApplicationOverrides(); + } +} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatDarkUiDefaultsMapper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatDarkUiDefaultsMapper.java new file mode 100644 index 0000000000..4b7b1eac54 --- /dev/null +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatDarkUiDefaultsMapper.java @@ -0,0 +1,68 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme.laf; + +import static generic.theme.SystemThemeIds.*; + +import java.awt.Color; + +import javax.swing.UIDefaults; + +import ghidra.util.WebColors; + +public class FlatDarkUiDefaultsMapper extends FlatUiDefaultsMapper { + + protected FlatDarkUiDefaultsMapper(UIDefaults defaults) { + super(defaults); + } + + @Override + protected void assignSystemColorValues() { + super.assignSystemColorValues(); + + // We don't think the FlatDark LaF's view background (Trees, Tables, Lists) is dark + // enough, so we are overriding the view group background and foreground colors + assignSystemColorDirect(BG_VIEW_ID, new Color(0x1c1d1e)); + assignSystemColorDirect(FG_VIEW_ID, WebColors.LIGHT_GRAY); + } + + @Override + protected void assignNormalizedColorValues() { + super.assignNormalizedColorValues(); + + // + // These components are initialized to "text", but we want them mapped to use + // our view background color so that they look like normal editable widgets + // + overrideColor("ComboBox.background", BG_VIEW_ID); + overrideColor("ComboBox.background", BG_VIEW_ID); + overrideColor("EditorPane.background", BG_VIEW_ID); + overrideColor("FormattedTextField.background", BG_VIEW_ID); + overrideColor("List.background", BG_VIEW_ID); + overrideColor("PasswordField.background", BG_VIEW_ID); + overrideColor("Table.background", BG_VIEW_ID); + overrideColor("Table.focusCellBackground", BG_VIEW_ID); + overrideColor("TableHeader.focusCellBackground", BG_VIEW_ID); + overrideColor("TextField.background", BG_VIEW_ID); + overrideColor("Tree.background", BG_VIEW_ID); + overrideColor("Tree.textBackground", BG_VIEW_ID); + overrideColor("TextArea.background", BG_VIEW_ID); + overrideColor("TextArea.foreground", BG_VIEW_ID); + overrideColor("TextPane.background", BG_VIEW_ID); + overrideColor("TextPane.foreground", BG_VIEW_ID); + } + +} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatLookAndFeelManager.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatLookAndFeelManager.java index 39c61836d4..dfdd39a08a 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatLookAndFeelManager.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatLookAndFeelManager.java @@ -15,18 +15,16 @@ */ package generic.theme.laf; +import javax.swing.UIDefaults; import javax.swing.UIManager; -import generic.theme.*; +import generic.theme.ApplicationThemeManager; +import generic.theme.LafType; public class FlatLookAndFeelManager extends LookAndFeelManager { public FlatLookAndFeelManager(LafType laf, ApplicationThemeManager themeManager) { super(laf, themeManager); - - // establish system color to LookAndFeel colors - systemToLafMap.addColor(new ColorValue(SYSTEM_WIDGET_BACKGROUND_COLOR_ID, "text")); - systemToLafMap.addColor(new ColorValue(SYSTEM_TOOLTIP_BACKGROUND_COLOR_ID, "info")); } @Override @@ -39,7 +37,10 @@ public class FlatLookAndFeelManager extends LookAndFeelManager { } @Override - protected ThemeGrouper getThemeGrouper() { - return new FlatThemeGrouper(); + protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { + if (getLookAndFeelType() == LafType.FLAT_DARK) { + return new FlatDarkUiDefaultsMapper(defaults); + } + return new FlatUiDefaultsMapper(defaults); } } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatThemeGrouper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatThemeGrouper.java deleted file mode 100644 index bed475a323..0000000000 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatThemeGrouper.java +++ /dev/null @@ -1,62 +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 generic.theme.laf; - -import generic.theme.GThemeValueMap; - -/** - * Adds specialized groupings unique to the Flat LookAndFeels - */ -public class FlatThemeGrouper extends ThemeGrouper { - - @Override - public void group(GThemeValueMap values) { - // @formatter:off - defineCustomColorGroup("color.flat.menu.hover.bg", "MenuBar.hoverBackground", values); - defineCustomColorGroup("color.flat.button.hover.bg", "Button.hoverBackground", values); - defineCustomColorGroup("color.flat.button.selected.bg", "Button.selectedBackground",values); - defineCustomColorGroup("color.flat.button.toolbar.hover.bg", "Button.toolbar.hoverBackground",values); - defineCustomColorGroup("color.flat.button.toolbar.pressed.bg", "Button.toolbar.pressedBackground",values); - defineCustomColorGroup("color.flat.checkbox.icon.focus.border", "CheckBox.icon.focusedBorderColor",values); - defineCustomColorGroup("color.flat.menu.accelerator.fg", "Menu.acceleratorForeground",values); - - - defineCustomColorGroup("color.flat.focus.border", "Button.focusedBorderColor", values); - defineCustomColorGroup("color.flat.focus", "Component.focusColor", values); - defineCustomColorGroup("color.flat.focus.bg", "Button.focusedBackground", values); - defineCustomColorGroup("color.flat.checkmark", "CheckBox.icon.checkmarkColor", values); - defineCustomColorGroup("color.flat.disabled", "Button.disabledBorderColor", values); - defineCustomColorGroup("color.flat.disabled.selected", "Button.disabledSelectedBackground",values); - defineCustomColorGroup("color.flat.arrow", "Spinner.buttonArrowColor",values); - defineCustomColorGroup("color.flat.arrow.disabled", "Spinner.buttonDisabledArrowColor",values); - defineCustomColorGroup("color.flat.arrow.hover", "Spinner.buttonHoverArrowColor",values); - defineCustomColorGroup("color.flat.arrow.pressed", "Spinner.buttonPressedArrowColor",values); - - defineCustomColorGroup("color.flat.dropcell.bg", "List.dropCellBackground",values); - defineCustomColorGroup("color.flat.dropline", "List.dropLineColor",values); - defineCustomColorGroup("color.flat.underline", "MenuItem.underlineSelectionColor",values); - defineCustomColorGroup("color.flat.docking.bg", "ToolBar.dockingBackground",values); - defineCustomColorGroup("color.flat.progressbar.bg", "ProgressBar.background",values); - defineCustomColorGroup("color.flat.progressbar.fg", "ProgressBar.foreground",values); - defineCustomColorGroup("color.flat.icon.bg", "Tree.icon.openColor",values); - defineCustomColorGroup("color.flat.selection.inactive", "Tree.selectionInactiveBackground",values); - - - // @formatter:on - super.group(values); - } - -} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatUiDefaultsMapper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatUiDefaultsMapper.java new file mode 100644 index 0000000000..686dac4683 --- /dev/null +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/FlatUiDefaultsMapper.java @@ -0,0 +1,65 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme.laf; + +import javax.swing.UIDefaults; + +public class FlatUiDefaultsMapper extends UiDefaultsMapper { + + protected FlatUiDefaultsMapper(UIDefaults defaults) { + super(defaults); + } + + @Override + protected void registerIgnoredLafIds() { + super.registerIgnoredLafIds(); + ignoredLafIds.add("Actions.Blue"); + ignoredLafIds.add("Actions.Green"); + ignoredLafIds.add("Actions.Grey"); + ignoredLafIds.add("Actions.Greyinline"); + ignoredLafIds.add("Actions.Red"); + ignoredLafIds.add("Actions.Yellow"); + + ignoredLafIds.add("Objects.BlackText"); + ignoredLafIds.add("Objects.Blue"); + ignoredLafIds.add("Objects.Green"); + ignoredLafIds.add("Objects.GreenAndroid"); + ignoredLafIds.add("Objects.Grey"); + ignoredLafIds.add("Objects.Pink"); + ignoredLafIds.add("Objects.Purple"); + ignoredLafIds.add("Objects.Red"); + ignoredLafIds.add("Objects.RedStatus"); + ignoredLafIds.add("Objects.Yellow"); + ignoredLafIds.add("Objects.YellowDark"); + + ignoredLafIds.add("h0.font"); + ignoredLafIds.add("h00.font"); + ignoredLafIds.add("h1.font"); + ignoredLafIds.add("h1.regular.font"); + ignoredLafIds.add("h2.font"); + ignoredLafIds.add("h2.regular.font"); + ignoredLafIds.add("h3.font"); + ignoredLafIds.add("h3.regular.font"); + ignoredLafIds.add("h4.font"); + ignoredLafIds.add("large.font"); + ignoredLafIds.add("light.font"); + ignoredLafIds.add("medium.font"); + ignoredLafIds.add("mini.font"); + ignoredLafIds.add("monospaced.font"); + ignoredLafIds.add("small.font"); + + } +} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/GNimbusLookAndFeel.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/GNimbusLookAndFeel.java deleted file mode 100644 index 00867d7e32..0000000000 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/GNimbusLookAndFeel.java +++ /dev/null @@ -1,120 +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 generic.theme.laf; - -import java.awt.Color; -import java.awt.Font; -import java.util.List; - -import javax.swing.*; -import javax.swing.plaf.FontUIResource; -import javax.swing.plaf.nimbus.NimbusLookAndFeel; - -import generic.theme.*; -import generic.theme.laf.nimbus.SelectedTreePainter; - -/** - * Extends the {@link NimbusLookAndFeel} to intercept the {@link #getDefaults()}. Nimbus does - * not honor changes to the UIDefaults after it is installed as the active - * {@link LookAndFeel}, so we have to make the changes at the time the UIDefaults are installed. - * - * To get around this issue, we extend the NimbusLookAndFeel - * so that we can install our GColors and overridden properties as Nimbus is being installed, - * specifically during the call to the getDefaults() method. For all other Look And Feels, the - * GColors and overridden properties are changed in the UIDefaults after the Look And Feel is - * installed, so they don't need to extends the Look and Feel class. - * - * Also, note that Nimbus needs to be reinstalled every time we need to make a change to any of the - * UIDefaults values, since it does not respond to changes other than when first installed. - */ -public class GNimbusLookAndFeel extends NimbusLookAndFeel { - private ApplicationThemeManager themeManager; - - GNimbusLookAndFeel(ApplicationThemeManager themeManager) { - this.themeManager = themeManager; - } - - @Override - public UIDefaults getDefaults() { - UIDefaults defaults = super.getDefaults(); - - installCustomPainters(defaults); - - GThemeValueMap javaDefaults = extractJavaDefaults(defaults); - - // replace all colors with GColors - for (ColorValue colorValue : javaDefaults.getColors()) { - String id = colorValue.getId(); - defaults.put(id, themeManager.getGColorUiResource(id)); - } - - // put fonts back into defaults in case they have been changed by the current theme - for (FontValue fontValue : javaDefaults.getFonts()) { - String id = fontValue.getId(); - Font font = themeManager.getFont(id); - defaults.put(id, new FontUIResource(font)); - } - - // put icons back into defaults in case they have been changed by the current theme - for (IconValue iconValue : javaDefaults.getIcons()) { - String id = iconValue.getId(); - // because some icons are weird, put raw icons into defaults, only use GIcons for - // setting Icons explicitly on components - Icon icon = themeManager.getIcon(id); - defaults.put(id, icon); - } - - defaults.put("Label.textForeground", themeManager.getGColorUiResource("Label.foreground")); - themeManager.refreshGThemeValues(); - return defaults; - } - - private void installCustomPainters(UIDefaults defaults) { - defaults.put("Tree:TreeCell[Enabled+Selected].backgroundPainter", - new SelectedTreePainter()); - defaults.put("Tree:TreeCell[Focused+Selected].backgroundPainter", - new SelectedTreePainter()); - } - - protected GThemeValueMap extractJavaDefaults(UIDefaults defaults) { - GThemeValueMap javaDefaults = new GThemeValueMap(); - - List colorIds = - LookAndFeelManager.getLookAndFeelIdsForType(defaults, Color.class); - for (String id : colorIds) { - Color color = defaults.getColor(id); - ColorValue value = new ColorValue(id, color); - javaDefaults.addColor(value); - } - List fontIds = - LookAndFeelManager.getLookAndFeelIdsForType(defaults, Font.class); - for (String id : fontIds) { - Font font = defaults.getFont(id); - FontValue value = new FontValue(id, LookAndFeelManager.fromUiResource(font)); - javaDefaults.addFont(value); - } - List iconIds = - LookAndFeelManager.getLookAndFeelIdsForType(defaults, Icon.class); - for (String id : iconIds) { - Icon icon = defaults.getIcon(id); - javaDefaults.addIcon(new IconValue(id, icon)); - } - // need to set javaDefalts now to trigger building currentValues so the when - // we create GColors below, they can be resolved. - themeManager.setJavaDefaults(javaDefaults); - return javaDefaults; - } -} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/GtkLookAndFeelManager.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/GtkLookAndFeelManager.java index edf22b089a..9e85c5922e 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/GtkLookAndFeelManager.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/GtkLookAndFeelManager.java @@ -15,6 +15,8 @@ */ package generic.theme.laf; +import javax.swing.UIDefaults; + import generic.theme.ApplicationThemeManager; import generic.theme.LafType; @@ -26,4 +28,9 @@ public class GtkLookAndFeelManager extends LookAndFeelManager { public GtkLookAndFeelManager(ApplicationThemeManager themeManager) { super(LafType.GTK, themeManager); } + + @Override + protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { + return new GtkUiDefaultsMapper(defaults); + } } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/GtkUiDefaultsMapper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/GtkUiDefaultsMapper.java new file mode 100644 index 0000000000..e0e8c1f5c6 --- /dev/null +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/GtkUiDefaultsMapper.java @@ -0,0 +1,41 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme.laf; + +import static generic.theme.SystemThemeIds.*; + +import javax.swing.UIDefaults; + +public class GtkUiDefaultsMapper extends UiDefaultsMapper { + + protected GtkUiDefaultsMapper(UIDefaults defaults) { + super(defaults); + } + + @Override + protected void registerIgnoredLafIds() { + super.registerIgnoredLafIds(); + ignoredLafIds.add("textInactiveText"); + } + + @Override + protected void assignColorMatchersToComponentIds() { + super.assignColorMatchersToComponentIds(); + assignSystemColorFromLafId(FG_VIEW_ID, "windowText"); + assignSystemColorFromLafId(FG_DISABLED_ID, "Label.disabledForeground"); + } + +} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/LookAndFeelManager.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/LookAndFeelManager.java index 91fad08373..e588c3d880 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/LookAndFeelManager.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/LookAndFeelManager.java @@ -36,32 +36,14 @@ import ghidra.util.SystemUtilities; */ public abstract class LookAndFeelManager { - /** - * These are color ids (see {@link GColor} used to represent general concepts that - * application developers can use to get the color for that concept as defined by - * a specific {@link LookAndFeel}. This class will define some standard default - * mappings in the constructor, but it is expected that each specific LookAndFeelManager - * will override these mappings with values appropriate for that LookAndFeel. - */ - protected static final String SYSTEM_APP_BACKGROUND_COLOR_ID = "system.color.bg.application"; - protected static final String SYSTEM_WIDGET_BACKGROUND_COLOR_ID = "system.color.bg.widget"; - protected static final String SYSTEM_TOOLTIP_BACKGROUND_COLOR_ID = "system.color.bg.tooltip"; - protected static final String SYSTEM_BORDER_COLOR_ID = "system.color.border"; - private LafType laf; private Map fontRegistryMap = new HashMap<>(); - protected GThemeValueMap systemToLafMap = new GThemeValueMap(); protected ApplicationThemeManager themeManager; + protected Map normalizedIdToLafIdMap; protected LookAndFeelManager(LafType laf, ApplicationThemeManager themeManager) { this.laf = laf; this.themeManager = themeManager; - - // establish system color to LookAndFeel colors - systemToLafMap.addColor(new ColorValue(SYSTEM_APP_BACKGROUND_COLOR_ID, "control")); - systemToLafMap.addColor(new ColorValue(SYSTEM_WIDGET_BACKGROUND_COLOR_ID, "control")); - systemToLafMap.addColor(new ColorValue(SYSTEM_TOOLTIP_BACKGROUND_COLOR_ID, "control")); - systemToLafMap.addColor(new ColorValue(SYSTEM_BORDER_COLOR_ID, "controlShadow")); } /** @@ -85,10 +67,8 @@ public abstract class LookAndFeelManager { public void installLookAndFeel() throws ClassNotFoundException, InstantiationException, IllegalAccessException, UnsupportedLookAndFeelException { - cleanUiDefaults(); - themeManager.setSystemDefaults(systemToLafMap); doInstallLookAndFeel(); - installJavaDefaults(); + processJavaDefaults(); fixupLookAndFeelIssues(); installGlobalProperties(); installCustomLookAndFeelActions(); @@ -119,10 +99,14 @@ public abstract class LookAndFeelManager { UIDefaults defaults = UIManager.getDefaults(); for (FontValue fontValue : fonts) { String id = fontValue.getId(); + String lafId = normalizedIdToLafIdMap.get(id); + if (lafId == null) { + continue; + } Font correctFont = Gui.getFont(id); Font storedFont = defaults.getFont(id); if (correctFont != null && !correctFont.equals(storedFont)) { - defaults.put(id, correctFont); + defaults.put(lafId, toUiResource(correctFont)); } } } @@ -132,10 +116,11 @@ public abstract class LookAndFeelManager { UIDefaults defaults = UIManager.getDefaults(); for (IconValue iconValue : icons) { String id = iconValue.getId(); + String lafId = normalizedIdToLafIdMap.get(id); Icon correctIcon = Gui.getIcon(id); Icon storedIcon = defaults.getIcon(id); if (correctIcon != null && !correctIcon.equals(storedIcon)) { - defaults.put(id, correctIcon); + defaults.put(lafId, correctIcon); } } } @@ -154,14 +139,16 @@ public abstract class LookAndFeelManager { * @param newIcon the new icon to use for the given set of icon ids */ public void iconsChanged(Set changedIconIds, Icon newIcon) { + UIDefaults defaults = UIManager.getDefaults(); if (!(newIcon instanceof UIResource)) { newIcon = new IconUIResource(newIcon); } + for (String changedIconId : changedIconIds) { + String lafIconId = normalizedIdToLafIdMap.get(changedIconId); + defaults.put(lafIconId, newIcon); + } + if (!changedIconIds.isEmpty()) { - UIDefaults defaults = UIManager.getDefaults(); - for (String javaIconId : changedIconIds) { - defaults.put(javaIconId, newIcon); - } updateComponentUis(); } themeManager.refreshGThemeValues(); @@ -173,16 +160,22 @@ public abstract class LookAndFeelManager { * @param changedJavaFontIds the set of Java Font ids that are affected by this change */ public void fontsChanged(Set changedJavaFontIds) { - if (!changedJavaFontIds.isEmpty()) { - UIDefaults defaults = UIManager.getDefaults(); - for (String javaFontId : changedJavaFontIds) { - // even though all these derive from the new font, they might be different - // because of FontModifiers. - Font font = Gui.getFont(javaFontId); - defaults.put(javaFontId, new FontUIResource(font)); + UIDefaults defaults = UIManager.getDefaults(); + for (String changedFontId : changedJavaFontIds) { + // even though all these derive from the new font, they might be different + // because of FontModifiers. + Font font = Gui.getFont(changedFontId); + String lafFontId = normalizedIdToLafIdMap.get(changedFontId); + if (lafFontId != null) { + // lafFontId is null for group ids + defaults.put(lafFontId, new FontUIResource(font)); } + } + + if (!changedJavaFontIds.isEmpty()) { updateComponentUis(); } + updateAllRegisteredComponentFonts(); repaintAll(); } @@ -212,26 +205,9 @@ public abstract class LookAndFeelManager { register.addComponent(component); } - /** - * Returns a color that is not a {@link UIResource}. - * @param color the color to return an non UIResource color for - * @return a color that is not a {@link UIResource}. - */ - public static Color fromUiResource(Color color) { - if (color.getClass() == Color.class) { - return color; - } - return new Color(color.getRGB(), true); - } - - /** - * Returns a font that is not a {@link UIResource}. - * @param font the font to return an non UIResource font for - * @return a font that is not a {@link UIResource}. - */ - public static Font fromUiResource(Font font) { - if (font instanceof UIResource) { - return new FontNonUiResource(font); + private Font toUiResource(Font font) { + if (!(font instanceof UIResource)) { + return new FontUIResource(font); } return font; } @@ -261,80 +237,21 @@ public abstract class LookAndFeelManager { } /** - * Extracts java default colors, fonts, and icons and stores them in {@link Gui}. + * Extracts java default colors, fonts, and icons and stores them in the + * {@link ThemeManager} and updates the {@link UIDefaults} by installing GColors for all + * color values and installing any overridden fonts or icons. */ - private void installJavaDefaults() { - GThemeValueMap javaDefaults = extractJavaDefaults(); - ThemeGrouper grouper = getThemeGrouper(); - grouper.group(javaDefaults); + protected void processJavaDefaults() { + UIDefaults defaults = UIManager.getDefaults(); + UiDefaultsMapper uiDefaultsMapper = getUiDefaultsMapper(defaults); + + GThemeValueMap javaDefaults = uiDefaultsMapper.getJavaDefaults(); themeManager.setJavaDefaults(javaDefaults); - installPropertiesBackIntoUiDefaults(javaDefaults); + uiDefaultsMapper.installValuesIntoUIDefaults(themeManager.getApplicationOverrides()); + normalizedIdToLafIdMap = uiDefaultsMapper.getNormalizedIdToLafIdMap(); } - protected ThemeGrouper getThemeGrouper() { - return new ThemeGrouper(); - } - - protected void installPropertiesBackIntoUiDefaults(GThemeValueMap javaDefaults) { - UIDefaults defaults = UIManager.getDefaults(); - - GTheme theme = themeManager.getActiveTheme(); - - // we replace java default colors with GColor equivalents so that we - // can change colors without having to reinstall ui on each component - // This trick only works for colors. Fonts and icons don't universally - // allow being wrapped like colors do. - for (ColorValue colorValue : javaDefaults.getColors()) { - String id = colorValue.getId(); - defaults.put(id, themeManager.getGColorUiResource(id)); - } - - // put fonts back into defaults in case they have been changed by the current theme - for (FontValue fontValue : javaDefaults.getFonts()) { - String id = fontValue.getId(); - FontValue themeValue = theme.getFont(id); - if (themeValue != null) { - Font font = Gui.getFont(id); - defaults.put(id, new FontUIResource(font)); - } - } - - // put icons back into defaults in case they have been changed by the current theme - for (IconValue iconValue : javaDefaults.getIcons()) { - String id = iconValue.getId(); - IconValue themeValue = theme.getIcon(id); - if (themeValue != null) { - // because some icons are weird, put raw icons into defaults, only use GIcons for - // setting Icons explicitly on components - Icon icon = Gui.getIcon(id); - defaults.put(id, icon); - } - } - } - - protected GThemeValueMap extractJavaDefaults() { - UIDefaults defaults = UIManager.getDefaults(); - GThemeValueMap values = new GThemeValueMap(); - // for now, just doing color properties. - List ids = getLookAndFeelIdsForType(defaults, Color.class); - for (String id : ids) { - // convert UIResource color to regular colors so if used, they don't get wiped - // out when we update the UIs - values.addColor(new ColorValue(id, fromUiResource(UIManager.getColor(id)))); - } - ids = getLookAndFeelIdsForType(defaults, Font.class); - for (String id : ids) { - // convert UIResource fonts to regular fonts so if used, they don't get wiped - // out when we update UIs - values.addFont(new FontValue(id, fromUiResource(UIManager.getFont(id)))); - } - ids = getLookAndFeelIdsForType(defaults, Icon.class); - for (String id : ids) { - Icon icon = UIManager.getIcon(id); - values.addIcon(new IconValue(id, icon)); - } - return values; - } + protected abstract UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults); protected String findLookAndFeelClassName(String lookAndFeelName) { LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels(); @@ -463,26 +380,6 @@ public abstract class LookAndFeelManager { installPopupMenuSettingsOverride(); } - private void cleanUiDefaults() { - GThemeValueMap javaDefaults = themeManager.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); - } - for (IconValue iconValue : javaDefaults.getIcons()) { - String id = iconValue.getId(); - defaults.put(id, null); - } - } - /** * Searches the given UIDefaults for ids whose value matches the given class * @param defaults the UIDefaults to search diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MacLookAndFeelManager.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MacLookAndFeelManager.java index 7c6caf885f..872cc04ea4 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MacLookAndFeelManager.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MacLookAndFeelManager.java @@ -15,6 +15,8 @@ */ package generic.theme.laf; +import javax.swing.UIDefaults; + import generic.theme.ApplicationThemeManager; import generic.theme.LafType; @@ -25,7 +27,7 @@ public class MacLookAndFeelManager extends LookAndFeelManager { } @Override - protected ThemeGrouper getThemeGrouper() { - return new MacThemeGrouper(); + protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { + return new MacUiDefaultsMapper(defaults); } } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MacThemeGrouper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MacThemeGrouper.java deleted file mode 100644 index 7411e7abe7..0000000000 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MacThemeGrouper.java +++ /dev/null @@ -1,38 +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 generic.theme.laf; - -import generic.theme.GThemeValueMap; - -/** - * Adds specialized groupings unique to the Mac LookAndFeel - */ -public class MacThemeGrouper extends ThemeGrouper { - - @Override - public void group(GThemeValueMap values) { - // @formatter:off - defineCustomColorGroup("color.mac.disabled.fg", "Menu.disabledForeground", values); - defineCustomColorGroup("color.mac.button.select", "Button.select", values); - defineCustomColorGroup("color.mac.menu.select", "Menu.selectionBackground",values); - defineCustomColorGroup("color.mac.seletion.inactive.bg", "List.selectionInactiveBackground",values);//d4d4d4 - - defineCustomFontGroup("font.mac.small.font", "IconButton.font", values); - // @formatter:on - super.group(values); - } - -} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MacUiDefaultsMapper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MacUiDefaultsMapper.java new file mode 100644 index 0000000000..4e864c1b83 --- /dev/null +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MacUiDefaultsMapper.java @@ -0,0 +1,25 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme.laf; + +import javax.swing.UIDefaults; + +public class MacUiDefaultsMapper extends UiDefaultsMapper { + + protected MacUiDefaultsMapper(UIDefaults defaults) { + super(defaults); + } +} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MetalLookAndFeelManager.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MetalLookAndFeelManager.java index 0eb565025e..823fce63d8 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MetalLookAndFeelManager.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MetalLookAndFeelManager.java @@ -15,6 +15,8 @@ */ package generic.theme.laf; +import javax.swing.UIDefaults; + import generic.theme.ApplicationThemeManager; import generic.theme.LafType; @@ -23,4 +25,9 @@ public class MetalLookAndFeelManager extends LookAndFeelManager { public MetalLookAndFeelManager(ApplicationThemeManager themeManager) { super(LafType.METAL, themeManager); } + + @Override + protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { + return new MetalUiDefaultsMapper(defaults); + } } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MetalUiDefaultsMapper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MetalUiDefaultsMapper.java new file mode 100644 index 0000000000..e6a340d187 --- /dev/null +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MetalUiDefaultsMapper.java @@ -0,0 +1,41 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme.laf; + +import static generic.theme.SystemThemeIds.*; + +import javax.swing.UIDefaults; + +public class MetalUiDefaultsMapper extends UiDefaultsMapper { + + protected MetalUiDefaultsMapper(UIDefaults defaults) { + super(defaults); + } + + @Override + protected void registerIgnoredLafIds() { + super.registerIgnoredLafIds(); + ignoredLafIds.add("textInactiveText"); + } + + @Override + protected void assignSystemColorValues() { + super.assignSystemColorValues(); + assignSystemColorFromLafId(FG_DISABLED_ID, "Label.disabledForeground"); + assignSystemColorFromLafId(FG_VIEW_ID, "windowText"); + } + +} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MotifLookAndFeelManager.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MotifLookAndFeelManager.java index ba8421359e..eb3b846594 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MotifLookAndFeelManager.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MotifLookAndFeelManager.java @@ -15,7 +15,10 @@ */ package generic.theme.laf; -import generic.theme.*; +import javax.swing.UIDefaults; + +import generic.theme.ApplicationThemeManager; +import generic.theme.LafType; /** * Motif {@link LookAndFeelManager}. Specialized so that it can return the Motif installer @@ -24,11 +27,11 @@ public class MotifLookAndFeelManager extends LookAndFeelManager { public MotifLookAndFeelManager(ApplicationThemeManager themeManager) { super(LafType.MOTIF, themeManager); - // establish system color to LookAndFeel colors - systemToLafMap.addColor(new ColorValue(SYSTEM_APP_BACKGROUND_COLOR_ID, "control")); - systemToLafMap.addColor(new ColorValue(SYSTEM_WIDGET_BACKGROUND_COLOR_ID, "window")); - systemToLafMap.addColor(new ColorValue(SYSTEM_TOOLTIP_BACKGROUND_COLOR_ID, "info")); - systemToLafMap.addColor(new ColorValue(SYSTEM_BORDER_COLOR_ID, "activeCaptionBorder")); + } + + @Override + protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { + return new MotifUiDefaultsMapper(defaults); } @Override @@ -46,10 +49,4 @@ public class MotifLookAndFeelManager extends LookAndFeelManager { setKeyBinding("PASTE", "ctrl V", UIPrefixValues); setKeyBinding("CUT", "ctrl X", UIPrefixValues); } - - @Override - protected ThemeGrouper getThemeGrouper() { - return new MotifThemeGrouper(); - } - } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MotifThemeGrouper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MotifUiDefaultsMapper.java similarity index 66% rename from Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MotifThemeGrouper.java rename to Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MotifUiDefaultsMapper.java index 0b584e932d..068c4ecbaa 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MotifThemeGrouper.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/MotifUiDefaultsMapper.java @@ -15,21 +15,18 @@ */ package generic.theme.laf; -import generic.theme.GThemeValueMap; +import javax.swing.UIDefaults; -/** - * Adds specialized groupings unique to the Motif LookAndFeel - */ -public class MotifThemeGrouper extends ThemeGrouper { - public MotifThemeGrouper() { +public class MotifUiDefaultsMapper extends UiDefaultsMapper { + protected MotifUiDefaultsMapper(UIDefaults defaults) { + super(defaults); } @Override - public void group(GThemeValueMap values) { - defineCustomFontGroup("font.monospaced", "Spinner.font", values); - - super.group(values); + protected void registerIgnoredLafIds() { + super.registerIgnoredLafIds(); + ignoredLafIds.add("controlLightShadow"); } } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/NimbusLookAndFeelManager.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/NimbusLookAndFeelManager.java index a36006ce57..8f018a8193 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/NimbusLookAndFeelManager.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/NimbusLookAndFeelManager.java @@ -32,9 +32,6 @@ public class NimbusLookAndFeelManager extends LookAndFeelManager { public NimbusLookAndFeelManager(ApplicationThemeManager themeManager) { super(LafType.NIMBUS, themeManager); - - // establish system color specific to Nimbus - systemToLafMap.addColor(new ColorValue(SYSTEM_BORDER_COLOR_ID, "nimbusBorder")); } @Override @@ -63,11 +60,30 @@ public class NimbusLookAndFeelManager extends LookAndFeelManager { private void reinstallNimubus() { try { - UIManager.setLookAndFeel(new GNimbusLookAndFeel(themeManager) { + /** + * In order to get Nimbus to honor changes to fonts and icons in the UiDefaults, + * we have to reinstall nimbus. Reinstalling nimbus is a bit different that the first + * install. First, we don't want to re-install the java defaults, the current ones are + * fine and we don't want loose any current theme values changes. Second, when we + * get font and theme value overrides, we want to use all the current values as they + * may include additional overrides than just the original values from theme.property + * files. + */ + UIManager.setLookAndFeel(new CustomNimbusLookAndFeel(themeManager) { @Override - protected GThemeValueMap extractJavaDefaults(UIDefaults defaults) { - return themeManager.getJavaDefaults(); + protected void installJavaDefaultsIntoThemeManager( + UiDefaultsMapper uiDefaultsMapper) { + // as explained above, don't change the java defaults in the theme manager + // on a reinstall } + + @Override + protected GThemeValueMap getApplicationOverrides() { + // on a reinstall, we may also have overrides in the current values and not + // just the theme.property files + return themeManager.getCurrentValues(); + } + }); } catch (UnsupportedLookAndFeelException e) { @@ -78,18 +94,15 @@ public class NimbusLookAndFeelManager extends LookAndFeelManager { @Override protected void doInstallLookAndFeel() throws UnsupportedLookAndFeelException { - UIManager.setLookAndFeel(new GNimbusLookAndFeel(themeManager)); + CustomNimbusLookAndFeel nimbusLookAndFeel = new CustomNimbusLookAndFeel(themeManager); + UIManager.setLookAndFeel(nimbusLookAndFeel); + normalizedIdToLafIdMap = nimbusLookAndFeel.getNormalizedIdToLafIdMap(); } @Override - protected GThemeValueMap extractJavaDefaults() { + protected void processJavaDefaults() { // The GNimbusLookAndFeel already extracted the java defaults and installed them in the Gui - return themeManager.getJavaDefaults(); - } - @Override - protected ThemeGrouper getThemeGrouper() { - return new NimbusThemeGrouper(); } @Override @@ -105,8 +118,7 @@ public class NimbusLookAndFeelManager extends LookAndFeelManager { } @Override - protected void installPropertiesBackIntoUiDefaults(GThemeValueMap javaDefaults) { - // do nothing, this was handled when we overrode the getDefaults() method in the - // GNimubusLookAndFeel + protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { + return new NimbusUiDefaultsMapper(defaults); } } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/NimbusThemeGrouper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/NimbusThemeGrouper.java deleted file mode 100644 index 8c977d3ee1..0000000000 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/NimbusThemeGrouper.java +++ /dev/null @@ -1,43 +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 generic.theme.laf; - -import generic.theme.GThemeValueMap; - -/** - * Adds specialized groupings unique to the Nimbus LookAndFeel - */ -public class NimbusThemeGrouper extends ThemeGrouper { - public NimbusThemeGrouper() { - // Nimbus defines a new type of button - buttonGroup.addComponents("ArrowButton"); - - // Nimbus defines some other color sources - colorSourceProperties.add("nimbusFocus"); - colorSourceProperties.add("nimbusOrange"); - colorSourceProperties.add("nimbusBorder"); - - } - - @Override - public void group(GThemeValueMap values) { - defineCustomColorGroup("color.nimbus.text.alt", "Menu.foreground", values); - defineCustomFontGroup("font.titledborder", "TitledBorder.font", values); - - super.group(values); - } - -} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/NimbusUiDefaultsMapper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/NimbusUiDefaultsMapper.java new file mode 100644 index 0000000000..09f1178544 --- /dev/null +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/NimbusUiDefaultsMapper.java @@ -0,0 +1,96 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme.laf; + +import static generic.theme.SystemThemeIds.*; + +import javax.swing.UIDefaults; + +import generic.theme.*; + +public class NimbusUiDefaultsMapper extends UiDefaultsMapper { + + protected NimbusUiDefaultsMapper(UIDefaults defaults) { + super(defaults); + } + + @Override + protected void registerIgnoredLafIds() { + super.registerIgnoredLafIds(); + ignoredLafIds.add("background"); + + ignoredLafIds.add("controlLHighlight"); + + ignoredLafIds.add("nimbusAlertYellow"); + ignoredLafIds.add("nimbusBase"); + ignoredLafIds.add("nimbusBlueGrey"); + ignoredLafIds.add("nimbusDisabledText"); + ignoredLafIds.add("nimbusFocus"); + ignoredLafIds.add("nimbusGreen"); + ignoredLafIds.add("nimbusInfoBlue"); + ignoredLafIds.add("nimbusOrange"); + ignoredLafIds.add("nimbusRed"); + ignoredLafIds.add("nimbusSelectedText"); + ignoredLafIds.add("nimbusSelection"); + ignoredLafIds.add("nimbusSelectionBackground"); + + ignoredLafIds.add("defaultFont"); + + } + + @Override + protected void assignSystemColorValues() { + + // different from base class + assignSystemColorFromLafId(BG_CONTROL_ID, "Button.background"); + assignSystemColorFromLafId(FG_CONTROL_ID, "Button.foreground"); + assignSystemColorFromLafId(BG_BORDER_ID, "nimbusBorder"); + assignSystemColorFromLafId(BG_VIEW_ID, "nimbusLightBackground"); + assignSystemColorFromLafId(FG_VIEW_ID, "controlText"); + + // the following are the same as the base class (we can't just call super because + // it will report errors for missing lafIds such as "window" + + assignSystemColorFromLafId(BG_VIEW_SELECTED_ID, "textHighlight"); + assignSystemColorFromLafId(FG_VIEW_SELECTED_ID, "textHighlightText"); + assignSystemColorFromLafId(FG_DISABLED_ID, "textInactiveText"); + assignSystemColorFromLafId(BG_TOOLTIP_ID, "info"); + assignSystemColorFromLafId(FG_TOOLTIP_ID, "infoText"); + } + + @Override + protected GThemeValueMap extractColorFontAndIconValuesFromDefaults() { + // Nimbus always uses "info" to paint its tooltip and it appears they forgot to update + // that value to the value they assigned to the "ToolTip.background. So we fix it here + // before extracting the values from the UIDefaults + defaults.put("info", defaults.getColor("ToolTip.background")); + return super.extractColorFontAndIconValuesFromDefaults(); + } + + @Override + protected void installGColorsIntoUIDefaults() { + super.installGColorsIntoUIDefaults(); + + // The Nimbus selected text field color is not honored if the value is a ColorUIResource. + // We install GColorUIResources by default. Thus, our setting for this particular + // attribute was being ignored. We set it here to be a GColor, which causes Nimbus + // to honor the value. We may need to add more entries here as they are discovered. + + defaults.put("TextField.selectionForeground", + new GColor(SystemThemeIds.FG_VIEW_SELECTED_ID)); + } + +} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/ThemeGrouper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/ThemeGrouper.java deleted file mode 100644 index 420e5ee782..0000000000 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/ThemeGrouper.java +++ /dev/null @@ -1,452 +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 generic.theme.laf; - -import java.awt.Color; -import java.awt.Font; -import java.util.*; - -import javax.swing.LookAndFeel; -import javax.swing.plaf.basic.BasicLookAndFeel; - -import generic.theme.*; - -/** - * Organizes UIDefaults color and font properties into groups so that every property doesn't - * have its own direct value. The idea is that users can affect many properties that have the - * same value by just changing the value for the group. For colors, the {@link LookAndFeel}s - * organize the properties internally and this class attempts to restore that organization - * as much as possible by using the values defined in the {@link BasicLookAndFeel} such as - * "control", "window", "controlShadlow", etc. The fonts don't appear to have any such internal - * organization, so we created our own groups and used a lookAndFeel property to initialize each - * group source value. Then whenever the font matched a group source value, the font is replace - * with an indirect reference to the group source font value. - *

- * This class is sometimes sub-classed for a particular {@link LookAndFeel}. The subclass can - * create new groups and mappings that are unique to that LookAndFeel. - * - * Often, many of the various group source ids have the same color value. To try to group - * properties as defined in BasicLookAndFeel, the preferred source ids are - * defined for each group. These will be tried first, but if a match isn't found among the - * preferred sources, then all the sources will be searched for a match - */ -public class ThemeGrouper { - private static String DEFAULT_FONT_GROUP_ID = "font.default"; - private static String BUTTON_FONT_GROUP_ID = "font.button"; - private static String TEXT_FONT_GROUP_ID = "font.text"; - private static String WIDGET_FONT_GROUP_ID = "font.widget"; - private static String COMPONENT_FONT_GROUP_ID = "font.component"; - private static String MENU_FONT_GROUP_ID = "font.menu"; - private static String MENU_ACCELERATOR_FONT_GROUP_ID = "font.menu.accelerator"; - - static List DEFAULT_FONT_SOURCE_PROPERTIES = List.of( - DEFAULT_FONT_GROUP_ID, - COMPONENT_FONT_GROUP_ID, - WIDGET_FONT_GROUP_ID, - TEXT_FONT_GROUP_ID, - BUTTON_FONT_GROUP_ID, - MENU_FONT_GROUP_ID, - MENU_ACCELERATOR_FONT_GROUP_ID); - - // The list of color properties (defined in BasicLookAndFeel) that are used to populate - // other component specific colors. - // The order is important. If any have the same color value, the one higher in the list is used. - // Individual groups (buttons, menus, etc.) may define a different order that is more specific - // to that group - public static List DEFAULT_COLOR_SOURCE_PROPERTIES = List.of( - "control", - "window", - "activeCaption", - "activeCaptionBorder", - "activeCaptionText", - "controlDkShadow", - "controlHighlight", - "controlLtHighlight", - "controlShadow", - "controlText", - "desktp", - "inactiveCaption", - "inactiveCaptionBorder", - "inactiveCaptionText", - "info", - "infoText", - "menu", - "menuText", - "scrollbar", - "scrollBarTrack", - "text", - "textHighlight", - "textHighlightText", - "textInactiveText", - "textText", - "windowBorder", - "windowText"); - - private static final String[] BUTTON_GROUP = { - "Button", - "ToggleButton", - "RadioButton", - "CheckBox" - }; - private static final String[] MENU_GROUP = { - "Menu", - "MenuBar", - "MenuItem", - "PopupMenu", - "RadioButtonMenuItem", - "CheckBoxMenuItem" - }; - private static final String[] TEXT_GROUP = { - "TextField", - "FormattedTextField", - "PasswordField", - "TextArea", - "TextPane", - "EditorPane" - }; - private static final String[] WIDGET_GROUP = { - "FileChooser", - "ColorChooser", - "ComboBox", - "List", - "Table", - "Tree" - }; - private static final String[] COMPONENT_GROUP = { - "Desktop", - "Panel", - "InternalFrame", - "Label", - "OptionPane", - "ProgressBar", - "Separator", - "ScrollBar", - "ScrollPane", - "Viewport", - "Slider", - "Spinner", - "SplitPane", - "TabbedPane", - "TableHeader", - "TitledBorder", - "ToolBar", - "ToolTip" - }; - - private static final String[] BUTTON_PREFERRED_SOURCES = { - "control", - "controlText", - "controlShadow", - "controlDkShadow", - "controlHighlight", - "controlLtHighlight" - }; - private static final String[] MENU_PREFERRED_SOURCES = { - "menu", - "menuText", - "textHighlightText", - "textHighlight", - "controlShadow", - "controlDkShadow", - "controlHighlight", - "controlLtHighlight" - }; - private static final String[] TEXT_PREFERRED_SOURCES = { - "window", - "text", - "textText", - "textInactiveText", - "textHighlight", - "textHighlightText", - "controlShadow", - "controlDkShadow", - "controlHighlight", - "controlLtHighlight" - }; - private static final String[] WIDGET_PREFERRED_SOURCES = { - "window", - "textText", - "textHighlight", - "textHighlightText", - "control", - "controlShadow", - "controlDkShadow", - "controlHighlight", - "controlLtHighlight" - }; - private static final String[] COMPONENT_PREFERRED_SOURCES = { - "control", - "controlText", - "controlShadow", - "controlDkShadow", - "controlHighlight", - "controlLtHighlight", - "textText", - "textHighlight" - }; - - protected List colorSourceProperties; - protected List fontSourceProperties; - protected Set groups; - protected PropertyGroup buttonGroup = new PropertyGroup(BUTTON_GROUP, BUTTON_PREFERRED_SOURCES); - protected PropertyGroup menuGroup = new PropertyGroup(MENU_GROUP, MENU_PREFERRED_SOURCES); - protected PropertyGroup widgetGroup = new PropertyGroup(WIDGET_GROUP, WIDGET_PREFERRED_SOURCES); - protected PropertyGroup textGroup = new PropertyGroup(TEXT_GROUP, TEXT_PREFERRED_SOURCES); - protected PropertyGroup componentGroup = - new PropertyGroup(COMPONENT_GROUP, COMPONENT_PREFERRED_SOURCES); - - public ThemeGrouper() { - colorSourceProperties = new ArrayList<>(DEFAULT_COLOR_SOURCE_PROPERTIES); - fontSourceProperties = new ArrayList<>(DEFAULT_FONT_SOURCE_PROPERTIES); - groups = getPropertyGroups(); - } - - /** - * Replaces direct property values in the given GThemeValueMap with indirect references - * using the values from match source ids. - * @param values the values to search and replace source matches - */ - public void group(GThemeValueMap values) { - initialize(values); - Map groupMap = buildGroupMap(values); - groupColors(values, groupMap); - groupFonts(values, groupMap); - } - - /** - * Defines a new color id that will be used as the reference value for any specific color ids that - * have the same color value. This will allow all those specific colors to be changed at once. - * @param customGroupColorName name of a higher level group color id that will be used as the - * value for more specific color ids defined by the lookAndFeel. - * @param lookAndFeelSourceId the lookAndFeel color id whose value will be used as the value - * for the new custom group color id - * @param values the map where we store the default theme value mappings - */ - protected void defineCustomColorGroup(String customGroupColorName, String lookAndFeelSourceId, - GThemeValueMap values) { - - colorSourceProperties.add(customGroupColorName); - ColorValue colorValue = values.getColor(lookAndFeelSourceId); - if (colorValue != null) { - Color color = colorValue.get(values); - values.addColor(new ColorValue(customGroupColorName, color)); - } - } - - /** - * Defines a new font id that will be used as the reference value for any specific font ids that - * have the same font value. This will allow all those specific fonts to be changed at once. - * @param customGroupFontName name of a higher level group font id that will be used as the - * value of more specific font ids defined by the lookAndFeel. - * @param lookAndFeelSourceId the lookAndFeel font id whose value will be used as the value - * for the new custom group font id - * @param values the map where we store the default theme value mappings - */ - protected void defineCustomFontGroup(String customGroupFontName, String lookAndFeelSourceId, - GThemeValueMap values) { - fontSourceProperties.add(customGroupFontName); - FontValue fontValue = values.getFont(lookAndFeelSourceId); - if (fontValue != null) { - Font font = fontValue.get(values); - values.addFont(new FontValue(customGroupFontName, font)); - } - } - - private void groupColors(GThemeValueMap values, Map groupMap) { - Set skip = new HashSet<>(colorSourceProperties); // we don't want to map sources - Map defaultColorMapping = buildColorToSourceMap(values); - // try to map each color property to a source property (e.g., Button.background -> control) - for (ColorValue colorValue : values.getColors()) { - String id = colorValue.getId(); - if (colorValue.isIndirect() || skip.contains(id)) { - continue; - } - PropertyGroup group = groupMap.get(getComponentName(id)); - int rgb = colorValue.getRawValue().getRGB(); - String sourceProperty = group == null ? null : group.getSourceProperty(rgb); - if (sourceProperty == null) { - sourceProperty = defaultColorMapping.get(rgb); - } - - if (sourceProperty != null) { - values.addColor(new ColorValue(id, sourceProperty)); - } - } - } - - private void groupFonts(GThemeValueMap values, Map groupMap) { - Set skip = new HashSet<>(fontSourceProperties); // we don't want to map sources - Map defaultFontMapping = buildFontToSourceMap(values); - - // try to map each color property to a source property (e.g., Button.background -> control) - for (FontValue fontValue : values.getFonts()) { - String id = fontValue.getId(); - if (fontValue.isIndirect() || skip.contains(id)) { - continue; - } - Font font = fontValue.getRawValue(); - PropertyGroup group = groupMap.get(getComponentName(id)); - String sourceProperty = group == null ? null : group.getSourceProperty(font); - if (sourceProperty == null) { - sourceProperty = defaultFontMapping.get(font); - } - if (sourceProperty != null) { - values.addFont(new FontValue(id, sourceProperty)); - } - } - } - - private void initialize(GThemeValueMap values) { - // initialized default font to the Panel's font - FontValue defaultFontValue = values.getFont("Panel.font"); - if (defaultFontValue != null) { - values.addFont(new FontValue(DEFAULT_FONT_GROUP_ID, defaultFontValue.get(values))); - } - - // initialize the default group fonts to a font from an exemplar property in that group - initializeFontGroup(buttonGroup, BUTTON_FONT_GROUP_ID, "Button.font", values); - initializeFontGroup(textGroup, TEXT_FONT_GROUP_ID, "TextField.font", values); - initializeFontGroup(widgetGroup, WIDGET_FONT_GROUP_ID, "Table.font", values); - initializeFontGroup(componentGroup, COMPONENT_FONT_GROUP_ID, "Panel.font", values); - initializeFontGroup(menuGroup, MENU_FONT_GROUP_ID, "Menu.font", values); - initializeFontGroup(menuGroup, MENU_ACCELERATOR_FONT_GROUP_ID, "Menu.acceleratorFont", - values); - } - - private void initializeFontGroup(PropertyGroup group, String fontGroupId, String exemplarId, - GThemeValueMap values) { - FontValue fontValue = values.getFont(exemplarId); - if (fontValue != null) { - Font font = fontValue.getRawValue(); - values.addFont(new FontValue(fontGroupId, font)); - group.addFontMapping(font, fontGroupId); - } - } - - private Set getPropertyGroups() { - Set set = new HashSet<>(); - set.add(buttonGroup); - set.add(menuGroup); - set.add(textGroup); - set.add(widgetGroup); - set.add(componentGroup); - return set; - } - - private Map buildGroupMap(GThemeValueMap values) { - Map map = new HashMap<>(); - for (PropertyGroup group : groups) { - group.initialize(values); - group.populateGroupMap(map); - } - return map; - } - - private String getComponentName(String id) { - int dotIndex = id.indexOf("."); - if (dotIndex < 0) { - return id; - } - return id.substring(0, dotIndex); - } - - private Map buildColorToSourceMap(GThemeValueMap values) { - Map colorMapping = new HashMap<>(); - ArrayList reversed = new ArrayList<>(colorSourceProperties); - Collections.reverse(reversed); - // go through in reverse order so that values at the top of the list have precedence - // if multiple propertyBases have the save value. - for (String propertyBase : reversed) { - ColorValue colorValue = values.getColor(propertyBase); - if (colorValue != null) { - Color color = colorValue.get(values); - colorMapping.put(color.getRGB(), propertyBase); - } - } - return colorMapping; - } - - private Map buildFontToSourceMap(GThemeValueMap values) { - Map fontMapping = new HashMap<>(); - ArrayList reversed = new ArrayList<>(fontSourceProperties); - Collections.reverse(reversed); - // go through in reverse order so that values at the top of the list have precedence - // if multiple propertyBases have the save value. - for (String propertyBase : reversed) { - FontValue fontValue = values.getFont(propertyBase); - if (fontValue != null) { - Font font = fontValue.get(values); - fontMapping.put(font, propertyBase); - } - } - return fontMapping; - } - - static class PropertyGroup { - private Set groupComponents = new HashSet<>(); - private List preferredPropertyColorSources = new ArrayList<>(); - private Map colorMapping; - private Map fontMapping = new HashMap<>(); - - PropertyGroup(String[] components, String[] perferredSources) { - addComponents(components); - addPreferredColorSources(perferredSources); - } - - String getSourceProperty(int rgb) { - return colorMapping.get(rgb); - } - - String getSourceProperty(Font font) { - return fontMapping.get(font); - } - - void populateGroupMap(Map groupMap) { - for (String component : groupComponents) { - groupMap.put(component, this); - } - } - - void addPreferredColorSources(String... preferedColorSources) { - this.preferredPropertyColorSources.addAll(Arrays.asList(preferedColorSources)); - } - - void addComponents(String... properties) { - groupComponents.addAll(Arrays.asList(properties)); - } - - void addFontMapping(Font font, String sourceId) { - fontMapping.put(font, sourceId); - } - - private Map initialize(GThemeValueMap values) { - colorMapping = new HashMap<>(); - ArrayList reversed = new ArrayList<>(preferredPropertyColorSources); - Collections.reverse(reversed); - // go through in reverse order so that values at the top of the list have precedence - // if multiple propertyBases have the save value. - for (String propertyBase : reversed) { - ColorValue colorValue = values.getColor(propertyBase); - if (colorValue != null) { - Color color = colorValue.get(values); - colorMapping.put(color.getRGB(), propertyBase); - } - } - return colorMapping; - } - } - -} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/UiDefaultsMapper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/UiDefaultsMapper.java new file mode 100644 index 0000000000..d815a92679 --- /dev/null +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/UiDefaultsMapper.java @@ -0,0 +1,716 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme.laf; + +import static generic.theme.SystemThemeIds.*; + +import java.awt.Color; +import java.awt.Font; +import java.util.*; +import java.util.Map.Entry; + +import javax.swing.*; +import javax.swing.plaf.FontUIResource; +import javax.swing.plaf.UIResource; +import javax.swing.plaf.basic.BasicLookAndFeel; + +import org.apache.commons.collections4.IteratorUtils; + +import generic.theme.*; +import ghidra.util.Msg; + +/** + * The purpose of this class is to introduce multiple levels of indirection into the Java + * {@code LookAndFeel} (LaF), which allows the user to change these values. Further, when + * introducing this indirection we combine the Java settings into user-friendly system ids to make + * changing these values easier. + *

+ * This class defines these user-friendly groups. The default system assignments are based on the + * {@link BasicLookAndFeel} values. + *

+ * Subclasses can override the mapping of these standard system values for particular LaFs that have + * different keys or color assignments. + *

+ * Some basic concepts: + *

    + *
  • UI Defaults - key-value pairs defined by the Java LaF; there are 2 key types, widget + * keys and Java group/reusable keys (e.g., Button.background; control) + *
  • UI Indirection - UI Defaults values are changed to point to custom terms we created to + * allow for indirection (e.g., Button.background -> laf.color.Button.background) + *
  • Normalized Keys - keys we created to facilitate the UI Indirection, based upon the Java + * keys (e.g., laf.color.Button.background) + *
  • System Color/Font Keys - user facing terms for common color or font concepts into an + * easy-to-change setting (e.g., system.color.fg.text) + *
  • Palette Keys - dynamically generated color palette keys based on the LaF for any colors + * and fonts that were not mapped into an system color or font (e.g., + * laf.palette.color.01) + *
+ * + *

+ * The mapper performs the following operations: + *

    + *
  1. Extracts all color, font, and icon values from the UI Defaults.
  2. + *
  3. Use the current LaF values to populate the pre-defined system colors and fonts.
  4. + *
  5. Any UI Defaults values not assigned in the previous step will be assigned to a dynamic shared + * palette color or font. + *
  6. Update Java UI Defaults to use our indirection and system values.
  7. + *
+ * + */ +public class UiDefaultsMapper { + public static final String LAF_COLOR_ID_PREFIX = "laf.color."; + public static final String LAF_FONT_ID_PREFIX = "laf.font."; + public static final String LAF_ICON_ID_PREFIX = "laf.icon."; + private static final String LAF_COLOR_PALETTE_PREFIX = "laf.palette.color."; + private static final String LAF_FONT_PALETTE_PREFIX = "laf.palette.font."; + + private static final String[] MENU_COMPONENTS = + { "Menu", "MenuBar", "MenuItem", "PopupMenu", "RadioButtonMenuItem", "CheckBoxMenuItem" }; + private static final String[] VIEW_COMPONENTS = + { "FileChooser", "ColorChooser", "ComboBox", "List", "Table", "Tree", "TextField", + "FormattedTextField", "PasswordField", "TextArea", "TextPane", "EditorPane" }; + private static final String[] TOOLTIP_COMPONENTS = { "ToolTip" }; + + protected UIDefaults defaults; + private GThemeValueMap extractedValues; + private GThemeValueMap normalizedValues = new GThemeValueMap(); + + private Map lafIdToNormalizedIdMap = new HashMap<>(); + protected Set ignoredLafIds = new HashSet<>(); + + private Map componentToColorMatcherMap = new HashMap<>(); + private Map componentToFontMatcherMap = new HashMap<>(); + + // @formatter:off + protected ColorMatcher viewColorMatcher = new ColorMatcher(BG_VIEW_ID, + FG_VIEW_ID, + BG_VIEW_SELECTED_ID, + FG_VIEW_SELECTED_ID); + protected ColorMatcher tooltipColorMatcher = new ColorMatcher(BG_TOOLTIP_ID, + FG_TOOLTIP_ID); + protected ColorMatcher defaultColorMatcher = new ColorMatcher(BG_CONTROL_ID, + FG_CONTROL_ID, + BG_VIEW_ID, + FG_VIEW_ID, + FG_DISABLED_ID, + BG_VIEW_SELECTED_ID, + FG_VIEW_SELECTED_ID, + BG_TOOLTIP_ID, + BG_BORDER_ID); + + protected FontMatcher menuFontMatcher = new FontMatcher(FONT_MENU_ID); + protected FontMatcher viewFontMatcher = new FontMatcher(FONT_VIEW_ID); + protected FontMatcher defaultFontMatcher = new FontMatcher(FONT_CONTROL_ID, + FONT_VIEW_ID, + FONT_MENU_ID); + + //@formatter:on + + private Map lafColorPaletteMap = new HashMap<>(); + private Map lafFontPaletteMap = new HashMap<>(); + private int nextColorPaletteId; + private int nextFontPaletteId; + + protected UiDefaultsMapper(UIDefaults defaults) { + this.defaults = defaults; + this.extractedValues = extractColorFontAndIconValuesFromDefaults(); + + assignSystemColorValues(); + assignSystemFontValues(); + + registerIgnoredLafIds(); + + assignColorMatchersToComponentIds(); + assignFontMatchersToComponentIds(); + + assignNormalizedColorValues(); + assignNormalizedFontValues(); + assignNormalizedIconValues(); + } + + /** + * Returns the normalized id to value map that will be installed into the + * ApplicationThemeManager to be the user changeable values for affecting the Java + * LookAndFeel colors, fonts, and icons + * @return a map of changeable values that affect java LookAndFeel values + */ + public GThemeValueMap getJavaDefaults() { + return normalizedValues; + } + + /** + * Updates the UIDefaults file with indirect colors (GColors) and any overridden font or icon + * values as defined in theme.properites files + * @param overrides a Map that contains possible LookAndFeel value overridess + */ + public void installValuesIntoUIDefaults(GThemeValueMap overrides) { + // + // In the UI Defaults, colors use indirect values and fonts and icons use direct values. + // Here we install our GColors for the indirect colors. Then we set any font and icon + // values that are different than the defaults. + // + installGColorsIntoUIDefaults(); + installOverriddenFontsIntoUIDefaults(overrides); + installOverriddenIconsIntoUIDefaults(overrides); + } + + /** + * Returns a mapping of normalized LaF Ids so that when fonts and icons get changed using the + * normalized ids that are presented to the user, we know which LaF ids need to be updated in + * the UiDefaults so that the LookAndFeel will pick up and use the changes. + * @return a mapping of normalized LaF ids to original LaF ids. + */ + public Map getNormalizedIdToLafIdMap() { + Map map = new HashMap<>(); + for (Entry entry : lafIdToNormalizedIdMap.entrySet()) { + String lafId = entry.getKey(); + String standardId = entry.getValue(); + map.put(standardId, lafId); + } + return map; + } + + /** + * Registers any {@link LookAndFeel} ids that are not used directly (e.g. "control", "text", + * etc.) so that these values won't get mapped to any normalized id. There is no need for these + * values to show up in the theme values, since changing them will have no effect. They are + * used to seed the values for the system color and fonts. Subclasses should + * override this method to add additional ids so they won't show up in the theme values. + */ + protected void registerIgnoredLafIds() { + + ignoredLafIds.add("desktop"); + ignoredLafIds.add("activeCaption"); + ignoredLafIds.add("activeCaptionText"); + ignoredLafIds.add("activeCaptionBorder"); + ignoredLafIds.add("inactiveCaption"); + ignoredLafIds.add("inactiveCaptionText"); + ignoredLafIds.add("inactiveCaptionBorder"); + ignoredLafIds.add("window"); + ignoredLafIds.add("windowBorder"); + ignoredLafIds.add("windowText"); + ignoredLafIds.add("menu"); + ignoredLafIds.add("menuText"); + ignoredLafIds.add("text"); + ignoredLafIds.add("textText"); + ignoredLafIds.add("textHighlight"); + ignoredLafIds.add("textHighightText"); + ignoredLafIds.add("textInactiveText"); + ignoredLafIds.add("control"); + ignoredLafIds.add("controlText"); + ignoredLafIds.add("controlHighlight"); + ignoredLafIds.add("controlLtHighlight"); + ignoredLafIds.add("controlShadow"); + ignoredLafIds.add("controlDkShadow"); + ignoredLafIds.add("info"); + ignoredLafIds.add("infoText"); + ignoredLafIds.add("scrollbar"); + } + + /** + * Defines the values to assign to all the system color ids based on the system ids + * defined in the {@link BasicLookAndFeel} + */ + protected void assignSystemColorValues() { + + assignSystemColorFromLafId(BG_CONTROL_ID, "control"); + assignSystemColorFromLafId(FG_CONTROL_ID, "controlText"); + assignSystemColorFromLafId(BG_BORDER_ID, "controlShadow"); + + assignSystemColorFromLafId(BG_VIEW_ID, "window"); + assignSystemColorFromLafId(FG_VIEW_ID, "windowText"); + assignSystemColorFromLafId(BG_VIEW_SELECTED_ID, "textHighlight"); + assignSystemColorFromLafId(FG_VIEW_SELECTED_ID, "textHighlightText"); + + assignSystemColorFromLafId(FG_DISABLED_ID, "textInactiveText"); + + assignSystemColorFromLafId(BG_TOOLTIP_ID, "info"); + assignSystemColorFromLafId(FG_TOOLTIP_ID, "infoText"); + } + + /** + * Assigns the system color id to a color value from the UiDefaults map. + * @param systemColorId the system color id to get a value for + * @param lafId the LaF key to use to retrieve a color from the UiDefaults + */ + protected void assignSystemColorFromLafId(String systemColorId, String lafId) { + Color lafColor = defaults.getColor(lafId); + if (lafColor == null) { + Msg.debug(this, "Missing value for system color: \"" + systemColorId + + "\". No value for laf id: \"" + lafId + "\"."); + return; + } + normalizedValues.addColor(new ColorValue(systemColorId, lafColor)); + } + + /** + * Assigns the system color id to a directly specified color and does not use the LaF to populate + * the system color. + * @param systemColorId the system color id to assign the given color + * @param color the color to be assigned to the system color id + */ + protected void assignSystemColorDirect(String systemColorId, Color color) { + normalizedValues.addColor(new ColorValue(systemColorId, color)); + } + + /** + * Assigns the system font id a directly specified font and does not use the LaF to populate + * the system font. + * @param systemFontId the system font id to assign the given font + * @param font the font to be assigned to the system font id + */ + protected void assignSystemFontDirect(String systemFontId, Font font) { + normalizedValues.addFont(new FontValue(systemFontId, font)); + } + + /** + * Defines the values to use for the system fonts. + */ + protected void assignSystemFontValues() { + assignSystemFontFromLafId(FONT_CONTROL_ID, "Button.font"); + assignSystemFontFromLafId(FONT_VIEW_ID, "Table.font"); + assignSystemFontFromLafId(FONT_MENU_ID, "Menu.font"); + } + + private void assignSystemFontFromLafId(String systemFontId, String lafId) { + + Font lafFont = extractedValues.getResolvedFont(lafId); + if (lafFont == null) { + Msg.debug(this, "Missing value for system font: \"" + systemFontId + + "\". No value for laf id: \"" + lafId + "\"."); + return; + } + normalizedValues.addFont(new FontValue(systemFontId, fromUiResource(lafFont))); + } + + /** + * Assigns the appropriate font matcher to each component in the related component group + */ + protected void assignFontMatchersToComponentIds() { + defineComponentFontMatcher(MENU_COMPONENTS, menuFontMatcher); + defineComponentFontMatcher(VIEW_COMPONENTS, viewFontMatcher); + } + + /** + * Assigns the appropriate color matcher to each component in the related component group + */ + protected void assignColorMatchersToComponentIds() { + defineComponentColorMatcher(VIEW_COMPONENTS, viewColorMatcher); + defineComponentColorMatcher(TOOLTIP_COMPONENTS, tooltipColorMatcher); + } + + /** + * Assigns every component name in the component group to the given ColorValueMatcher + * @param componentGroups a list of component names + * @param matcher the ColorMatcher that will provide the precedence of system ids to + * search when replacing LaF component specific values + */ + private void defineComponentColorMatcher(String[] componentGroups, ColorMatcher matcher) { + for (String componentGroup : componentGroups) { + componentToColorMatcherMap.put(componentGroup, matcher); + } + } + + /** + * Assigns every component name in a component group to the given FontValueMapper + * @param componentGroups a list of component names + * @param matcher the FontValueMatcher that will provide the precedence of ststem font ids to + * search when replacing LaF component specific fonts with a system Font + */ + private void defineComponentFontMatcher(String[] componentGroups, FontMatcher matcher) { + for (String componentGroup : componentGroups) { + componentToFontMatcherMap.put(componentGroup, matcher); + } + } + + /** + * Populates the GThemeValueMap with normalized font ids. For example + * it will assign "laf.font.Button.font" to "system.font.control". + */ + private void assignNormalizedFontValues() { + List list = new ArrayList<>(extractedValues.getFontIds()); + Collections.sort(list); + for (String lafId : list) { + // we don't want to create java defaults for laf system ids since changing them would + // have no effect + if (ignoredLafIds.contains(lafId)) { + continue; + } + + String createdId = LAF_FONT_ID_PREFIX + lafId; + lafIdToNormalizedIdMap.put(lafId, createdId); + + Font lafFont = extractedValues.getResolvedFont(lafId); + FontValue fontValue = getFontValue(createdId, lafId, lafFont); + normalizedValues.addFont(fontValue); + } + } + + /** + * Populates the GThemeValueMap with normalized icon ids. For example + * it will assign "laf.font.CheckBox.icon" to a direct icon that was mined from the UiDefaults + * using the id "CheckBox.icon" + */ + private void assignNormalizedIconValues() { + for (String lafId : extractedValues.getIconIds()) { + String createdId = LAF_ICON_ID_PREFIX + lafId; + Icon icon = extractedValues.getResolvedIcon(lafId); + if (icon != null) { + normalizedValues.addIcon(new IconValue(createdId, icon)); + lafIdToNormalizedIdMap.put(lafId, createdId); + } + } + } + + /** + * Populates the GThemeValueMap with normalized color ids. For example + * it will assign "laf.color.Button.background" to "system.color.bg.control". + */ + protected void assignNormalizedColorValues() { + List list = new ArrayList<>(extractedValues.getColorIds()); + Collections.sort(list); + for (String lafId : list) { + if (ignoredLafIds.contains(lafId)) { + continue; + } + String createdId = LAF_COLOR_ID_PREFIX + lafId; + lafIdToNormalizedIdMap.put(lafId, createdId); + + Color lafColor = extractedValues.getResolvedColor(lafId); + ColorValue colorValue = getColorValue(createdId, lafId, lafColor); + normalizedValues.addColor(colorValue); + } + } + + /** + * Creates a {@link ColorValue} for the given id. It either finds a system color id that matches + * the given color, or a shared palette color if no system color found. + * @param id the id to get a color value for + * @param lafId the lafId that we are creating a standard id/color value for + * @param lafColor the color as defined in the UiDefaults for the lafId + * @return a {@link ColorValue} for the given id. It either finds a system color id that matches + * the given color, or a shared palette color if no system color found. + */ + private ColorValue getColorValue(String id, String lafId, Color lafColor) { + String systemId = findSystemColorId(lafId, lafColor); + + if (systemId == null) { + systemId = getColorPaletteId(lafColor); + } + + return new ColorValue(id, systemId); + } + + /** + * Creates a {@link FontValue} for the given id. It either finds a system font id that matches + * the given Font, or a shared palette Font if no system font found. + * @param id the id to get a Font value for + * @param lafId the lafId that we are creating a standard id/Font value for + * @param lafFont the Font as defined in the UiDefaults for the lafId + * @return a {@link FontValue} for the given id. It either finds a system font id that matches + * the given Font, or a shared palette Font if no group Font found. + */ + private FontValue getFontValue(String id, String lafId, Font lafFont) { + String systemFontId = findSystemFontId(lafId, lafFont); + if (systemFontId == null) { + systemFontId = getFontPaletteId(lafFont); + } + return new FontValue(id, systemFontId); + } + + /** + * Finds a matching color palette id or creates a new one + * @param lafColor the color to find a matching palette color for + * @return a matching color palette id or creates a new one + */ + private String getColorPaletteId(Color lafColor) { + String paletteId = lafColorPaletteMap.get(lafColor); + if (paletteId == null) { + nextColorPaletteId++; + // laf.palette.color01 + paletteId = String.format("%s%02d", LAF_COLOR_PALETTE_PREFIX, nextColorPaletteId); + lafColorPaletteMap.put(lafColor, paletteId); + normalizedValues.addColor(new ColorValue(paletteId, lafColor)); + } + return paletteId; + } + + /** + * Finds a matching font palette id or creates a new one + * @param lafFont the font to find a matching palette font for + * @return a matching font palette id or creates a new one + */ + private String getFontPaletteId(Font lafFont) { + String paletteId = lafFontPaletteMap.get(lafFont); + if (paletteId == null) { + nextFontPaletteId++; + + // laf.palette.font01 + paletteId = String.format("%s%02d", LAF_FONT_PALETTE_PREFIX, nextFontPaletteId); + lafFontPaletteMap.put(lafFont, paletteId); + normalizedValues.addFont(new FontValue(paletteId, lafFont)); + } + return paletteId; + } + + /** + * Attempts to find a system color id that matches the given color. The order system ids are + * searched depends on the component (Button, Menu, etc.) which is derived from the given + * lafId. + * @param lafId the lafId we are attempting to get a system color for + * @param lafColor the color we are trying to match to a system color + * @return a system color id that matches the given lafColor or null if one can't be found + */ + private String findSystemColorId(String lafId, Color lafColor) { + String componentName = getComponentName(lafId); + ColorMatcher colorMatcher = componentToColorMatcherMap.get(componentName); + // check in widget specific group first + if (colorMatcher != null) { + String systemId = colorMatcher.getSystemId(lafColor); + if (systemId != null) { + return systemId; + } + } + // not found in widget specific group, check general component groups + return defaultColorMatcher.getSystemId(lafColor); + } + + /** + * Attempts to find a system font id that matches the given font. The order system fonts are + * searched depends on the component (Button, Menu, etc.) which is derived from the given + * lafId. + * @param lafId the lafId we are attempting to get a system font for + * @param lafFont the font we are trying to match to a system font + * @return a system font id that matches the given lafFont or null if one can't be found + */ + private String findSystemFontId(String lafId, Font lafFont) { + String componentName = getComponentName(lafId); + FontMatcher fontMatcher = componentToFontMatcherMap.get(componentName); + // check in widget specific group first + if (fontMatcher != null) { + String systemId = fontMatcher.getSystemId(lafFont); + if (systemId != null) { + return systemId; + } + } + // not found in widget specific group, check general component groups + return defaultFontMatcher.getSystemId(lafFont); + } + + /** + * Gets the component name from the given lafId. + * @param lafId the lafId that starts with a component name + * @return the component name from the given lafId. + */ + private String getComponentName(String lafId) { + int dotIndex = lafId.indexOf("."); + if (dotIndex < 0) { + return lafId; + } + return lafId.substring(0, dotIndex); + } + + /** + * Replaces UiDefaults values with {@link GColorUIResource} values the provide the theme + * indirection. + */ + protected void installGColorsIntoUIDefaults() { + Map cachedColors = new HashMap<>(); + + for (String lafId : extractedValues.getColorIds()) { + String standardColorId = lafIdToNormalizedIdMap.get(lafId); + if (standardColorId != null) { + GColorUIResource sharedGColor = getSharedGColor(cachedColors, standardColorId); + defaults.put(lafId, sharedGColor); + } + } + } + + /** + * Replaces any theme overridden icons into the UiDefaults. + * @param overrides the theme values that potentially override a laf icon value + */ + private void installOverriddenIconsIntoUIDefaults(GThemeValueMap overrides) { + for (String lafId : extractedValues.getIconIds()) { + String standardId = lafIdToNormalizedIdMap.get(lafId); + if (overrides.containsIcon(standardId)) { + defaults.put(lafId, overrides.getResolvedIcon(standardId)); + } + } + } + + /** + * Replaces any theme overridden fonts into the UiDefaults. + * @param overrides the theme values that potentially override a laf font value + */ + private void installOverriddenFontsIntoUIDefaults(GThemeValueMap overrides) { + for (String lafId : extractedValues.getFontIds()) { + String standardId = lafIdToNormalizedIdMap.get(lafId); + if (overrides.containsFont(standardId)) { + defaults.put(lafId, new FontUIResource(overrides.getResolvedFont(standardId))); + } + } + } + + /** + * When putting {@link GColorUIResource} values into the UiDefaults, we need to make sure + * we use the same instance for the same color. Some LookAndFeels do "==" checks on colors + * when updating UI values. + * @param cache the cache of shared {@link GColorUIResource}s + * @param id the id we are creating a shared GColorUIResource for + * @return a GColorUIResource such that only one instance for a given id exists. + */ + private GColorUIResource getSharedGColor(Map cache, String id) { + GColorUIResource gColor = cache.get(id); + if (gColor == null) { + gColor = new GColorUIResource(id); + cache.put(id, gColor); + } + return gColor; + } + + protected void overrideColor(String lafId, String sytemId) { + String normalizedId = lafIdToNormalizedIdMap.get(lafId); + if (normalizedId == null) { + Msg.debug(this, "Missing value for laf id: \"" + lafId); + return; + } + normalizedValues.addColor(new ColorValue(normalizedId, sytemId)); + } + + /** + * Mines the UiDefaults for all color values. + * @return a map of id to values for UIDefaults Colors. + */ + protected GThemeValueMap extractColorFontAndIconValuesFromDefaults() { + GThemeValueMap values = new GThemeValueMap(); + + List ids = getLookAndFeelIdsForType(Color.class); + for (String id : ids) { + values.addColor(new ColorValue(id, defaults.getColor(id))); + } + + ids = getLookAndFeelIdsForType(Font.class); + for (String id : ids) { + values.addFont(new FontValue(id, defaults.getFont(id))); + } + + ids = getLookAndFeelIdsForType(Icon.class); + for (String id : ids) { + Icon icon = defaults.getIcon(id); + values.addIcon(new IconValue(id, icon)); + } + return values; + } + + private Font fromUiResource(Font font) { + if (font instanceof UIResource) { + return new FontNonUiResource(font); + } + return font; + } + + /** + * Finds all ids in the UIDefaults for a specific type (Color, Font, Icon) + * @param clazz the class of the type to mine for + * @return a list of all ids that have the given value type + */ + private List getLookAndFeelIdsForType(Class clazz) { + List colorKeys = new ArrayList<>(); + List keyList = IteratorUtils.toList(defaults.keys().asIterator()); + for (Object key : keyList) { + if (key instanceof String) { + Object value = defaults.get(key); + if (clazz.isInstance(value)) { + colorKeys.add((String) key); + } + } + } + return colorKeys; + } + + /** + * Used to match values (Colors or Fonts) into appropriate system ids. System ids are searched + * in the order the system ids are given in the constructor. + * @param The theme value type (Color or Font) + */ + private abstract class ValueMatcher { + private Map map = new HashMap<>(); + private List systemIdList; + private boolean initialized; + + ValueMatcher(String... systemIds) { + systemIdList = new ArrayList<>(Arrays.asList(systemIds)); + } + + private void initialize() { + initialized = true; + + // process in reverse order so that earlier items in the list can overwrite later + // items if they have the same font + for (int i = systemIdList.size() - 1; i >= 0; i--) { + String systemId = systemIdList.get(i); + T value = getValueFromJavaDefaults(systemId); + if (value != null) { + map.put(value, systemId); + } + } + } + + protected abstract T getValueFromJavaDefaults(String systemId); + + String getSystemId(T value) { + if (!initialized) { + initialize(); + } + return map.get(value); + } + } + + /** + * Searches through all the system color ids registered for this matcher to find a system color + * id that matches a given color. The order that color system ids are added is important and is + * the precedence order if more than one system color id has the same color. + */ + private class ColorMatcher extends ValueMatcher { + + ColorMatcher(String... systemIds) { + super(systemIds); + } + + @Override + protected Color getValueFromJavaDefaults(String systemId) { + return normalizedValues.getResolvedColor(systemId); + } + + } + + /** + * Searches through all the system font ids registered for this matcher to find a system font id + * that matches a given font. The order that system font ids are added is important and is + * the precedence order if more than one system id has the same font. + */ + private class FontMatcher extends ValueMatcher { + FontMatcher(String... systemIds) { + super(systemIds); + } + + @Override + protected Font getValueFromJavaDefaults(String systemId) { + return normalizedValues.getResolvedFont(systemId); + } + } +} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsClassicLookAndFeelManager.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsClassicLookAndFeelManager.java index c6ba737d0e..f871c231e5 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsClassicLookAndFeelManager.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsClassicLookAndFeelManager.java @@ -15,6 +15,8 @@ */ package generic.theme.laf; +import javax.swing.UIDefaults; + import generic.theme.ApplicationThemeManager; import generic.theme.LafType; @@ -23,4 +25,9 @@ public class WindowsClassicLookAndFeelManager extends LookAndFeelManager { public WindowsClassicLookAndFeelManager(ApplicationThemeManager themeManager) { super(LafType.WINDOWS_CLASSIC, themeManager); } + + @Override + protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { + return new WindowsClassicUiDefaultsMapper(defaults); + } } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsClassicUiDefaultsMapper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsClassicUiDefaultsMapper.java new file mode 100644 index 0000000000..1cfd9d8eb5 --- /dev/null +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsClassicUiDefaultsMapper.java @@ -0,0 +1,26 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme.laf; + +import javax.swing.UIDefaults; + +public class WindowsClassicUiDefaultsMapper extends UiDefaultsMapper { + + protected WindowsClassicUiDefaultsMapper(UIDefaults defaults) { + super(defaults); + } + +} diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsLookAndFeelManager.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsLookAndFeelManager.java index ab86b2c231..31bd8d46c9 100644 --- a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsLookAndFeelManager.java +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsLookAndFeelManager.java @@ -15,6 +15,8 @@ */ package generic.theme.laf; +import javax.swing.UIDefaults; + import generic.theme.ApplicationThemeManager; import generic.theme.LafType; @@ -24,4 +26,9 @@ public class WindowsLookAndFeelManager extends LookAndFeelManager { super(LafType.WINDOWS, themeManager); } + @Override + protected UiDefaultsMapper getUiDefaultsMapper(UIDefaults defaults) { + return new WindowsUiDefaultsMapper(defaults); + } + } diff --git a/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsUiDefaultsMapper.java b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsUiDefaultsMapper.java new file mode 100644 index 0000000000..53220de96c --- /dev/null +++ b/Ghidra/Framework/Gui/src/main/java/generic/theme/laf/WindowsUiDefaultsMapper.java @@ -0,0 +1,26 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme.laf; + +import javax.swing.UIDefaults; + +public class WindowsUiDefaultsMapper extends UiDefaultsMapper { + + protected WindowsUiDefaultsMapper(UIDefaults defaults) { + super(defaults); + } + +} diff --git a/Ghidra/Framework/Gui/src/test/java/generic/theme/ApplicationThemeManagerTest.java b/Ghidra/Framework/Gui/src/test/java/generic/theme/ApplicationThemeManagerTest.java index 6bb6a60be0..8fd90bac87 100644 --- a/Ghidra/Framework/Gui/src/test/java/generic/theme/ApplicationThemeManagerTest.java +++ b/Ghidra/Framework/Gui/src/test/java/generic/theme/ApplicationThemeManagerTest.java @@ -23,12 +23,10 @@ import java.awt.Font; import java.net.URL; import java.util.*; -import javax.swing.Icon; -import javax.swing.JLabel; +import javax.swing.*; import javax.swing.plaf.UIResource; -import org.junit.Before; -import org.junit.Test; +import org.junit.*; import generic.theme.builtin.*; import resources.ResourceManager; @@ -73,6 +71,12 @@ public class ApplicationThemeManagerTest { themeManager = new DummyApplicationThemeManager(); } + @After + public void cleanupUIDefaults() { + UIDefaults defaults = UIManager.getDefaults(); + defaults.clear(); + } + @Test public void testDarkThemeColorOverride() { GColor gColor = new GColor("color.test.bg"); @@ -348,21 +352,21 @@ public class ApplicationThemeManagerTest { } @Override - protected ThemeDefaultsProvider getThemeDefaultsProvider() { - return new ThemeDefaultsProvider() { + protected ApplicationThemeDefaults getApplicationDefaults() { + return new ApplicationThemeDefaults() { @Override - public GThemeValueMap getDefaults() { + public GThemeValueMap getLightValues() { return defaultValues; } @Override - public GThemeValueMap getDarkDefaults() { + public GThemeValueMap getDarkValues() { return darkDefaultValues; } @Override - public GThemeValueMap getLookAndFeelDefaults(LafType lafType) { + public GThemeValueMap getLookAndFeelValues(LafType lafType) { return null; } diff --git a/Ghidra/Framework/Gui/src/test/java/generic/theme/laf/ThemeGrouperTest.java b/Ghidra/Framework/Gui/src/test/java/generic/theme/laf/ThemeGrouperTest.java deleted file mode 100644 index 6dfe6f4e23..0000000000 --- a/Ghidra/Framework/Gui/src/test/java/generic/theme/laf/ThemeGrouperTest.java +++ /dev/null @@ -1,104 +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 generic.theme.laf; - -import static ghidra.util.WebColors.*; -import static org.junit.Assert.*; - -import java.awt.Color; -import java.awt.Font; - -import org.junit.Before; -import org.junit.Test; - -import generic.theme.*; - -public class ThemeGrouperTest { - private static Font FONT1 = new Font("Dialog", Font.PLAIN, 12); - private static Font FONT2 = new Font("Dialog", Font.BOLD, 16); - - private ThemeGrouper grouper; - private GThemeValueMap values; - - @Before - public void setUp() { - grouper = new ThemeGrouper(); - values = new GThemeValueMap(); - } - - @Test - public void testGroupColorUsingPreferredSources() { - initColor("control", RED); - initColor("menu", RED); - initColor("Menu.background", RED); - grouper.group(values); - - ColorValue colorValue = values.getColor("Menu.background"); - assertEquals("menu", colorValue.getReferenceId()); - } - - @Test - public void testGroupColorUsingNonPreferredSourceWhenPreferredDoesntMatch() { - initColor("control", RED); - initColor("menu", BLUE); - initColor("Menu.background", RED); - grouper.group(values); - - ColorValue colorValue = values.getColor("Menu.background"); - assertEquals("control", colorValue.getReferenceId()); - } - - @Test - public void testGroupFontUsingPreferredSources() { - initFont("Button.font", FONT1); - initFont("RadioButton.font", FONT1); - initFont("Menu.font", FONT1); - initFont("MenuItem.font", FONT1); - grouper.group(values); - - assertEquals(FONT1, values.getFont("font.button").getRawValue()); - assertEquals(FONT1, values.getFont("font.menu").getRawValue()); - assertEquals("font.button", values.getFont("Button.font").getReferenceId()); - assertEquals("font.button", values.getFont("RadioButton.font").getReferenceId()); - assertEquals("font.menu", values.getFont("Menu.font").getReferenceId()); - assertEquals("font.menu", values.getFont("MenuItem.font").getReferenceId()); - } - - @Test - public void testGroupFontUsingNonPreferredSourceWhenPreferredDoesntMatch() { - initFont("Button.font", FONT1); - initFont("RadioButton.font", FONT1); - initFont("Menu.font", FONT2); - initFont("MenuItem.font", FONT1); - grouper.group(values); - - assertEquals(FONT1, values.getFont("font.button").getRawValue()); - assertEquals(FONT2, values.getFont("font.menu").getRawValue()); - assertEquals("font.button", values.getFont("Button.font").getReferenceId()); - assertEquals("font.button", values.getFont("RadioButton.font").getReferenceId()); - assertEquals("font.menu", values.getFont("Menu.font").getReferenceId()); - assertEquals("font.button", values.getFont("MenuItem.font").getReferenceId()); - } - - private void initColor(String id, Color color) { - values.addColor(new ColorValue(id, color)); - } - - private void initFont(String id, Font font) { - values.addFont(new FontValue(id, font)); - } - -} diff --git a/Ghidra/Framework/Gui/src/test/java/generic/theme/laf/UIDefaultsMapperTest.java b/Ghidra/Framework/Gui/src/test/java/generic/theme/laf/UIDefaultsMapperTest.java new file mode 100644 index 0000000000..25ef58a265 --- /dev/null +++ b/Ghidra/Framework/Gui/src/test/java/generic/theme/laf/UIDefaultsMapperTest.java @@ -0,0 +1,183 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package generic.theme.laf; + +import static org.junit.Assert.*; + +import java.awt.Color; +import java.awt.Font; +import java.util.Map; + +import javax.swing.Icon; +import javax.swing.UIDefaults; + +import org.junit.Before; +import org.junit.Test; + +import generic.theme.*; +import resources.ResourceManager; + +public class UIDefaultsMapperTest { + + private Icon ICON = ResourceManager.loadImage("images/exec.png"); + private Font SMALL_FONT = new Font("Dialog", Font.PLAIN, 4); + private Font FONT = new Font("Dialog", Font.PLAIN, 13); + private UIDefaults defaults; + private UiDefaultsMapper mapper; + + @Before + public void setup() { + defaults = createDefaults(); + defaults.put("control", Color.RED); + defaults.put("Button.background", Color.RED); + defaults.put("Button.font", FONT); + defaults.put("CheckBox.icon", ICON); + + } + + @Test + public void testGetJavaDefaults() { + mapper = new UiDefaultsMapper(defaults); + GThemeValueMap javaDefaults = mapper.getJavaDefaults(); + + assertEquals(Color.RED, javaDefaults.getResolvedColor("system.color.bg.control")); + assertEquals(Color.RED, javaDefaults.getResolvedColor("laf.color.Button.background")); + + assertEquals(FONT, javaDefaults.getResolvedFont("laf.font.Button.font")); + assertEquals(ICON, javaDefaults.getResolvedIcon("laf.icon.CheckBox.icon")); + + assertIndirectColor(javaDefaults, "laf.color.Button.background", "system.color.bg.control"); + assertIndirectFont(javaDefaults, "laf.font.Button.font", "system.font.control"); + assertDirectIcon(javaDefaults, "laf.icon.CheckBox.icon", ICON); + } + + @Test + public void testGetJavaDefaultsNoGroupCreatesPaletteColors() { + defaults.put("CheckBox.background", Color.GREEN); // Green not defined in a color group + defaults.put("ToggleButton.background", Color.GREEN); // Green not defined in a color group + defaults.put("RadioButton.background", Color.BLUE); // Blue not defined in a color group + mapper = new UiDefaultsMapper(defaults); + + GThemeValueMap javaDefaults = mapper.getJavaDefaults(); + + // expecting two palette groups to be created + String greenPalette = findPaletteColor(javaDefaults, Color.GREEN); + String bluePalette = findPaletteColor(javaDefaults, Color.BLUE); + + assertIndirectColor(javaDefaults, "laf.color.Button.background", "system.color.bg.control"); + assertIndirectColor(javaDefaults, "laf.color.CheckBox.background", greenPalette); + assertIndirectColor(javaDefaults, "laf.color.ToggleButton.background", greenPalette); + assertIndirectColor(javaDefaults, "laf.color.RadioButton.background", bluePalette); + assertDirectColor(javaDefaults, "system.color.bg.control", Color.RED); + } + + @Test + public void testGetJavaDefaultsNoGroupCreatesPaletteFonts() { + defaults.put("CheckBox.font", SMALL_FONT); // SMALL_FONT not defined in a font group + defaults.put("ToggleButton.font", SMALL_FONT); // Green not defined in a color group + mapper = new UiDefaultsMapper(defaults); + + GThemeValueMap javaDefaults = mapper.getJavaDefaults(); + + assertDirectFont(javaDefaults, "laf.palette.font.01", SMALL_FONT); + assertIndirectFont(javaDefaults, "laf.font.ToggleButton.font", "laf.palette.font.01"); + assertIndirectFont(javaDefaults, "laf.font.CheckBox.font", "laf.palette.font.01"); + assertIndirectFont(javaDefaults, "laf.font.Button.font", "system.font.control"); + + } + + @Test + public void testInstallValuesIntoUiDefaults() { + mapper = new UiDefaultsMapper(defaults); + mapper.installValuesIntoUIDefaults(new GThemeValueMap()); + + assertEquals(new GColorUIResource("laf.color.Button.background"), + defaults.getColor("Button.background")); + assertEquals(FONT, defaults.getFont("Button.font")); + assertEquals(ICON, defaults.getIcon("CheckBox.icon")); + } + + @Test + public void testGetNormalizedIdToLafidMap() { + mapper = new UiDefaultsMapper(defaults); + Map map = mapper.getNormalizedIdToLafIdMap(); + assertEquals("Button.background", map.get("laf.color.Button.background")); + assertEquals("Button.font", map.get("laf.font.Button.font")); + assertEquals("CheckBox.icon", map.get("laf.icon.CheckBox.icon")); + } + + private void assertDirectColor(GThemeValueMap javaDefaults, String id, Color color) { + ColorValue colorValue = javaDefaults.getColor(id); + assertEquals(color, colorValue.getRawValue()); + } + + private void assertDirectFont(GThemeValueMap javaDefaults, String id, Font font) { + FontValue fontValue = javaDefaults.getFont(id); + assertEquals(font, fontValue.getRawValue()); + } + + private void assertIndirectColor(GThemeValueMap javaDefaults, String id, String indirectId) { + ColorValue colorValue = javaDefaults.getColor(id); + assertEquals(indirectId, colorValue.getReferenceId()); + } + + private void assertIndirectFont(GThemeValueMap javaDefaults, String id, String indirectId) { + FontValue fontValue = javaDefaults.getFont(id); + assertEquals(indirectId, fontValue.getReferenceId()); + } + + private void assertDirectIcon(GThemeValueMap javaDefaults, String id, Icon icon) { + IconValue iconValue = javaDefaults.getIcon(id); + assertEquals(icon, iconValue.getRawValue()); + } + + private String findPaletteColor(GThemeValueMap javaDefaults, Color color) { + for (ColorValue colorValue : javaDefaults.getColors()) { + if (colorValue.getId().contains("palette.color") && + colorValue.getRawValue().equals(color)) { + return colorValue.getId(); + } + } + fail("Could not find pallete color for " + color); + return null; + } + + private UIDefaults createDefaults() { + // populate defaults with standard laf group values, to avoid warning messages complaining + // about them being undefined + UIDefaults uiDefaults = new UIDefaults(); + uiDefaults.put("control", Color.BLACK); // for laf.group.control.color.bg + uiDefaults.put("controlText", Color.BLACK); // for laf.group.control.color.fg + uiDefaults.put("controlShadow", Color.BLACK); // for laf.group.control.color.border + uiDefaults.put("window", Color.BLACK); // for laf.group.view.color.bg + uiDefaults.put("windowText", Color.BLACK); // for laf.group.view.color.fg + uiDefaults.put("textHighlight", Color.BLACK); // for laf.group.view.color.bg.selected and laf.group.text.color.bg.selected + uiDefaults.put("textHighlightText", Color.BLACK); // for laf.group.view.color.fg.selected and laf.group.text.color.fg.selected + + uiDefaults.put("textText", Color.BLACK); // for laf.group.text.color.fg + uiDefaults.put("text", Color.BLACK); // for laf.group.text.color.bg + uiDefaults.put("textInactiveText", Color.BLACK); // for laf.group.text.color.fg.disabled + uiDefaults.put("info", Color.BLACK); // for laf.group.tooltip.color.bg + uiDefaults.put("infoText", Color.BLACK); // for laf.group.tooltip.color.fg + + uiDefaults.put("Panel.font", FONT); // for laf.group.control.font + uiDefaults.put("TextField.font", FONT); // for laf.group.text.font + uiDefaults.put("Table.font", FONT); // for laf.group.view.font + uiDefaults.put("Menu.font", FONT); // for laf.group.menu.font + + return uiDefaults; + } +}