From 8bead998f513ddc453b3733d5fa5efda4931d51e Mon Sep 17 00:00:00 2001 From: Dennis Sheirer Date: Sat, 18 Nov 2023 03:26:21 -0500 Subject: [PATCH] #1743 Resolves CPU status indicator strobing and adds control to show/hide status panel and now playing channel details tabs. --- .../channel/metadata/NowPlayingPanel.java | 89 ++++++++++--- .../dsheirer/controller/ControllerPanel.java | 12 +- .../java/io/github/dsheirer/gui/SDRTrunk.java | 123 +++++++++++++++--- .../dsheirer/monitor/ResourceMonitor.java | 15 ++- .../io/github/dsheirer/monitor/StatusBox.java | 9 +- .../github/dsheirer/monitor/StatusPanel.java | 37 ------ 6 files changed, 209 insertions(+), 76 deletions(-) delete mode 100644 src/main/java/io/github/dsheirer/monitor/StatusPanel.java diff --git a/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java b/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java index 8200516f..963f73c3 100644 --- a/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java +++ b/src/main/java/io/github/dsheirer/channel/metadata/NowPlayingPanel.java @@ -33,6 +33,9 @@ import net.miginfocom.swing.MigLayout; import javax.swing.JPanel; +/** + * Swing panel for Now Playing channels table and channel details tab set. + */ public class NowPlayingPanel extends JPanel { private final ChannelMetadataPanel mChannelMetadataPanel; @@ -40,45 +43,95 @@ public class NowPlayingPanel extends JPanel private final DecodeEventPanel mDecodeEventPanel; private final MessageActivityPanel mMessageActivityPanel; private final ChannelPowerPanel mChannelPowerPanel; + private JideTabbedPane mTabbedPane; + private JideSplitPane mSplitPane; + private boolean mDetailTabsVisible; /** * GUI panel that combines the currently decoding channels metadata table and viewers for channel details, * messages, events, and spectral view. */ public NowPlayingPanel(PlaylistManager playlistManager, IconModel iconModel, UserPreferences userPreferences, - SettingsManager settingsManager) + SettingsManager settingsManager, boolean detailTabsVisible) { mChannelDetailPanel = new ChannelDetailPanel(playlistManager.getChannelProcessingManager()); mDecodeEventPanel = new DecodeEventPanel(iconModel, userPreferences, playlistManager.getAliasModel()); mMessageActivityPanel = new MessageActivityPanel(userPreferences); mChannelMetadataPanel = new ChannelMetadataPanel(playlistManager, iconModel, userPreferences); mChannelPowerPanel = new ChannelPowerPanel(playlistManager, settingsManager); + mDetailTabsVisible = detailTabsVisible; init(); } + /** + * Change the visibility of the channel details tabs panel. + * @param visible true to show or false to hide. + */ + public void setDetailTabsVisible(boolean visible) + { + //Only adjust if there is a change in state + if(visible ^ mDetailTabsVisible) + { + mDetailTabsVisible = visible; + + if(mDetailTabsVisible) + { + getSplitPane().add(getTabbedPane()); + } + else + { + getSplitPane().remove(getTabbedPane()); + } + + revalidate(); + } + } + + private JideTabbedPane getTabbedPane() + { + if(mTabbedPane == null) + { + mTabbedPane = new JideTabbedPane(); + mTabbedPane.addTab("Details", mChannelDetailPanel); + mTabbedPane.addTab("Events", mDecodeEventPanel); + mTabbedPane.addTab("Messages", mMessageActivityPanel); + mTabbedPane.addTab("Channel", mChannelPowerPanel); + mTabbedPane.setFont(this.getFont()); + mTabbedPane.setForeground(Color.BLACK); + //Register state change listener to toggle visibility state for channel tab to turn-on/off FFT processing + mTabbedPane.addChangeListener(e -> mChannelPowerPanel.setPanelVisible(getTabbedPane().getSelectedIndex() == getTabbedPane() + .indexOfComponent(mChannelPowerPanel))); + } + + return mTabbedPane; + } + + /** + * Split pane for channels table and channel details tabs. + */ + private JideSplitPane getSplitPane() + { + if(mSplitPane == null) + { + mSplitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT); + mSplitPane.setShowGripper(true); + } + + return mSplitPane; + } + private void init() { setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[grow,fill]") ); + getSplitPane().add(mChannelMetadataPanel); - JideTabbedPane tabbedPane = new JideTabbedPane(); - tabbedPane.addTab("Details", mChannelDetailPanel); - tabbedPane.addTab("Events", mDecodeEventPanel); - tabbedPane.addTab("Messages", mMessageActivityPanel); - tabbedPane.addTab("Channel", mChannelPowerPanel); - tabbedPane.setFont(this.getFont()); - tabbedPane.setForeground(Color.BLACK); - - //Register state change listener to toggle visibility state for channel tab to turn-on/off FFT processing - tabbedPane.addChangeListener(e -> mChannelPowerPanel.setPanelVisible(tabbedPane.getSelectedIndex() == tabbedPane - .indexOfComponent(mChannelPowerPanel))); - - JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT); - splitPane.setShowGripper(true); - splitPane.add(mChannelMetadataPanel); - splitPane.add(tabbedPane); - add(splitPane); + if(mDetailTabsVisible) + { + getSplitPane().add(getTabbedPane()); + } + add(getSplitPane()); mChannelMetadataPanel.addProcessingChainSelectionListener(mChannelDetailPanel); mChannelMetadataPanel.addProcessingChainSelectionListener(mDecodeEventPanel); mChannelMetadataPanel.addProcessingChainSelectionListener(mMessageActivityPanel); diff --git a/src/main/java/io/github/dsheirer/controller/ControllerPanel.java b/src/main/java/io/github/dsheirer/controller/ControllerPanel.java index a3d510d7..1f4c62d6 100644 --- a/src/main/java/io/github/dsheirer/controller/ControllerPanel.java +++ b/src/main/java/io/github/dsheirer/controller/ControllerPanel.java @@ -59,17 +59,25 @@ public class ControllerPanel extends JPanel public ControllerPanel(PlaylistManager playlistManager, AudioPlaybackManager audioPlaybackManager, IconModel iconModel, MapService mapService, SettingsManager settingsManager, - TunerManager tunerManager, UserPreferences userPreferences) + TunerManager tunerManager, UserPreferences userPreferences, boolean detailTabsVisible) { mAudioPanel = new AudioPanel(iconModel, userPreferences, settingsManager, audioPlaybackManager, playlistManager.getAliasModel()); - mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconModel, userPreferences, settingsManager); + mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconModel, userPreferences, settingsManager, detailTabsVisible); mMapPanel = new MapPanel(mapService, playlistManager.getAliasModel(), iconModel, settingsManager); mTunerManagerPanel = new TunerViewPanel(tunerManager, userPreferences); init(); } + /** + * Now playing panel. + */ + public NowPlayingPanel getNowPlayingPanel() + { + return mNowPlayingPanel; + } + private void init() { setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[]0[grow,fill]0[]")); diff --git a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java index ddb6c70d..1313571f 100644 --- a/src/main/java/io/github/dsheirer/gui/SDRTrunk.java +++ b/src/main/java/io/github/dsheirer/gui/SDRTrunk.java @@ -81,6 +81,7 @@ import java.nio.file.Path; import java.util.List; import java.util.Locale; import java.util.Optional; +import java.util.prefs.Preferences; import javafx.application.Platform; import javafx.embed.swing.JFXPanel; import javafx.scene.control.ButtonType; @@ -107,14 +108,19 @@ import javax.swing.plaf.metal.MetalLookAndFeel; public class SDRTrunk implements Listener { private final static Logger mLog = LoggerFactory.getLogger(SDRTrunk.class); + private Preferences mPreferences = Preferences.userNodeForPackage(SDRTrunk.class); - private static final String PROPERTY_BROADCAST_STATUS_VISIBLE = "main.broadcast.status.visible"; + private static final String PREFERENCE_BROADCAST_STATUS_VISIBLE = "sdrtrunk.broadcast.status.visible"; + private static final String PREFERENCE_NOW_PLAYING_DETAILS_VISIBLE = "sdrtrunk.now.playing.details.visible"; + private static final String PREFERENCE_RESOURCE_STATUS_VISIBLE = "sdrtrunk.resource.status.visible"; private static final String BASE_WINDOW_NAME = "sdrtrunk.main.window"; private static final String CONTROLLER_PANEL_IDENTIFIER = BASE_WINDOW_NAME + ".control.panel"; private static final String SPECTRAL_PANEL_IDENTIFIER = BASE_WINDOW_NAME + ".spectral.panel"; private static final String WINDOW_FRAME_IDENTIFIER = BASE_WINDOW_NAME + ".frame"; private boolean mBroadcastStatusVisible; + private boolean mResourceStatusVisible; + private boolean mNowPlayingDetailsVisible; private AudioRecordingManager mAudioRecordingManager; private AudioStreamingManager mAudioStreamingManager; private BroadcastStatusPanel mBroadcastStatusPanel; @@ -130,7 +136,7 @@ public class SDRTrunk implements Listener private TunerManager mTunerManager; private ApplicationLog mApplicationLog; private ResourceMonitor mResourceMonitor; - private JFXPanel mStatusPanel; + private JFXPanel mResourceStatusPanel; private String mTitle; @@ -211,10 +217,12 @@ public class SDRTrunk implements Listener MapService mapService = new MapService(mIconModel); mPlaylistManager.getChannelProcessingManager().addDecodeEventListener(mapService); + mNowPlayingDetailsVisible = mPreferences.getBoolean(PREFERENCE_NOW_PLAYING_DETAILS_VISIBLE, true); + if(!GraphicsEnvironment.isHeadless()) { mControllerPanel = new ControllerPanel(mPlaylistManager, audioPlaybackManager, mIconModel, mapService, - mSettingsManager, mTunerManager, mUserPreferences); + mSettingsManager, mTunerManager, mUserPreferences, mNowPlayingDetailsVisible); } mSpectralPanel = new SpectralDisplayPanel(mPlaylistManager, mSettingsManager, mTunerManager.getDiscoveredTunerModel()); @@ -321,7 +329,7 @@ public class SDRTrunk implements Listener */ private void initGUI() { - mMainGui.setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill][]")); + mMainGui.setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]0[shrink 0]")); /** * Setup main JFrame window @@ -377,7 +385,7 @@ public class SDRTrunk implements Listener mSplitPane.add(mSpectralPanel); mSplitPane.add(mControllerPanel); - mBroadcastStatusVisible = SystemProperties.getInstance().get(PROPERTY_BROADCAST_STATUS_VISIBLE, false); + mBroadcastStatusVisible = mPreferences.getBoolean(PREFERENCE_BROADCAST_STATUS_VISIBLE, false); //Show broadcast status panel when user requests - disabled by default if(mBroadcastStatusVisible) @@ -388,8 +396,11 @@ public class SDRTrunk implements Listener mMainGui.add(mSplitPane, "cell 0 0,span,grow"); mResourceMonitor.start(); - mStatusPanel = mJavaFxWindowManager.getStatusPanel(mResourceMonitor); - mMainGui.add(mStatusPanel, "span,growx"); + mResourceStatusVisible = mPreferences.getBoolean(PREFERENCE_RESOURCE_STATUS_VISIBLE, true); + if(mResourceStatusVisible) + { + mMainGui.add(getResourceStatusPanel(), "span,growx"); + } /** * Menu items @@ -519,7 +530,9 @@ public class SDRTrunk implements Listener viewMenu.add(new TunersMenu()); viewMenu.add(new JSeparator()); viewMenu.add(new DisableSpectrumWaterfallMenuItem(mSpectralPanel)); - viewMenu.add(new BroadcastStatusVisibleMenuItem(mControllerPanel)); + viewMenu.add(new NowPlayingChannelDetailsVisibleMenuItem()); + viewMenu.add(new BroadcastStatusVisibleMenuItem()); + viewMenu.add(new ResourceStatusVisibleMenuItem()); menuBar.add(viewMenu); @@ -621,7 +634,54 @@ public class SDRTrunk implements Listener mMainGui.revalidate(); }); - SystemProperties.getInstance().set(PROPERTY_BROADCAST_STATUS_VISIBLE, mBroadcastStatusVisible); + mPreferences.putBoolean(PREFERENCE_BROADCAST_STATUS_VISIBLE, mBroadcastStatusVisible); + } + + /** + * Lazy constructor for resource status panel + */ + private JFXPanel getResourceStatusPanel() + { + + if(mResourceStatusPanel == null) + { + mResourceStatusPanel = mJavaFxWindowManager.getStatusPanel(mResourceMonitor); + } + + return mResourceStatusPanel; + } + + /** + * Toggles visibility of the resource status panel at the bottom of the main UI window + */ + private void toggleResourceStatusPanelVisibility() + { + mResourceStatusVisible = !mResourceStatusVisible; + + EventQueue.invokeLater(() -> { + if(mResourceStatusVisible) + { + mMainGui.add(getResourceStatusPanel(), "span,growx"); + } + else + { + mMainGui.remove(getResourceStatusPanel()); + } + + mMainGui.revalidate(); + }); + + mPreferences.putBoolean(PREFERENCE_RESOURCE_STATUS_VISIBLE, mResourceStatusVisible); + } + + /** + * Toggles visibility of the Now Playing channel details panel + */ + private void toggleNowPlayingDetailsPanelVisibility() + { + mNowPlayingDetailsVisible = !mNowPlayingDetailsVisible; + mControllerPanel.getNowPlayingPanel().setDetailTabsVisible(mNowPlayingDetailsVisible); + mPreferences.putBoolean(PREFERENCE_NOW_PLAYING_DETAILS_VISIBLE, mNowPlayingDetailsVisible); } @@ -735,18 +795,15 @@ public class SDRTrunk implements Listener } } + /** + * Broadcast status panel visible toggle menu item + */ public class BroadcastStatusVisibleMenuItem extends JCheckBoxMenuItem { - private ControllerPanel mControllerPanel; - - public BroadcastStatusVisibleMenuItem(ControllerPanel controllerPanel) + public BroadcastStatusVisibleMenuItem() { super("Show Streaming Status"); - - mControllerPanel = controllerPanel; - - setSelected(mBroadcastStatusPanel != null); - + setSelected(mBroadcastStatusVisible); addActionListener(e -> { toggleBroadcastStatusPanelVisibility(); setSelected(mBroadcastStatusVisible); @@ -754,6 +811,38 @@ public class SDRTrunk implements Listener } } + /** + * Resource status panel visible toggle menu item + */ + public class ResourceStatusVisibleMenuItem extends JCheckBoxMenuItem + { + public ResourceStatusVisibleMenuItem() + { + super("Show Resource Status"); + setSelected(mResourceStatusVisible); + addActionListener(e -> { + toggleResourceStatusPanelVisibility(); + setSelected(mResourceStatusVisible); + }); + } + } + + /** + * Now Playing channel details visible toggle menu item + */ + public class NowPlayingChannelDetailsVisibleMenuItem extends JCheckBoxMenuItem + { + public NowPlayingChannelDetailsVisibleMenuItem() + { + super("Show Now Playing Channel Details"); + setSelected(mNowPlayingDetailsVisible); + addActionListener(e -> { + toggleNowPlayingDetailsPanelVisibility(); + setSelected(mNowPlayingDetailsVisible); + }); + } + } + public class TunersMenu extends JMenu { public TunersMenu() diff --git a/src/main/java/io/github/dsheirer/monitor/ResourceMonitor.java b/src/main/java/io/github/dsheirer/monitor/ResourceMonitor.java index 1ac8e425..996b87a5 100644 --- a/src/main/java/io/github/dsheirer/monitor/ResourceMonitor.java +++ b/src/main/java/io/github/dsheirer/monitor/ResourceMonitor.java @@ -31,8 +31,10 @@ import java.nio.file.Path; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.LongProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleLongProperty; import javafx.beans.property.SimpleStringProperty; @@ -64,6 +66,7 @@ public class ResourceMonitor private DoubleProperty mJavaMemoryUsedPercentage = new SimpleDoubleProperty(); private DoubleProperty mSystemMemoryUsedPercentage = new SimpleDoubleProperty(); private DoubleProperty mCpuPercentage = new SimpleDoubleProperty(); + private BooleanProperty mCpuAvailable = new SimpleBooleanProperty(); private DoubleProperty mDirectoryUsePercentEventLogs = new SimpleDoubleProperty(); private DoubleProperty mDirectoryUsePercentRecordings = new SimpleDoubleProperty(); private StringProperty mFileSizeEventLogs = new SimpleStringProperty(); @@ -143,7 +146,8 @@ public class ResourceMonitor mMemoryUsed.set(mMemoryAllocated.getValue() - Runtime.getRuntime().freeMemory()); mJavaMemoryUsedPercentage.set((double)mMemoryUsed.get() / (double)mMemoryAllocated.get()); mSystemMemoryUsedPercentage.set((double)mMemoryAllocated.get() / (double)mMemoryTotal.get()); - mCpuPercentage.set(loadFinal); + mCpuPercentage.set(loadFinal > 0 ? loadFinal : 0); + mCpuAvailable.set(loadFinal >= 0); }); } @@ -230,6 +234,15 @@ public class ResourceMonitor return mCpuPercentage; } + /** + * Indicates if the CPU usage percentage value is available on this operating system. + * @return false when the CPU load value is a negative value, indicating that the JVM for this OS doesn't support it. + */ + public BooleanProperty cpuAvailableProperty() + { + return mCpuAvailable; + } + /** * Property for total system memory */ diff --git a/src/main/java/io/github/dsheirer/monitor/StatusBox.java b/src/main/java/io/github/dsheirer/monitor/StatusBox.java index aec03e1e..1599b990 100644 --- a/src/main/java/io/github/dsheirer/monitor/StatusBox.java +++ b/src/main/java/io/github/dsheirer/monitor/StatusBox.java @@ -23,6 +23,7 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.ProgressBar; +import javafx.scene.control.Tooltip; import javafx.scene.layout.HBox; /** @@ -39,7 +40,7 @@ public class StatusBox extends HBox public StatusBox(ResourceMonitor resourceMonitor) { mResourceMonitor = resourceMonitor; - setPadding(new Insets(0, 0, 2, 0)); + setPadding(new Insets(1, 0, 1, 0)); setSpacing(6); Label cpuLabel = new Label("CPU:"); cpuLabel.setPadding(new Insets(0, 0, 0, 10)); @@ -48,6 +49,8 @@ public class StatusBox extends HBox ProgressBar cpuIndicator = new ProgressBar(); cpuIndicator.progressProperty().bind(mResourceMonitor.cpuPercentageProperty()); + cpuIndicator.disableProperty().bind(mResourceMonitor.cpuAvailableProperty().not()); + cpuIndicator.setTooltip(new Tooltip("Java process CPU usage. Disabled if the CPU loading is not available from the OS")); getChildren().add(cpuIndicator); Label memoryLabel = new Label("Allocated Memory:"); @@ -56,6 +59,7 @@ public class StatusBox extends HBox ProgressBar memoryBar = new ProgressBar(); memoryBar.progressProperty().bind(mResourceMonitor.systemMemoryUsedPercentageProperty()); + memoryBar.setTooltip(new Tooltip("Percentage of total system memory that Java has reserved from the Operating System.")); getChildren().add(memoryBar); Label javaMemoryLabel = new Label("Used Memory:"); @@ -64,6 +68,7 @@ public class StatusBox extends HBox ProgressBar javaMemoryBar = new ProgressBar(); javaMemoryBar.progressProperty().bind(mResourceMonitor.javaMemoryUsedPercentageProperty()); + javaMemoryBar.setTooltip(new Tooltip("Percentage of allocated memory that Java/sdrtrunk is currently using. This value fluctuates as Java manages memory and garbage collection")); getChildren().add(javaMemoryBar); Label eventLogsLabel = new Label("Event Logs:"); @@ -72,6 +77,7 @@ public class StatusBox extends HBox ProgressBar eventLogsBar = new ProgressBar(); eventLogsBar.progressProperty().bind(mResourceMonitor.directoryUsePercentEventLogsProperty()); + eventLogsBar.setTooltip(new Tooltip("Percentage of drive space used for event logs based on user-specified max threshold in user preferences")); getChildren().add(eventLogsBar); Label eventLogsSizeLabel = new Label(); @@ -86,6 +92,7 @@ public class StatusBox extends HBox ProgressBar recordingsBar = new ProgressBar(); recordingsBar.progressProperty().bind(mResourceMonitor.directoryUsePercentRecordingsProperty()); + recordingsBar.setTooltip(new Tooltip("Percentage of drive space used for recordings based on user-specified max threshold in user preferences")); getChildren().add(recordingsBar); Label recordingsSizeLabel = new Label(); diff --git a/src/main/java/io/github/dsheirer/monitor/StatusPanel.java b/src/main/java/io/github/dsheirer/monitor/StatusPanel.java deleted file mode 100644 index 8cc68187..00000000 --- a/src/main/java/io/github/dsheirer/monitor/StatusPanel.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * ***************************************************************************** - * Copyright (C) 2014-2023 Dennis Sheirer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * **************************************************************************** - */ - -package io.github.dsheirer.monitor; - -import javafx.embed.swing.JFXPanel; - -/** - * Status panel providing resource usage statistics. - */ -public class StatusPanel extends JFXPanel -{ - private ResourceMonitor mResourceMonitor; - - public StatusPanel(ResourceMonitor resourceMonitor) - { - mResourceMonitor = resourceMonitor; - } - - -}