Files
sdrtrunk/src/main/java/io/github/dsheirer/gui/SDRTrunk.java
Denny Sheirer 0eec2e7ab8 Updates complex decimation filters to use either scalar or vector optimal implementation. (#1148)
Updates HackRF native byte buffers with automatic DC removal.
Creates new half-band channel source to replace the cic-based channel source in the heterodyne channelizer.  Resolves issue in RTL native buffer processing.  Creates a CACHED thread pool and reduces the number of threads for the SCHEDULED thread pool to 2.  Changes each of the channel source implementations to use a dedicated thread (Dispatcher class) instead of the scheduled thread pool, to reduce the amount of buffered/retained samples objects in memory. Lots of other small changes and tweaks.
Removes last of Reusable buffer code.  Updates DFTProcessor to use a single thread scheduled executor.  Updates Polyphase channelizer to use threads instead of an executor.  Removes a number of unused classes.
Commenting out window calibration.  Scalar is faster in both implementations.
Adds vector and scalar implementations for windows.  Repackages window and window type classes.
WIP adds vector implementations for Airspy non-interleaved buffer iterator and calibration plugins for both interleaved and non-interleaved buffer iterators.
WIP adds vector implementations for Airspy interleaved buffer iterator.
Work on developing scalar and vector implementations of airspy sample conversion and airspy native buffer processing.
Introduces new INativeBuffer framework where tuner raw samples are retained in their native (compressed) format until when they're needed and they are converted on-demand into smaller complex sample buffers to aid garbage collection.
Removes all usage of ReusableComplexBuffers and ReusableChannelREsultsBuffers.  Updates polyphase channelizer to be more efficient in output channel processing.
Updates ViterbiDecoder to change Map usage to array for memory efficiency.
Updates channel processors to used scalar implementation for gain and mixer, until the polyphase channel results can be fixed to be a multiple of the SIMD lane width.
Resolves final changes for the conversion from interleaved sample arrays to complexsamples buffers for distributing baseband samples to channels and processing chains.
Updates tuner channel sources and all decoders to receive new complex samples buffers, removing the previous reusable complex buffers as the default baseband samples buffer object.
Still to resolve is the ComplexMixer implementations.  Needs validation and a calibration implementation with an update to the complex mixer factory.
Updates the DMR decoder to use optimal (scalar vs vector) implementations of filter and AGC controls.
Updates the AM demodulator for separated I&Q float sample processing.
Code review and testing changes.
updates build script for runtime configuration.
Integrates calibration manager with main sdrtrunk application.  Updates NBFMDecoder to use new optimized FIR and Decimation filter options.
Updates real half-band decimation filters to select the optimal (scalar vs vector) implementation.
Adds calibration manager plugins.
Adds calibration manager and plugins to determine the optimal (scalar vs vector) implementation type to use for various classes of dsp components.
Adds vector squelching FM demodulator and cleans up the IIR filter and power squelch to use all floats.
Adds vector implementation of FM demodulator.  This introduces a new interface that accepts i/q samples as two separate float arrays, versus an interleaved array, which enables more efficient processing.  This new complex sample interface will be used across the codebase eventually.
Adds ComplexOscillator and a SIMD version VectorComplexOscillator.  Deprecates the LowPhaseNoiseOscillator because the ComplexOscillator is about 35% faster.  The vectorized version is about 43% faster when SIMD lane width is 8x.
Adds new vector complex filters and new real oscillators.  Note: the RealOscillator using straight Java math sin() function is 3x slower than the existing legacy implementation.  Will replace this with a complex oscillator that only uses the real part.
Updates ComplexFIRFilter to be 50% more efficient.
Adds 23 tap filter implementations.  Will not add 63-tap implementations because they are slower than the default vector implementation.
Adds 15-tap customized vector half band filters.
Adds initial custom complex vector half-band decimation filters for 11-tap and 15-tap decimation filters..
Adds vector complex half band filter implementations.  Note: performance of the generic 64-bit 2-lane filter is horrible and is not included.
Optimizes complex half-band filter to be 27% more efficient.
Implements vectorized real half-band filter implementations.
Implements a new FIR filter class that processes sample buffers instead of single sample at a time.
Refactored all of the calibratons to be time-duration based.  Testing on windows i7 gen 3 CPU showed that it took 1000x longer for one of the calibrations to run, versus my AMD 5800x.
Changed gradle jvm args so that the windows plaf export is always applied.
Updates gradle build script to ensure incubator module is included and to cleanup javafx library management for building runtime images.
Testing tweaks and changes.
Adds logging limiter to dispatcher for buffer overflows
Adds User Preferences editor for performing Vector/SIMD calibrations and new startup dialog prompting the user to perform calibrations.
Updates calibration manager and all calibration implementations to add warm-up and test phases and to reduce the overall time it takes to calibrate a system.

Co-authored-by: Dennis Sheirer <dsheirer@github.com>
2022-03-04 08:45:05 -05:00

648 lines
23 KiB
Java

/*
* *****************************************************************************
* Copyright (C) 2014-2022 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 <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
package io.github.dsheirer.gui;
import com.jidesoft.plaf.LookAndFeelFactory;
import com.jidesoft.swing.JideSplitPane;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.DuplicateCallDetector;
import io.github.dsheirer.audio.broadcast.AudioStreamingManager;
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastStatusPanel;
import io.github.dsheirer.audio.playback.AudioPlaybackManager;
import io.github.dsheirer.controller.ControllerPanel;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.controller.channel.ChannelAutoStartFrame;
import io.github.dsheirer.controller.channel.ChannelSelectionManager;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.icon.ViewIconManagerRequest;
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
import io.github.dsheirer.gui.preference.CalibrateRequest;
import io.github.dsheirer.gui.preference.PreferenceEditorType;
import io.github.dsheirer.gui.preference.ViewUserPreferenceEditorRequest;
import io.github.dsheirer.gui.preference.calibration.CalibrationDialog;
import io.github.dsheirer.icon.IconModel;
import io.github.dsheirer.log.ApplicationLog;
import io.github.dsheirer.map.MapService;
import io.github.dsheirer.module.log.EventLogManager;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.properties.SystemProperties;
import io.github.dsheirer.record.AudioRecordingManager;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.SourceManager;
import io.github.dsheirer.source.tuner.Tuner;
import io.github.dsheirer.source.tuner.TunerEvent;
import io.github.dsheirer.source.tuner.TunerModel;
import io.github.dsheirer.source.tuner.TunerSpectralDisplayManager;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
import io.github.dsheirer.spectrum.ClearTunerMenuItem;
import io.github.dsheirer.spectrum.ShowTunerMenuItem;
import io.github.dsheirer.spectrum.SpectralDisplayPanel;
import io.github.dsheirer.util.ThreadPool;
import io.github.dsheirer.util.TimeStamp;
import io.github.dsheirer.vector.calibrate.CalibrationManager;
import javafx.application.Platform;
import javafx.scene.control.ButtonType;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.swing.IconFontSwing;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.plaf.metal.MetalLookAndFeel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
public class SDRTrunk implements Listener<TunerEvent>
{
private final static Logger mLog = LoggerFactory.getLogger(SDRTrunk.class);
private static final String PROPERTY_BROADCAST_STATUS_VISIBLE = "main.broadcast.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 AudioRecordingManager mAudioRecordingManager;
private AudioStreamingManager mAudioStreamingManager;
private BroadcastStatusPanel mBroadcastStatusPanel;
private ControllerPanel mControllerPanel;
private IconModel mIconModel = new IconModel();
private PlaylistManager mPlaylistManager;
private SourceManager mSourceManager;
private SettingsManager mSettingsManager;
private SpectralDisplayPanel mSpectralPanel;
private JFrame mMainGui = new JFrame();
private JideSplitPane mSplitPane;
private JavaFxWindowManager mJavaFxWindowManager;
private UserPreferences mUserPreferences = new UserPreferences();
private ApplicationLog mApplicationLog;
private String mTitle;
public SDRTrunk()
{
mApplicationLog = new ApplicationLog(mUserPreferences);
mApplicationLog.start();
String operatingSystem = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH);
if(operatingSystem.contains("mac") || operatingSystem.contains("nux"))
{
try
{
UIManager.setLookAndFeel(MetalLookAndFeel.class.getName());
LookAndFeelFactory.installJideExtension();
}
catch(Exception e)
{
mLog.error("Error trying to set Metal look and feel for OS [" + operatingSystem + "]");
}
}
//Setup the application home directory
Path home = getHomePath();
ThreadPool.logSettings();
mLog.info("Home path: " + home.toString());
//Load properties file
if(home != null)
{
loadProperties(home);
}
//Log current properties setting
SystemProperties.getInstance().logCurrentSettings();
//Register FontAwesome so we can use the fonts in Swing windows
IconFontSwing.register(FontAwesome.getIconFont());
TunerConfigurationModel tunerConfigurationModel = new TunerConfigurationModel();
TunerModel tunerModel = new TunerModel(tunerConfigurationModel);
mSettingsManager = new SettingsManager(tunerConfigurationModel);
mSourceManager = new SourceManager(tunerModel, mSettingsManager, mUserPreferences);
AliasModel aliasModel = new AliasModel();
EventLogManager eventLogManager = new EventLogManager(aliasModel, mUserPreferences);
mPlaylistManager = new PlaylistManager(mUserPreferences, mSourceManager, aliasModel, eventLogManager, mIconModel);
mJavaFxWindowManager = new JavaFxWindowManager(mUserPreferences, mPlaylistManager);
CalibrationManager calibrationManager = CalibrationManager.getInstance(mUserPreferences);
final boolean calibrating = !calibrationManager.isCalibrated() &&
!mUserPreferences.getVectorCalibrationPreference().isHideCalibrationDialog();
new ChannelSelectionManager(mPlaylistManager.getChannelModel());
AudioPlaybackManager audioPlaybackManager = new AudioPlaybackManager(mUserPreferences);
mAudioRecordingManager = new AudioRecordingManager(mUserPreferences);
mAudioRecordingManager.start();
mAudioStreamingManager = new AudioStreamingManager(mPlaylistManager.getBroadcastModel(), BroadcastFormat.MP3,
mUserPreferences);
mAudioStreamingManager.start();
DuplicateCallDetector duplicateCallDetector = new DuplicateCallDetector(mUserPreferences);
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(duplicateCallDetector);
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(audioPlaybackManager);
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(mAudioRecordingManager);
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(mAudioStreamingManager);
MapService mapService = new MapService(mIconModel);
mPlaylistManager.getChannelProcessingManager().addDecodeEventListener(mapService);
mControllerPanel = new ControllerPanel(mPlaylistManager, audioPlaybackManager, mIconModel, mapService,
mSettingsManager, mSourceManager, mUserPreferences);
mSpectralPanel = new SpectralDisplayPanel(mPlaylistManager, mSettingsManager, tunerModel);
TunerSpectralDisplayManager tunerSpectralDisplayManager = new TunerSpectralDisplayManager(mSpectralPanel,
mPlaylistManager, mSettingsManager, tunerModel);
tunerModel.addListener(tunerSpectralDisplayManager);
tunerModel.addListener(this);
mPlaylistManager.init();
mLog.info("starting main application gui");
//Initialize the GUI
initGUI();
tunerModel.requestFirstTunerDisplay();
//Start the gui
EventQueue.invokeLater(() -> {
try
{
mMainGui.setVisible(true);
if(calibrating)
{
Platform.runLater(() ->
{
CalibrationDialog calibrationDialog = mJavaFxWindowManager.getCalibrationDialog(mUserPreferences);
Optional<ButtonType> calibrate = calibrationDialog.showAndWait();
if(calibrate.isPresent() && calibrate.get().getText().equals("Calibrate"))
{
//Request focus and execute calibration
MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.VECTOR_CALIBRATION));
MyEventBus.getGlobalEventBus().post(new CalibrateRequest());
}
else
{
autoStartChannels();
}
});
}
else
{
autoStartChannels();
}
}
catch(Exception e)
{
e.printStackTrace();
}
});
}
/**
* Shows a dialog that lists the channels that have been designated for auto-start, sorted by auto-start order and
* allows the user to start now, cancel, or allow the timer to expire and then start the channels. The dialog will
* only show if there are one ore more channels designated for auto-start.
*/
private void autoStartChannels()
{
List<Channel> channels = mPlaylistManager.getChannelModel().getAutoStartChannels();
if(channels.size() > 0)
{
ChannelAutoStartFrame autoStartFrame = new ChannelAutoStartFrame(mPlaylistManager.getChannelProcessingManager(),
channels);
}
}
/**
* Initialize the contents of the frame.
*/
private void initGUI()
{
mMainGui.setLayout(new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]"));
/**
* Setup main JFrame window
*/
mTitle = SystemProperties.getInstance().getApplicationName();
mMainGui.setTitle(mTitle);
Point location = mUserPreferences.getSwingPreference().getLocation(WINDOW_FRAME_IDENTIFIER);
if(location != null)
{
mMainGui.setLocation(location);
}
else
{
mMainGui.setLocationRelativeTo(null);
}
mMainGui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
mMainGui.addWindowListener(new ShutdownMonitor());
Dimension dimension = mUserPreferences.getSwingPreference().getDimension(WINDOW_FRAME_IDENTIFIER);
mSpectralPanel.setPreferredSize(new Dimension(1280, 300));
mControllerPanel.setPreferredSize(new Dimension(1280, 500));
if(dimension != null)
{
Dimension spectral = mUserPreferences.getSwingPreference().getDimension(SPECTRAL_PANEL_IDENTIFIER);
if(spectral != null)
{
mSpectralPanel.setSize(spectral);
}
Dimension controller = mUserPreferences.getSwingPreference().getDimension(CONTROLLER_PANEL_IDENTIFIER);
if(controller != null)
{
mControllerPanel.setSize(controller);
}
mMainGui.setSize(dimension);
if(mUserPreferences.getSwingPreference().getMaximized(WINDOW_FRAME_IDENTIFIER, false))
{
mMainGui.setExtendedState(JFrame.MAXIMIZED_BOTH);
}
}
else
{
mMainGui.setSize(new Dimension(1280, 800));
}
mSplitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
mSplitPane.setDividerSize(5);
mSplitPane.add(mSpectralPanel);
mSplitPane.add(mControllerPanel);
mBroadcastStatusVisible = SystemProperties.getInstance().get(PROPERTY_BROADCAST_STATUS_VISIBLE, false);
//Show broadcast status panel when user requests - disabled by default
if(mBroadcastStatusVisible)
{
mSplitPane.add(getBroadcastStatusPanel());
}
mMainGui.add(mSplitPane, "cell 0 0,span,grow");
/**
* Menu items
*/
JMenuBar menuBar = new JMenuBar();
mMainGui.setJMenuBar(menuBar);
JMenu fileMenu = new JMenu("File");
menuBar.add(fileMenu);
JMenuItem exitMenu = new JMenuItem("Exit");
exitMenu.addActionListener(
new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
processShutdown();
System.exit(0);
}
}
);
fileMenu.add(exitMenu);
JMenu viewMenu = new JMenu("View");
JMenuItem viewPlaylistItem = new JMenuItem("Playlist Editor");
viewPlaylistItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewPlaylistRequest()));
viewMenu.add(viewPlaylistItem);
JMenuItem preferencesItem = new JMenuItem("User Preferences");
preferencesItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest()));
viewMenu.add(preferencesItem);
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
settingsMenu.addActionListener(arg0 -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
viewMenu.add(settingsMenu);
JMenuItem logFilesMenu = new JMenuItem("Logs & Recordings");
logFilesMenu.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent arg0)
{
try
{
Desktop.getDesktop().open(getHomePath().toFile());
}
catch(Exception e)
{
mLog.error("Couldn't open file explorer");
JOptionPane.showMessageDialog(mMainGui,
"Can't launch file explorer - files are located at: " +
getHomePath().toString(),
"Can't launch file explorer",
JOptionPane.ERROR_MESSAGE);
}
}
});
viewMenu.add(logFilesMenu);
viewMenu.add(new JSeparator());
viewMenu.add(new TunersMenu());
viewMenu.add(new JSeparator());
viewMenu.add(new ClearTunerMenuItem(mSpectralPanel));
viewMenu.add(new BroadcastStatusVisibleMenuItem(mControllerPanel));
menuBar.add(viewMenu);
JMenuItem screenCaptureItem = new JMenuItem("Screen Capture");
screenCaptureItem.setMnemonic(KeyEvent.VK_C);
screenCaptureItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.ALT_MASK));
screenCaptureItem.setMaximumSize(screenCaptureItem.getPreferredSize());
screenCaptureItem.addActionListener(arg0 -> {
try
{
Robot robot = new Robot();
final BufferedImage image = robot.createScreenCapture(mMainGui.getBounds());
String filename = TimeStamp.getTimeStamp("_") + "_screen_capture.png";
final Path captureFile = mUserPreferences.getDirectoryPreference().getDirectoryScreenCapture().resolve(filename);
EventQueue.invokeLater(() -> {
try
{
ImageIO.write(image, "png", captureFile.toFile());
}
catch(IOException e)
{
mLog.error("Couldn't write screen capture to file [" + captureFile.toString() + "]", e);
}
});
}
catch(AWTException e)
{
mLog.error("Exception while taking screen capture", e);
}
});
menuBar.add(screenCaptureItem);
}
/**
* Performs shutdown operations
*/
private void processShutdown()
{
mLog.info("Application shutdown started ...");
mUserPreferences.getSwingPreference().setLocation(WINDOW_FRAME_IDENTIFIER, mMainGui.getLocation());
mUserPreferences.getSwingPreference().setDimension(WINDOW_FRAME_IDENTIFIER, mMainGui.getSize());
mUserPreferences.getSwingPreference().setMaximized(WINDOW_FRAME_IDENTIFIER,
(mMainGui.getExtendedState() & JFrame.MAXIMIZED_BOTH) == JFrame.MAXIMIZED_BOTH);
mUserPreferences.getSwingPreference().setDimension(SPECTRAL_PANEL_IDENTIFIER, mSpectralPanel.getSize());
mUserPreferences.getSwingPreference().setDimension(CONTROLLER_PANEL_IDENTIFIER, mControllerPanel.getSize());
mJavaFxWindowManager.shutdown();
mLog.info("Stopping channels ...");
mPlaylistManager.getChannelProcessingManager().shutdown();
mAudioRecordingManager.stop();
mLog.info("Stopping spectral display ...");
mSpectralPanel.clearTuner();
mSourceManager.shutdown();
mLog.info("Shutdown complete.");
mApplicationLog.stop();
}
/**
* Lazy constructor for broadcast status panel
*/
private BroadcastStatusPanel getBroadcastStatusPanel()
{
if(mBroadcastStatusPanel == null)
{
mBroadcastStatusPanel = new BroadcastStatusPanel(mPlaylistManager.getBroadcastModel(), mUserPreferences,
"application.broadcast.status.panel");
mBroadcastStatusPanel.setPreferredSize(new Dimension(880, 70));
mBroadcastStatusPanel.getTable().setEnabled(false);
}
return mBroadcastStatusPanel;
}
/**
* Toggles visibility of the broadcast channels status panel at the bottom of the controller panel
*/
private void toggleBroadcastStatusPanelVisibility()
{
mBroadcastStatusVisible = !mBroadcastStatusVisible;
EventQueue.invokeLater(() -> {
if(mBroadcastStatusVisible)
{
mSplitPane.add(getBroadcastStatusPanel());
}
else
{
mSplitPane.remove(getBroadcastStatusPanel());
}
mMainGui.revalidate();
});
SystemProperties.getInstance().set(PROPERTY_BROADCAST_STATUS_VISIBLE, mBroadcastStatusVisible);
}
/**
* Loads the application properties file from the user's home directory,
* creating the properties file for the first-time, if necessary
*/
private void loadProperties(Path homePath)
{
Path propsPath = homePath.resolve("SDRTrunk.properties");
if(!Files.exists(propsPath))
{
try
{
mLog.info("SDRTrunk - creating application properties file [" +
propsPath.toAbsolutePath() + "]");
Files.createFile(propsPath);
}
catch(IOException e)
{
mLog.error("SDRTrunk - couldn't create application properties "
+ "file [" + propsPath.toAbsolutePath(), e);
}
}
if(Files.exists(propsPath))
{
SystemProperties.getInstance().load(propsPath);
}
else
{
mLog.error("SDRTrunk - couldn't find or recreate the SDRTrunk " +
"application properties file");
}
}
/**
* Gets (or creates) the SDRTRunk application home directory.
*
* Note: the user can change this setting to allow log files and other
* files to reside elsewhere on the file system.
*/
private Path getHomePath()
{
Path homePath = FileSystems.getDefault()
.getPath(System.getProperty("user.home"), "SDRTrunk");
if(!Files.exists(homePath))
{
try
{
Files.createDirectory(homePath);
mLog.info("SDRTrunk - created application home directory [" +
homePath.toString() + "]");
}
catch(Exception e)
{
homePath = null;
mLog.error("SDRTrunk: exception while creating SDRTrunk home " +
"directory in the user's home directory", e);
}
}
return homePath;
}
@Override
public void receive(TunerEvent event)
{
if(event.getEvent() == TunerEvent.Event.REQUEST_MAIN_SPECTRAL_DISPLAY)
{
mMainGui.setTitle(mTitle + " - " + event.getTuner().getName());
}
}
public class ShutdownMonitor extends WindowAdapter
{
@Override
public void windowClosing(WindowEvent e)
{
processShutdown();
}
}
public class BroadcastStatusVisibleMenuItem extends JCheckBoxMenuItem
{
private ControllerPanel mControllerPanel;
public BroadcastStatusVisibleMenuItem(ControllerPanel controllerPanel)
{
super("Show Streaming Status");
mControllerPanel = controllerPanel;
setSelected(mBroadcastStatusPanel != null);
addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
toggleBroadcastStatusPanelVisibility();
setSelected(mBroadcastStatusVisible);
}
});
}
}
public class TunersMenu extends JMenu
{
public TunersMenu()
{
super("Tuners");
addMenuListener(new MenuListener()
{
@Override
public void menuSelected(MenuEvent e)
{
removeAll();
for(Tuner tuner : mSourceManager.getTunerModel().getTuners())
{
add(new ShowTunerMenuItem(mSourceManager.getTunerModel(), tuner));
}
}
@Override
public void menuDeselected(MenuEvent e) { }
@Override
public void menuCanceled(MenuEvent e) { }
});
}
}
/**
* Launch the application.
*/
public static void main(String[] args)
{
new SDRTrunk();
}
}