From 856b17197fe3ab148e9983de792cc0f80b857de3 Mon Sep 17 00:00:00 2001 From: Akumas_Coffin_ <68540569+AkumasCoffin@users.noreply.github.com> Date: Thu, 14 May 2026 22:29:18 +1000 Subject: [PATCH] Refresh detached Channel-tab swing components on theme toggle The Channel tab swaps one of three right-pane components into its JSplitPane depending on the active decoder type: SignalPowerView for AM, the NoiseSquelchPanel JFXPanel for NBFM, the SymbolPanel JFXPanel for feedback decoders. Only one is attached at any moment - the other two are detached references on the ChannelSpectrumPanel. SwingUtilities.updateComponentTreeUI(Window) during a theme toggle walks only the currently-realized component trees, so the detached swappables miss the refresh and carry stale colours from the LAF that was active when they were last attached. Swapping back in then shows e.g. an AM SignalPowerView in dark colours after a light-mode toggle. Add ThemeManager.registerSwing(Component) tracking, walk those refs on every toggle, and register the three swappables when constructed. --- .../gui/channel/ChannelSpectrumPanel.java | 7 +++ .../dsheirer/gui/theme/ThemeManager.java | 52 ++++++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/github/dsheirer/gui/channel/ChannelSpectrumPanel.java b/src/main/java/io/github/dsheirer/gui/channel/ChannelSpectrumPanel.java index 1c3c7d0d..8da76277 100644 --- a/src/main/java/io/github/dsheirer/gui/channel/ChannelSpectrumPanel.java +++ b/src/main/java/io/github/dsheirer/gui/channel/ChannelSpectrumPanel.java @@ -102,6 +102,11 @@ public class ChannelSpectrumPanel extends JPanel implements Listener { diff --git a/src/main/java/io/github/dsheirer/gui/theme/ThemeManager.java b/src/main/java/io/github/dsheirer/gui/theme/ThemeManager.java index c01c3d02..bc122ece 100644 --- a/src/main/java/io/github/dsheirer/gui/theme/ThemeManager.java +++ b/src/main/java/io/github/dsheirer/gui/theme/ThemeManager.java @@ -61,6 +61,7 @@ public class ThemeManager private static final ThemeManager INSTANCE = new ThemeManager(); private final Set> mScenes = new LinkedHashSet<>(); + private final Set> mSwingRoots = new LinkedHashSet<>(); private final String mDarkStylesheetUrl; private volatile UserPreferences mUserPreferences; private volatile boolean mDarkMode; @@ -140,6 +141,36 @@ public class ThemeManager applyToScene(scene, mDarkMode); } + /** + * Register a Swing component that may be temporarily detached from any Window's component tree + * (e.g. swappable right-pane components in the Channel tab). Plain + * {@link SwingUtilities#updateComponentTreeUI(java.awt.Component)} walks of + * {@link Window#getWindows()} miss such components and they end up carrying stale colours from + * the LAF that was active when they were last attached. Registered components get + * {@code updateComponentTreeUI} called on them whenever the theme is applied. + */ + public void registerSwing(java.awt.Component component) + { + if(component == null) + { + return; + } + + synchronized(mSwingRoots) + { + Iterator> it = mSwingRoots.iterator(); + while(it.hasNext()) + { + java.awt.Component existing = it.next().get(); + if(existing == null || existing == component) + { + it.remove(); + } + } + mSwingRoots.add(new WeakReference<>(component)); + } + } + /** * Receives notification when any preference changes. Re-reads the dark-mode setting and * propagates if it differs from the last known value. @@ -309,12 +340,31 @@ public class ThemeManager //near-white rather than FlatLaf's #bbbbbb so text reads sharply on the dark panels. applyExplicitOverrides(darkMode); - //Re-rendering any already-realized Swing windows must happen on the EDT. + //Re-rendering any already-realized Swing windows must happen on the EDT. Also walk any + //Swing components explicitly registered via registerSwing(...) - they may be detached + //from any Window's tree at toggle time (e.g. swappable channel-tab right components). Runnable updateRealized = () -> { for(Window window: Window.getWindows()) { SwingUtilities.updateComponentTreeUI(window); } + + synchronized(mSwingRoots) + { + Iterator> it = mSwingRoots.iterator(); + while(it.hasNext()) + { + java.awt.Component c = it.next().get(); + if(c == null) + { + it.remove(); + } + else + { + SwingUtilities.updateComponentTreeUI(c); + } + } + } }; if(EventQueue.isDispatchThread())