#5 Playlist Editor With Radio Reference Support (#760)

* WIP - Adds support for aliasing P25 Phase 2 tones.Playlist editor - adds editor support for alias actions

* Playlist editor implementation and integration into application is complete. Adds JavaFX window monitoring to persist window size/location across application runs.

Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
Denny Sheirer
2020-04-26 11:53:43 +00:00
committed by GitHub
parent 39f6d6c831
commit cc52ae5bd7
290 changed files with 31251 additions and 15306 deletions

View File

@@ -1,29 +1,54 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 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.
* * ******************************************************************************
* * Copyright (C) 2014-2020 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/>
* * *****************************************************************************
*
* 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.google.common.eventbus.Subscribe;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.controller.channel.map.ChannelMap;
import io.github.dsheirer.controller.channel.map.ChannelRange;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.preference.PreferenceEditorViewRequest;
import io.github.dsheirer.gui.preference.PreferencesEditor;
import io.github.dsheirer.gui.playlist.PlaylistEditor;
import io.github.dsheirer.gui.playlist.PlaylistEditorRequest;
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
import io.github.dsheirer.gui.playlist.channelMap.ChannelMapEditor;
import io.github.dsheirer.gui.playlist.channelMap.ChannelMapEditorViewRequest;
import io.github.dsheirer.gui.preference.PreferenceEditorType;
import io.github.dsheirer.gui.preference.UserPreferenceEditorViewRequest;
import io.github.dsheirer.gui.preference.UserPreferencesEditor;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.module.log.EventLogManager;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.SourceManager;
import io.github.dsheirer.source.tuner.TunerModel;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.stage.Stage;
import jiconfont.javafx.IconFontFX;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -31,68 +56,325 @@ import org.slf4j.LoggerFactory;
* Java FX window manager. Handles all secondary Java FX windows that are used within this primarily
* Swing application.
*/
public class JavaFxWindowManager
public class JavaFxWindowManager extends Application
{
private final static Logger mLog = LoggerFactory.getLogger(JavaFxWindowManager.class);
private UserPreferences mUserPreferences;
private PreferencesEditor mPreferencesEditor;
public static final String CHANNEL_MAP_EDITOR = "channelmap";
public static final String PLAYLIST_EDITOR = "playlist";
public static final String USER_PREFERENCES_EDITOR = "preferences";
public static final String STAGE_MONITOR_KEY_CHANNEL_MAP_EDITOR = "channel.map";
public static final String STAGE_MONITOR_KEY_PLAYLIST_EDITOR = "playlist";
public static final String STAGE_MONITOR_KEY_USER_PREFERENCES_EDITOR = "user.preferences";
public JavaFxWindowManager(UserPreferences userPreferences)
private JFXPanel mJFXPanel;
private PlaylistManager mPlaylistManager;
private ChannelMapEditor mChannelMapEditor;
private PlaylistEditor mPlaylistEditor;
private UserPreferences mUserPreferences;
private UserPreferencesEditor mUserPreferencesEditor;
private Stage mChannelMapStage;
private Stage mPlaylistStage;
private Stage mUserPreferencesStage;
/**
* Constructs an instance. Note: this constructor is used for Swing applications.
*/
public JavaFxWindowManager(UserPreferences userPreferences, PlaylistManager playlistManager)
{
mUserPreferences = userPreferences;
mPlaylistManager = playlistManager;
//Register this class to receive events via each method annotated with @Subscribe
MyEventBus.getEventBus().register(this);
setup();
}
/**
* Closes all JavaFX windows and shuts down the FX thread
* Constructs an instance. Note: this constructor is used for standalone JavaFX application testing
*/
public void shutdown()
public JavaFxWindowManager()
{
Platform.exit();
mUserPreferences = new UserPreferences();
AliasModel aliasModel = new AliasModel();
TunerConfigurationModel tunerConfigurationModel = new TunerConfigurationModel();
TunerModel tunerModel = new TunerModel(tunerConfigurationModel);
SourceManager sourceManager = new SourceManager(tunerModel, new SettingsManager(tunerConfigurationModel),
mUserPreferences);
EventLogManager eventLogManager = new EventLogManager(aliasModel, mUserPreferences);
mPlaylistManager = new PlaylistManager(mUserPreferences, sourceManager, aliasModel, eventLogManager, new IconManager());
mPlaylistManager.init();
setup();
}
@Subscribe
public void process(final PreferenceEditorViewRequest request)
private void setup()
{
if(mPreferencesEditor == null)
{
new JFXPanel();
Platform.setImplicitExit(false);
Platform.runLater(() -> {
try
{
mPreferencesEditor = new PreferencesEditor(mUserPreferences);
//Register this class to receive events via each method annotated with @Subscribe
MyEventBus.getEventBus().register(this);
Stage stage = new Stage();
stage.setOnHidden(event -> mPreferencesEditor = null);
mPreferencesEditor.start(stage);
mPreferencesEditor.showEditor(request);
}
catch(Throwable e)
{
mLog.error("Error launching user preferences window", e);
}
});
//Register JavaFX icon fonts
IconFontFX.register(jiconfont.icons.font_awesome.FontAwesome.getIconFont());
createJFXPanel();
}
/**
* Executes the runnable on the JavaFX application thread
*/
private void execute(Runnable runnable)
{
createJFXPanel();
if(Platform.isFxApplicationThread())
{
runnable.run();
}
else
{
Platform.runLater(() -> {
try
{
Stage stage = mPreferencesEditor.getStage();
stage.show();
stage.toFront();
stage.requestFocus();
mPreferencesEditor.showEditor(request);
}
catch(Throwable t)
{
mLog.error("Error showing existing preferences editor window", t);
}
});
Platform.runLater(runnable);
}
}
/**
* Creates a JavaFX panel for Swing application compatibility
*/
private void createJFXPanel()
{
if(mJFXPanel == null)
{
mJFXPanel = new JFXPanel();
Platform.setImplicitExit(false);
}
}
/**
* Removes monitoring for all JavaFX stages and shuts down the FX thread, killing all FX windows.
*/
public void shutdown()
{
if(mPlaylistStage != null)
{
mUserPreferences.getJavaFxPreferences().unmonitor(mPlaylistStage);
}
if(mUserPreferencesStage != null)
{
mUserPreferences.getJavaFxPreferences().unmonitor(mUserPreferencesStage);
}
if(mChannelMapStage != null)
{
mUserPreferences.getJavaFxPreferences().unmonitor(mChannelMapStage);
}
Platform.exit();
}
/**
* Lazy construct and access the playlist editor
*/
public PlaylistEditor getPlaylistEditor()
{
if(mPlaylistEditor == null)
{
mPlaylistEditor = new PlaylistEditor(mPlaylistManager, mUserPreferences);
}
return mPlaylistEditor;
}
/**
* Access the playlist stage.
*/
private Stage getPlaylistStage()
{
if(mPlaylistStage == null)
{
createJFXPanel();
Scene scene = new Scene(getPlaylistEditor(), 1000, 750);
mPlaylistStage = new Stage();
mPlaylistStage.setTitle("sdrtrunk - Playlist Editor");
mPlaylistStage.setScene(scene);
mUserPreferences.getJavaFxPreferences().monitor(mPlaylistStage, STAGE_MONITOR_KEY_PLAYLIST_EDITOR);
}
return mPlaylistStage;
}
/**
* Processes a playlist editor request and brings the playlist editor into focus
*/
@Subscribe
public void process(PlaylistEditorRequest request)
{
execute(() -> {
try
{
getPlaylistStage().show();
getPlaylistStage().requestFocus();
getPlaylistStage().toFront();
getPlaylistEditor().process(request);
}
catch(Throwable t)
{
mLog.error("Error processing show playlist editor request", t);
}
});
}
/**
* User Preferences Editor
*/
private UserPreferencesEditor getUserPreferencesEditor()
{
if(mUserPreferencesEditor == null)
{
mUserPreferencesEditor = new UserPreferencesEditor(mUserPreferences);
}
return mUserPreferencesEditor;
}
/**
* User Preferences Stage
*/
private Stage getUserPreferencesStage()
{
if(mUserPreferencesStage == null)
{
createJFXPanel();
Scene scene = new Scene(getUserPreferencesEditor(), 900, 500);
mUserPreferencesStage = new Stage();
mUserPreferencesStage.setTitle("sdrtrunk - User Preferences");
mUserPreferencesStage.setScene(scene);
mUserPreferences.getJavaFxPreferences().monitor(mUserPreferencesStage, STAGE_MONITOR_KEY_USER_PREFERENCES_EDITOR);
}
return mUserPreferencesStage;
}
/**
* Processes a user preferences editor request
*/
@Subscribe
public void process(final UserPreferenceEditorViewRequest request)
{
execute(() -> {
getUserPreferencesStage().show();
getUserPreferencesStage().requestFocus();
getUserPreferencesStage().toFront();
getUserPreferencesEditor().process(request);
});
}
/**
* Channel Map Editor
*/
private ChannelMapEditor getChannelMapEditor()
{
if(mChannelMapEditor == null)
{
mChannelMapEditor = new ChannelMapEditor(mPlaylistManager.getChannelMapModel());
}
return mChannelMapEditor;
}
/**
* Channel Map Stage
*/
private Stage getChannelMapStage()
{
if(mChannelMapStage == null)
{
createJFXPanel();
Scene scene = new Scene(getChannelMapEditor(), 500, 500);
mChannelMapStage = new Stage();
mChannelMapStage.setTitle("sdrtrunk - Channel Map Editor");
mChannelMapStage.setScene(scene);
mUserPreferences.getJavaFxPreferences().monitor(mChannelMapStage, STAGE_MONITOR_KEY_CHANNEL_MAP_EDITOR);
}
return mChannelMapStage;
}
/**
* Process a channel map editor request
*/
@Subscribe
public void process(final ChannelMapEditorViewRequest request)
{
execute(() -> {
getChannelMapStage().show();
getChannelMapStage().requestFocus();
getChannelMapStage().toFront();
getChannelMapEditor().process(request);
});
}
@Override
public void start(Stage primaryStage) throws Exception
{
mLog.debug("Starting ...");
Parameters parameters = getParameters();
mLog.debug("Parameters: " + (parameters != null));
boolean valid = false;
if(parameters != null && parameters.getRaw().size() == 1)
{
String window = parameters.getRaw().get(0);
if(window != null)
{
switch(window)
{
case CHANNEL_MAP_EDITOR:
//Generate some test data for the editor
ChannelMap channelMap1 = new ChannelMap("Test Map 1");
channelMap1.addRange(new ChannelRange(1,199,150000000, 12500));
channelMap1.addRange(new ChannelRange(200,299,160000000, 25000));
channelMap1.addRange(new ChannelRange(300,399,170000000, 12500));
channelMap1.addRange(new ChannelRange(400,499,180000000, 25000));
mPlaylistManager.getChannelMapModel().addChannelMap(channelMap1);
ChannelMap channelMap2 = new ChannelMap("Test Map 2");
channelMap2.addRange(new ChannelRange(1,199,450000000, 12500));
channelMap2.addRange(new ChannelRange(200,299,460000000, 25000));
channelMap2.addRange(new ChannelRange(300,399,470000000, 12500));
channelMap2.addRange(new ChannelRange(400,499,480000000, 25000));
mPlaylistManager.getChannelMapModel().addChannelMap(channelMap2);
valid = true;
process(new ChannelMapEditorViewRequest());
break;
case PLAYLIST_EDITOR:
valid = true;
process(new ViewPlaylistRequest());
break;
case USER_PREFERENCES_EDITOR:
valid = true;
process(new UserPreferenceEditorViewRequest(PreferenceEditorType.DEFAULT));
break;
default:
break;
}
}
}
if(!valid)
{
StringBuilder sb = new StringBuilder();
sb.append("An argument is required to launch JavaFX windows from this window manager. " +
"Valid options are:\n\tplaylist\tPlaylist Editor\n\tpreferences\tUser Preferences Editor\n\tchannelmap\t" +
"Channel Map Editor\n");
sb.append("Supplied Argument(s): ").append(parameters.getRaw());
mLog.error(sb.toString());
}
}
public static void main(String[] args)
{
mLog.info("Application Start - Parameters: " + args);
launch(args);
}
}

View File

@@ -23,19 +23,15 @@ import com.jidesoft.swing.JideSplitPane;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.AudioStreamingManager;
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
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.ChannelModel;
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
import io.github.dsheirer.controller.channel.ChannelSelectionManager;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.preference.PreferenceEditorType;
import io.github.dsheirer.gui.preference.PreferenceEditorViewRequest;
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
import io.github.dsheirer.gui.preference.UserPreferenceEditorViewRequest;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.log.ApplicationLog;
import io.github.dsheirer.map.MapService;
@@ -58,7 +54,6 @@ import io.github.dsheirer.spectrum.SpectralDisplayPanel;
import io.github.dsheirer.util.ThreadPool;
import io.github.dsheirer.util.TimeStamp;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconFontFX;
import jiconfont.swing.IconFontSwing;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
@@ -109,12 +104,10 @@ public class SDRTrunk implements Listener<TunerEvent>
private boolean mBroadcastStatusVisible;
private AudioRecordingManager mAudioRecordingManager;
private AudioStreamingManager mAudioStreamingManager;
private IconManager mIconManager;
private BroadcastStatusPanel mBroadcastStatusPanel;
private BroadcastModel mBroadcastModel;
private ControllerPanel mControllerPanel;
private ChannelModel mChannelModel;
private ChannelProcessingManager mChannelProcessingManager;
private IconManager mIconManager = new IconManager();
private PlaylistManager mPlaylistManager;
private SourceManager mSourceManager;
private SettingsManager mSettingsManager;
private SpectralDisplayPanel mSpectralPanel;
@@ -164,67 +157,46 @@ public class SDRTrunk implements Listener<TunerEvent>
//Register FontAwesome so we can use the fonts in Swing windows
IconFontSwing.register(FontAwesome.getIconFont());
IconFontFX.register(FontAwesome.getIconFont());
TunerConfigurationModel tunerConfigurationModel = new TunerConfigurationModel();
TunerModel tunerModel = new TunerModel(tunerConfigurationModel);
mIconManager = new IconManager();
mSettingsManager = new SettingsManager(tunerConfigurationModel);
mSourceManager = new SourceManager(tunerModel, mSettingsManager, mUserPreferences);
AliasModel aliasModel = new AliasModel();
mChannelModel = new ChannelModel();
ChannelMapModel channelMapModel = new ChannelMapModel();
EventLogManager eventLogManager = new EventLogManager(aliasModel, mUserPreferences);
mJavaFxWindowManager = new JavaFxWindowManager(mUserPreferences);
mSourceManager = new SourceManager(tunerModel, mSettingsManager, mUserPreferences);
mChannelProcessingManager = new ChannelProcessingManager(channelMapModel, eventLogManager, mSourceManager,
aliasModel, mUserPreferences);
mChannelModel.addListener(mChannelProcessingManager);
mChannelProcessingManager.addChannelEventListener(mChannelModel);
ChannelSelectionManager channelSelectionManager = new ChannelSelectionManager(mChannelModel);
mChannelModel.addListener(channelSelectionManager);
mPlaylistManager = new PlaylistManager(mUserPreferences, mSourceManager, aliasModel, eventLogManager, mIconManager);
mJavaFxWindowManager = new JavaFxWindowManager(mUserPreferences, mPlaylistManager);
new ChannelSelectionManager(mPlaylistManager.getChannelModel());
AudioPlaybackManager audioPlaybackManager = new AudioPlaybackManager(mUserPreferences);
mAudioRecordingManager = new AudioRecordingManager(mUserPreferences);
mAudioRecordingManager.start();
mBroadcastModel = new BroadcastModel(aliasModel, mIconManager, mUserPreferences);
mAudioStreamingManager = new AudioStreamingManager(mBroadcastModel, BroadcastFormat.MP3,
mAudioStreamingManager = new AudioStreamingManager(mPlaylistManager.getBroadcastModel(), BroadcastFormat.MP3,
mUserPreferences);
mAudioStreamingManager.start();
mChannelProcessingManager.addAudioSegmentListener(audioPlaybackManager);
mChannelProcessingManager.addAudioSegmentListener(mAudioRecordingManager);
mChannelProcessingManager.addAudioSegmentListener(mAudioStreamingManager);
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(audioPlaybackManager);
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(mAudioRecordingManager);
mPlaylistManager.getChannelProcessingManager().addAudioSegmentListener(mAudioStreamingManager);
MapService mapService = new MapService(mIconManager);
mChannelProcessingManager.addDecodeEventListener(mapService);
mPlaylistManager.getChannelProcessingManager().addDecodeEventListener(mapService);
mControllerPanel = new ControllerPanel(audioPlaybackManager, aliasModel, mBroadcastModel,
mChannelModel, channelMapModel, mChannelProcessingManager, mIconManager,
mapService, mSettingsManager, mSourceManager, tunerModel, mUserPreferences);
mControllerPanel = new ControllerPanel(mPlaylistManager, audioPlaybackManager, mIconManager, mapService,
mSettingsManager, mSourceManager, mUserPreferences);
mSpectralPanel = new SpectralDisplayPanel(mChannelModel,
mChannelProcessingManager, mSettingsManager, tunerModel);
mSpectralPanel = new SpectralDisplayPanel(mPlaylistManager, mSettingsManager, tunerModel);
TunerSpectralDisplayManager tunerSpectralDisplayManager = new TunerSpectralDisplayManager(mSpectralPanel,
mChannelModel, mChannelProcessingManager, mSettingsManager, tunerModel);
mPlaylistManager, mSettingsManager, tunerModel);
tunerModel.addListener(tunerSpectralDisplayManager);
tunerModel.addListener(this);
PlaylistManager playlistManager = new PlaylistManager(aliasModel, mBroadcastModel, mChannelModel,
channelMapModel, mUserPreferences);
playlistManager.init();
mPlaylistManager.init();
mLog.info("starting main application gui");
@@ -234,19 +206,15 @@ public class SDRTrunk implements Listener<TunerEvent>
tunerModel.requestFirstTunerDisplay();
//Start the gui
EventQueue.invokeLater(new Runnable()
{
public void run()
EventQueue.invokeLater(() -> {
try
{
try
{
mMainGui.setVisible(true);
autoStartChannels();
}
catch(Exception e)
{
e.printStackTrace();
}
mMainGui.setVisible(true);
autoStartChannels();
}
catch(Exception e)
{
e.printStackTrace();
}
});
}
@@ -258,11 +226,12 @@ public class SDRTrunk implements Listener<TunerEvent>
*/
private void autoStartChannels()
{
List<Channel> channels = mChannelModel.getAutoStartChannels();
List<Channel> channels = mPlaylistManager.getChannelModel().getAutoStartChannels();
if(channels.size() > 0)
{
ChannelAutoStartFrame autoStartFrame = new ChannelAutoStartFrame(mChannelProcessingManager, channels);
ChannelAutoStartFrame autoStartFrame = new ChannelAutoStartFrame(mPlaylistManager.getChannelProcessingManager(),
channels);
}
}
@@ -341,6 +310,34 @@ public class SDRTrunk implements Listener<TunerEvent>
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.getEventBus().post(new ViewPlaylistRequest()));
viewMenu.add(viewPlaylistItem);
JMenuItem preferencesItem = new JMenuItem("User Preferences");
preferencesItem.addActionListener(e -> MyEventBus.getEventBus().post(new UserPreferenceEditorViewRequest()));
viewMenu.add(preferencesItem);
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
settingsMenu.addActionListener(arg0 -> mIconManager.showEditor(mMainGui));
viewMenu.add(settingsMenu);
JMenuItem logFilesMenu = new JMenuItem("Logs & Recordings");
logFilesMenu.addActionListener(new ActionListener()
{
@@ -363,54 +360,13 @@ public class SDRTrunk implements Listener<TunerEvent>
}
}
});
fileMenu.add(logFilesMenu);
viewMenu.add(logFilesMenu);
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
settingsMenu.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent arg0)
{
mIconManager.showEditor(mMainGui);
}
});
fileMenu.add(settingsMenu);
fileMenu.add(new JSeparator());
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");
viewMenu.add(new BroadcastStatusVisibleMenuItem(mControllerPanel));
viewMenu.add(new JSeparator());
// for(Tuner tuner : mSourceManager.getTunerModel().getTuners())
// {
// viewMenu.add(new ShowTunerMenuItem(mSourceManager.getTunerModel(), tuner));
// }
viewMenu.add(new TunersMenu());
viewMenu.add(new JSeparator());
viewMenu.add(new ClearTunerMenuItem(mSpectralPanel));
viewMenu.add(new JSeparator());
JMenuItem preferencesItem = new JMenuItem("Preferences");
preferencesItem.addActionListener(e -> MyEventBus.getEventBus()
.post(new PreferenceEditorViewRequest(PreferenceEditorType.AUDIO_PLAYBACK)));
viewMenu.add(preferencesItem);
viewMenu.add(new BroadcastStatusVisibleMenuItem(mControllerPanel));
menuBar.add(viewMenu);
@@ -468,7 +424,7 @@ public class SDRTrunk implements Listener<TunerEvent>
mUserPreferences.getSwingPreference().setDimension(CONTROLLER_PANEL_IDENTIFIER, mControllerPanel.getSize());
mJavaFxWindowManager.shutdown();
mLog.info("Stopping channels ...");
mChannelProcessingManager.shutdown();
mPlaylistManager.getChannelProcessingManager().shutdown();
mAudioRecordingManager.stop();
mLog.info("Stopping spectral display ...");
@@ -485,7 +441,7 @@ public class SDRTrunk implements Listener<TunerEvent>
{
if(mBroadcastStatusPanel == null)
{
mBroadcastStatusPanel = new BroadcastStatusPanel(mBroadcastModel, mUserPreferences,
mBroadcastStatusPanel = new BroadcastStatusPanel(mPlaylistManager.getBroadcastModel(), mUserPreferences,
"application.broadcast.status.panel");
mBroadcastStatusPanel.setPreferredSize(new Dimension(880, 70));
mBroadcastStatusPanel.getTable().setEnabled(false);
@@ -501,22 +457,17 @@ public class SDRTrunk implements Listener<TunerEvent>
{
mBroadcastStatusVisible = !mBroadcastStatusVisible;
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
EventQueue.invokeLater(() -> {
if(mBroadcastStatusVisible)
{
if(mBroadcastStatusVisible)
{
mSplitPane.add(getBroadcastStatusPanel());
}
else
{
mSplitPane.remove(getBroadcastStatusPanel());
}
mMainGui.revalidate();
mSplitPane.add(getBroadcastStatusPanel());
}
else
{
mSplitPane.remove(getBroadcastStatusPanel());
}
mMainGui.revalidate();
});
SystemProperties.getInstance().set(PROPERTY_BROADCAST_STATUS_VISIBLE, mBroadcastStatusVisible);

View File

@@ -0,0 +1,139 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.control;
import javafx.scene.control.TextFormatter;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.UnaryOperator;
/**
* Text formatter for integer values displayed and edited as hexadecimal values that constrains values to
* specified minimum and maximum valid values.
*/
public class HexFormatter extends TextFormatter<Integer>
{
private static final Logger mLog = LoggerFactory.getLogger(HexFormatter.class);
/**
* Constructs an instance
* @param minimum allowed value
* @param maximum allowed value
*/
public HexFormatter(int minimum, int maximum)
{
super(new HexIntegerStringConverter(), null, new HexFilter(minimum, maximum));
}
/**
* Formatted text change filter that only allows hexadecimal characters where the converted decimal value
* is also constrained within minimum and maximum valid values.
*/
public static class HexFilter implements UnaryOperator<TextFormatter.Change>
{
private String HEXADECIMAL_REGEX = "[0-9A-Fa-f].*";
private int mMinimum;
private int mMaximum;
public HexFilter(int minimum, int maximum)
{
mMinimum = minimum;
mMaximum = maximum;
}
private boolean isValid(Integer value)
{
return value != null && mMinimum <= value && value <= mMaximum;
}
@Override
public Change apply(Change change)
{
//Only validate if the user added text to the control. Otherwise, allow it to go through
if(change.getText() != null)
{
String updatedText = change.getControlNewText();
if(updatedText == null || updatedText.isEmpty())
{
return change;
}
if(!updatedText.matches(HEXADECIMAL_REGEX) ||
!isValid(HexIntegerStringConverter.getValue(change.getControlNewText())))
{
return null;
}
else
{
//Force all hex letters to uppercase
if(change.getText().matches("[a-f].*"))
{
change.setText(change.getText().toUpperCase());
}
}
}
return change;
}
}
/**
* Hexadecimal integer string converter. Obtain value as an integer but user edits hexadecimal values.
*/
public static class HexIntegerStringConverter extends StringConverter<Integer>
{
@Override
public String toString(Integer value)
{
// If the specified value is null, return a zero-length String
if (value == null)
{
return "";
}
return (Integer.toHexString(value.intValue()).toUpperCase());
}
@Override
public Integer fromString(String value)
{
return getValue(value);
}
public static Integer getValue(String value)
{
// If the specified value is null or zero-length, return null
if (value == null) {
return null;
}
value = value.trim();
if (value.length() < 1) {
return null;
}
return Integer.valueOf(value, 16);
}
}
}

View File

@@ -0,0 +1,108 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.control;
import io.github.dsheirer.gui.playlist.alias.AliasItemEditor;
import javafx.scene.control.TextFormatter;
import javafx.util.converter.IntegerStringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.UnaryOperator;
/**
* Text formatter for integer values that constrains values to specified minimum and maximum valid values.
*/
public class IntegerFormatter extends TextFormatter<Integer>
{
private static final Logger mLog = LoggerFactory.getLogger(AliasItemEditor.class);
/**
* Constructs an instance
* @param minimum allowed value
* @param maximum allowed value
*/
public IntegerFormatter(int minimum, int maximum)
{
super(new IntegerStringConverter(), null, new IntegerFilter(minimum, maximum));
}
/**
* Formatted text change filter that only allows hexadecimal characters where the converted decimal value
* is also constrained within minimum and maximum valid values.
*/
public static class IntegerFilter implements UnaryOperator<Change>
{
private String DECIMAL_REGEX = "[0-9].*";
private int mMinimum;
private int mMaximum;
public IntegerFilter(int minimum, int maximum)
{
mMinimum = minimum;
mMaximum = maximum;
}
/**
* Indicates if the value argument is parsable as an integer, or is empty or null.
*/
private boolean isValid(String value)
{
if(value == null || value.isEmpty())
{
return true;
}
try
{
int parsed = Integer.parseInt(value);
return mMinimum <= parsed && parsed <= mMaximum;
}
catch(Exception e)
{
//no-op
}
return false;
}
@Override
public Change apply(Change change)
{
//Only validate if the user added text to the control. Otherwise, allow it to go through
if(change.getText() != null)
{
String updatedText = change.getControlNewText();
if(updatedText == null || updatedText.isEmpty())
{
return change;
}
if(!updatedText.matches(DECIMAL_REGEX) || !isValid(updatedText))
{
return null;
}
}
return change;
}
}
}

View File

@@ -0,0 +1,84 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.control;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.util.converter.IntegerStringConverter;
import java.util.function.UnaryOperator;
/**
* Text field control for integer values.
*/
public class IntegerTextField extends TextField
{
private static final String REGEX = "[1-9][0-9]*";
/**
* Constructs an instance
*/
public IntegerTextField()
{
UnaryOperator<TextFormatter.Change> filter = change ->
{
String newText = change.getControlNewText();
if(newText.isEmpty() || newText.matches(REGEX))
{
return change;
}
return null;
};
setTextFormatter(new TextFormatter<Integer>(new IntegerStringConverter(), 0, filter));
}
/**
* Current integer value of the control, or null if the value is not parsable as an integer
*/
public Integer get()
{
String rawText = getText();
try
{
return Integer.parseInt(rawText);
}
catch(Exception pe)
{
//Do nothing ... we couldn't parse the value
}
return null;
}
/**
* Sets the integer value in the control
*/
public void set(int value)
{
setText(String.valueOf(value));
}
}

View File

@@ -0,0 +1,230 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.control;
import javafx.scene.control.TextFormatter;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Text formatter for integer values displayed and edited as LTR formatted values that constrains values to
* specified minimum and maximum valid values.
*/
public class LtrFormatter extends TextFormatter<Integer>
{
private static final Logger mLog = LoggerFactory.getLogger(LtrFormatter.class);
/**
* Constructs an instance
* @param minimum allowed value
* @param maximum allowed value
*/
public LtrFormatter(int minimum, int maximum)
{
super(new LtrIntegerStringConverter(), null, new LtrFilter(minimum, maximum));
}
/**
* Formatted text change filter that only allows formatted characters where the converted decimal value
* is also constrained within minimum and maximum valid values.
*/
public static class LtrFilter implements UnaryOperator<Change>
{
private int mMinimum;
private int mMaximum;
public LtrFilter(int minimum, int maximum)
{
mMinimum = minimum;
mMaximum = maximum;
}
private boolean isValid(Integer value)
{
return value != null && mMinimum <= value && value <= mMaximum;
}
@Override
public Change apply(Change change)
{
String updatedText = change.getControlNewText();
if(updatedText == null || updatedText.isEmpty())
{
return change;
}
if(LtrIntegerStringConverter.isValid(updatedText))
{
return change;
}
return null;
}
}
/**
* LTR format integer string converter. Obtain value as an integer but user edits formatted values.
*/
public static class LtrIntegerStringConverter extends StringConverter<Integer>
{
private static final Pattern LTR_PATTERN = Pattern.compile("([01]?)(?:-(\\d{0,2})(?:-(\\d{0,3}))?)?");
private static final int AREA_MASK = 0x2000;
private static final int LCN_MASK = 0x1F00;
private static final int TALKGROUP_MASK = 0xFF;
@Override
public String toString(Integer value)
{
// If the specified value is null, return a zero-length String
if (value == null)
{
return "";
}
int area = (value & AREA_MASK) >> 13;
int lcn = (value & LCN_MASK) >> 8;
int talkgroup = (value & TALKGROUP_MASK);
StringBuilder sb = new StringBuilder();
sb.append(area);
sb.append("-");
sb.append(String.format("%02d", lcn));
sb.append("-");
sb.append(String.format("%03d", talkgroup));
return sb.toString();
}
@Override
public Integer fromString(String value)
{
if(value == null || value.isEmpty())
{
return null;
}
Matcher m = LTR_PATTERN.matcher(value);
if(m.matches())
{
int a = 0;
int b = 0;
int c = 0;
try
{
a = Integer.parseInt(m.group(1));
}
catch(Exception e)
{
//do nothing
}
try
{
b = Integer.parseInt(m.group(2));
}
catch(Exception e)
{
//do nothing
}
try
{
c = Integer.parseInt(m.group(3));
}
catch(Exception e)
{
//do nothing
}
int talkgroup = (a << 13) + (b << 8) + c;
return talkgroup;
}
return null;
}
public static boolean isValid(String value)
{
if(value == null || value.isEmpty())
{
return true;
}
Matcher m = LTR_PATTERN.matcher(value);
if(m.matches())
{
//We don't have to check the first digit - it will only match a 0 or 1
String part2 = m.group(2);
if(part2 != null && !part2.isEmpty())
{
try
{
int value2 = Integer.parseInt(part2);
if(value2 < 0 || value2 > 31)
{
return false;
}
}
catch(Exception e)
{
//Do nothing
}
}
String part3 = m.group(3);
if(part3 != null && !part3.isEmpty())
{
try
{
int value3 = Integer.parseInt(part3);
if(value3 < 0 || value3 > 255)
{
return false;
}
}
catch(Exception e)
{
//Do nothing
}
}
//if part 2 and/or part 3 is null or empty then valid = true
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.control;
import javafx.scene.control.TextFormatter;
import java.util.function.UnaryOperator;
public class MaxLengthUnaryOperator implements UnaryOperator<TextFormatter.Change>
{
private int mMaxLength;
public MaxLengthUnaryOperator(int maxLength)
{
mMaxLength = maxLength;
}
@Override
public TextFormatter.Change apply(TextFormatter.Change change)
{
if(change.isContentChange() && change.getControlNewText().length() > mMaxLength)
{
return null;
}
return change;
}
}

View File

@@ -1,499 +0,0 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 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.control;
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.swing.AbstractAction;
import javax.swing.DefaultCellEditor;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.NumberFormatter;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
/**
* Multiple frequency editor control for editing an ordered list of frequencies. Frequency values are persisted as
* long values in hertz, but presented to the user as double values in megahertz.
*
*/
public class MultipleFrequencyEditor extends JPanel
{
private final static Logger mLog = LoggerFactory.getLogger(MultipleFrequencyEditor.class);
private static final Long DEFAULT_FREQUENCY = 0l;
private static final Icon ICON_ADD = IconFontSwing.buildIcon(FontAwesome.PLUS, 10);
private static final Icon ICON_DOWN = IconFontSwing.buildIcon(FontAwesome.ARROW_DOWN, 10);
private static final Icon ICON_REMOVE = IconFontSwing.buildIcon(FontAwesome.MINUS, 10);
private static final Icon ICON_UP = IconFontSwing.buildIcon(FontAwesome.ARROW_UP, 10);
private DecimalFormat mDecimalFormat = new DecimalFormat("0.000000");
private FrequencyModel mFrequencyModel = new FrequencyModel();
private JTable mFrequencyTable;
private JButton mAddButton;
private JButton mRemoveButton;
private JButton mMoveUpButton;
private JButton mMoveDownButton;
/**
* Constructs the editor
* @param minimum allowable frequency value
* @param maximum allowable frequency value
*/
public MultipleFrequencyEditor(double minimum, double maximum)
{
setLayout(new MigLayout("insets 0 0 0 0", "[]1[grow,fill]1[]", "[]1[]1[top,grow,fill]"));
mMoveUpButton = new JButton();
mMoveUpButton.setIcon(ICON_UP);
mMoveUpButton.setEnabled(false);
mMoveUpButton.addActionListener(e -> {
int selectedRow = mFrequencyTable.getSelectedRow();
int index = mFrequencyTable.convertRowIndexToModel(selectedRow);
mFrequencyModel.moveUp(index);
mFrequencyTable.getSelectionModel().setLeadSelectionIndex(index - 1);
mFrequencyTable.scrollRectToVisible(new Rectangle(mFrequencyTable.getCellRect(selectedRow, 0, true)));
updateButtons();
});
add(mMoveUpButton);
mFrequencyTable = new JTable(mFrequencyModel);
//testing ...
mFrequencyTable.setPreferredScrollableViewportSize(new Dimension(100, 65));
mFrequencyTable.setFillsViewportHeight(true);
mFrequencyTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mFrequencyTable.getSelectionModel().addListSelectionListener(new SelectionListener());
mFrequencyTable.setDefaultEditor(Long.class, new FrequencyEditor(minimum, maximum));
mFrequencyTable.setDefaultRenderer(Long.class, new FrequencyRenderer());
JScrollPane listScrollPane = new JScrollPane(mFrequencyTable);
add(listScrollPane, "span 1 3");
mAddButton = new JButton();
mAddButton.setIcon(ICON_ADD);
mAddButton.addActionListener(e -> {
int selectedRow = mFrequencyTable.getSelectedRow();
if(selectedRow >= 0)
{
int index = mFrequencyTable.convertRowIndexToModel(selectedRow);
int nextIndex = index + 1;
mFrequencyModel.add(DEFAULT_FREQUENCY, nextIndex);
mFrequencyTable.requestFocus();
mFrequencyTable.setRowSelectionInterval(nextIndex, nextIndex);
mFrequencyTable.scrollRectToVisible(new Rectangle(mFrequencyTable.getCellRect(nextIndex, 0, true)));
mFrequencyTable.editCellAt(nextIndex, 0);
}
else
{
mFrequencyModel.add(DEFAULT_FREQUENCY);
int lastIndex = mFrequencyModel.getRowCount() - 1;
mFrequencyTable.setRowSelectionInterval(lastIndex, lastIndex);
mFrequencyTable.editCellAt(lastIndex, 0);
}
mFrequencyTable.setSurrendersFocusOnKeystroke(true);
mFrequencyTable.getEditorComponent().requestFocus();
updateButtons();
});
add(mAddButton, "wrap");
mMoveDownButton = new JButton();
mMoveDownButton.setIcon(ICON_DOWN);
mMoveDownButton.setEnabled(false);
mMoveDownButton.addActionListener(e -> {
int selectedRow = mFrequencyTable.getSelectedRow();
int index = mFrequencyTable.convertRowIndexToModel(selectedRow);
mFrequencyModel.moveDown(index);
mFrequencyTable.getSelectionModel().setLeadSelectionIndex(index + 1);
mFrequencyTable.scrollRectToVisible(new Rectangle(mFrequencyTable.getCellRect(selectedRow, 0, true)));
updateButtons();
});
add(mMoveDownButton);
mRemoveButton = new JButton();
mRemoveButton.setIcon(ICON_REMOVE);
mRemoveButton.setEnabled(false);
mRemoveButton.addActionListener(e -> {
int selectedRow = mFrequencyTable.getSelectedRow();
int selectedIndex = mFrequencyTable.convertRowIndexToModel(selectedRow);
mFrequencyModel.remove(selectedIndex);
if(selectedIndex < mFrequencyModel.getRowCount())
{
int reselectedRow = mFrequencyTable.convertRowIndexToView(selectedIndex);
mFrequencyTable.setRowSelectionInterval(reselectedRow, reselectedRow);
mFrequencyTable.scrollRectToVisible(new Rectangle(mFrequencyTable.getCellRect(reselectedRow, 0, true)));
}
updateButtons();
});
add(mRemoveButton);
}
public FrequencyModel getFrequencyModel()
{
return mFrequencyModel;
}
private void updateButtons()
{
int index = mFrequencyTable.getSelectedRow();
if(index >= 0)
{
mMoveUpButton.setEnabled(index > 0);
mMoveDownButton.setEnabled(index < mFrequencyModel.getRowCount() - 1);
mRemoveButton.setEnabled(true);
}
else
{
mRemoveButton.setEnabled(false);
mMoveUpButton.setEnabled(false);
mMoveDownButton.setEnabled(false);
}
}
/**
* Formats the frequency value as MHz
*/
private String format(long frequency)
{
return mDecimalFormat.format(frequency / 1e6d);
}
public class FrequencyModel extends AbstractTableModel
{
private List<Long> mFrequencies = new ArrayList<>();
@Override
public String getColumnName(int column)
{
return "Frequencies (MHz)";
}
@Override
public int getRowCount()
{
return mFrequencies.size();
}
@Override
public int getColumnCount()
{
return 1;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
return mFrequencies.get(rowIndex);
}
public void setValueAt(int index, Long value)
{
mFrequencies.set(index, value);
fireTableRowsUpdated(index, index);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return true;
}
@Override
public Class<?> getColumnClass(int columnIndex)
{
return Long.class;
}
/**
* Adds a frequency to the model
*/
public void add(long frequency, int index)
{
mFrequencies.add(index, frequency);
fireTableRowsInserted(index, index);
}
/**
* Adds a frequency to the model
*/
public void add(long frequency)
{
mFrequencies.add(frequency);
fireTableRowsInserted(mFrequencies.size() - 1, mFrequencies.size() - 1);
}
/**
* Removes an indexed value from the model
*/
public Long remove(int index)
{
Long value = mFrequencies.remove(index);
fireTableRowsDeleted(index, index);
return value;
}
/**
* Removes all frequencies from the model
*/
public void clear()
{
mFrequencies.clear();
fireTableDataChanged();
}
/**
* Moves the frequency up in the list
*/
public void moveUp(int index)
{
if(index > 0 && index < mFrequencies.size())
{
Long valueA = mFrequencies.get(index);
Long valueB = mFrequencies.get(index - 1);
mFrequencies.set(index, valueB);
mFrequencies.set(index - 1, valueA);
fireTableRowsUpdated(index - 1, index);
}
}
/**
* Moves the frequency down in the list
*/
public void moveDown(int index)
{
if(index >= 0 && index + 1 < mFrequencies.size())
{
Long valueA = mFrequencies.get(index);
Long valueB = mFrequencies.get(index + 1);
mFrequencies.set(index, valueB);
mFrequencies.set(index + 1, valueA);
fireTableRowsUpdated(index, index + 1);
}
}
/**
* Sets the frequencies for the model
*/
public void setFrequencies(List<Long> frequencies)
{
mFrequencies.clear();
mFrequencies.addAll(frequencies);
fireTableDataChanged();
}
/**
* List of long frequency values contained in the model
*/
public List<Long> getFrequencies()
{
List<Long> frequencies = new ArrayList<>(mFrequencies);
return frequencies;
}
}
public class SelectionListener implements ListSelectionListener
{
@Override
public void valueChanged(ListSelectionEvent event)
{
updateButtons();
}
}
public class FrequencyEditor extends DefaultCellEditor
{
private JFormattedTextField mTextField;
private NumberFormat mNumberFormat;
private Double mMinimum;
private Double mMaximum;
public FrequencyEditor(double minValue, double maxValue)
{
super(new JFormattedTextField());
mTextField = (JFormattedTextField)getComponent();
mMinimum = minValue;
mMaximum = maxValue;
mNumberFormat = NumberFormat.getNumberInstance();
mNumberFormat.setMinimumFractionDigits(1);
mNumberFormat.setMaximumFractionDigits(6);
mNumberFormat.setGroupingUsed(false);
NumberFormatter numberFormatter = new NumberFormatter(mNumberFormat);
numberFormatter.setFormat(mNumberFormat);
numberFormatter.setMinimum(mMinimum);
numberFormatter.setMaximum(mMaximum);
numberFormatter.setAllowsInvalid(false);
mTextField.setFormatterFactory(new DefaultFormatterFactory(numberFormatter));
mTextField.setValue(mMinimum);
mTextField.setHorizontalAlignment(JTextField.TRAILING);
mTextField.setFocusLostBehavior(JFormattedTextField.PERSIST);
mTextField.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "check");
mTextField.getActionMap().put("check", new AbstractAction()
{
@Override
public void actionPerformed(ActionEvent event)
{
//This should always be true since we use formatter.setAllowsInvalid(false) above.
if(mTextField.isEditValid())
{
int selectedRow = mFrequencyTable.getSelectedRow();
int selectedIndex = mFrequencyTable.convertRowIndexToModel(selectedRow);
try
{
Double frequencyMHz = Double.parseDouble(mTextField.getText());
long frequency = (long)(frequencyMHz * 1e6d);
mFrequencyModel.setValueAt(selectedIndex, frequency);
}
catch(Exception err)
{
mLog.error("Error parsing frequency: " + mTextField.getText(), err);
}
fireEditingStopped();
}
}
});
}
//Override to invoke setValue on the formatted text field.
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column)
{
JFormattedTextField ftf = (JFormattedTextField)super.getTableCellEditorComponent(table, value, isSelected, row, column);
if(value instanceof Long)
{
ftf.setValue(((Long)value).doubleValue() / 1e6d);
}
else
{
ftf.setValue(value);
}
ftf.requestFocus();
ftf.selectAll();
ftf.setCaretPosition(0);
return ftf;
}
//Override to ensure that the value remains a Double.
public Object getCellEditorValue()
{
JFormattedTextField ftf = (JFormattedTextField)getComponent();
Object o = ftf.getValue();
if(o instanceof Double)
{
return o;
}
else if(o instanceof Number)
{
return ((Number)o).doubleValue();
}
else
{
try
{
return Double.parseDouble(o.toString());
}
catch(Exception exc)
{
return 0.0d;
}
}
}
}
/**
* Table cell renderer to display long hertz values as double megahertz values
*/
public class FrequencyRenderer extends DefaultTableCellRenderer
{
public FrequencyRenderer()
{
super.setHorizontalAlignment(JLabel.RIGHT);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
JLabel cell = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if(value instanceof Long)
{
cell.setText(format((long)value));
}
else
{
cell.setText("??");
}
return cell;
}
}
public static void main(String[] args)
{
EventQueue.invokeLater(() -> {
final JFrame frame = new JFrame();
frame.setSize(new Dimension(400, 400));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new MultipleFrequencyEditor(1.0, 6000.0));
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}

View File

@@ -0,0 +1,216 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.control;
import javafx.scene.control.TextFormatter;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Text formatter for talkgroups that use prefix and ident format that constrains values to specified minimum and
* maximum valid values.
*/
public class PrefixIdentFormatter extends TextFormatter<Integer>
{
private static final Logger mLog = LoggerFactory.getLogger(PrefixIdentFormatter.class);
/**
* Constructs an instance
* @param minimum allowed value
* @param maximum allowed value
*/
public PrefixIdentFormatter(int minimum, int maximum)
{
super(new PrefixIdentIntegerStringConverter(), null, new PrefixIdentFilter(minimum, maximum));
}
/**
* Formatted text change filter that only allows formatted characters where the converted decimal value
* is also constrained within minimum and maximum valid values.
*/
public static class PrefixIdentFilter implements UnaryOperator<Change>
{
private int mMinimum;
private int mMaximum;
public PrefixIdentFilter(int minimum, int maximum)
{
mMinimum = minimum;
mMaximum = maximum;
}
private boolean isValid(Integer value)
{
return value != null && mMinimum <= value && value <= mMaximum;
}
@Override
public Change apply(Change change)
{
String updatedText = change.getControlNewText();
if(updatedText == null || updatedText.isEmpty())
{
return change;
}
if(PrefixIdentIntegerStringConverter.isValid(updatedText))
{
return change;
}
return null;
}
}
/**
* Prefix-Ident integer string converter. Obtain value as an integer but user edits formatted values.
*/
public static class PrefixIdentIntegerStringConverter extends StringConverter<Integer>
{
private static final Pattern PREFIX_IDENT_PATTERN = Pattern.compile("(\\d{0,3})(?:-(\\d{0,4}))?");
private static final int PREFIX_MASK = 0xFE000;
private static final int IDENT_MASK = 0x1FFF;
@Override
public String toString(Integer value)
{
// If the specified value is null, return a zero-length String
if (value == null)
{
return "";
}
int prefix = (value & PREFIX_MASK) >> 13;
int ident = (value & IDENT_MASK);
StringBuilder sb = new StringBuilder();
sb.append(String.format("%03d", prefix));
sb.append("-");
sb.append(String.format("%04d", ident));
return sb.toString();
}
@Override
public Integer fromString(String value)
{
if(value == null || value.isEmpty())
{
return null;
}
Matcher m = PREFIX_IDENT_PATTERN.matcher(value);
if(m.matches())
{
int a = 0;
int b = 0;
try
{
a = Integer.parseInt(m.group(1));
}
catch(Exception e)
{
//do nothing
}
try
{
b = Integer.parseInt(m.group(2));
}
catch(Exception e)
{
//do nothing
}
int talkgroup = (a << 13) + b;
return talkgroup;
}
return null;
}
public static boolean isValid(String value)
{
if(value == null || value.isEmpty())
{
return true;
}
Matcher m = PREFIX_IDENT_PATTERN.matcher(value);
if(m.matches())
{
//We don't have to check the first digit - it will only match a 0 or 1
String rawPrefix = m.group(1);
if(rawPrefix != null && !rawPrefix.isEmpty())
{
try
{
int prefix = Integer.parseInt(rawPrefix);
if(prefix < 0 || prefix > 127)
{
return false;
}
}
catch(Exception e)
{
//Do nothing
}
}
String rawIdent = m.group(2);
if(rawIdent != null && !rawIdent.isEmpty())
{
try
{
int ident = Integer.parseInt(rawIdent);
if(ident < 0 || ident > 8191)
{
return false;
}
}
catch(Exception e)
{
//Do nothing
}
}
//if prefix and/or ident is null or empty then valid = true
return true;
}
return false;
}
}
}

View File

@@ -1,25 +0,0 @@
package io.github.dsheirer.gui.editor;
public class EditorValidationException extends Exception
{
private static final long serialVersionUID = 1L;
private Editor<?> mEditor;
private String mReason;
public EditorValidationException( Editor<?> editor, String reason )
{
mEditor = editor;
mReason = reason;
}
public Editor<?> getEditor()
{
return mEditor;
}
public String getReason()
{
return mReason;
}
}

View File

@@ -1,36 +0,0 @@
package io.github.dsheirer.gui.editor;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
public class EmptyValidatingEditor<T> extends ValidatingEditor<T>
{
private static final long serialVersionUID = 1L;
public EmptyValidatingEditor( String descriptor )
{
setLayout( new MigLayout( "insets 0 0 0 0", "", "" ) );
add( new JLabel( "Please select " + descriptor ), "wrap" );
}
public EmptyValidatingEditor()
{
this( "an item" );
}
@Override
public void save()
{
}
@Override
public void reset()
{
}
@Override
public void validate( Editor<T> editor ) throws EditorValidationException
{
}
}

View File

@@ -1,15 +0,0 @@
package io.github.dsheirer.gui.editor;
/**
* Editor class that allows other editors to be validated for configuration with this editor
*/
public abstract class ValidatingEditor<T> extends Editor<T>
{
private static final long serialVersionUID = 1L;
/**
* Indicates if the editor argument is valid according to the settings of this editor
*/
public abstract void validate( Editor<T> editor ) throws EditorValidationException;
}

View File

@@ -39,7 +39,6 @@ import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconFontFX;
import jiconfont.javafx.IconNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -71,7 +70,6 @@ public class PlaybackController extends HBox implements IFrameLocationListener
public PlaybackController()
{
super(10);
IconFontFX.register(jiconfont.icons.font_awesome.FontAwesome.getIconFont());
getChildren().addAll(getControlsBox(), getFileLabel());

View File

@@ -0,0 +1,70 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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.playlist;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.layout.VBox;
public abstract class Editor<T> extends VBox
{
private T mItem;
private BooleanProperty mModifiedProperty = new SimpleBooleanProperty();
public Editor()
{
setMaxWidth(Double.MAX_VALUE);
}
/**
* Saves the editor's values to the edited item.
*/
public abstract void save();
/**
* Prepare the editor for disposal
*/
public abstract void dispose();
/**
* Sets the item to be edited by this editor
* @param item to be edited
*/
public void setItem(T item)
{
mItem = item;
}
public T getItem()
{
return mItem;
}
/**
* Observable property that indicates if the contents of the editor have been modified (and need saved or reset).
*/
public BooleanProperty modifiedProperty()
{
return mModifiedProperty;
}
}

View File

@@ -0,0 +1,276 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.playlist.alias.AliasEditor;
import io.github.dsheirer.gui.playlist.alias.AliasTabRequest;
import io.github.dsheirer.gui.playlist.channel.ChannelEditor;
import io.github.dsheirer.gui.playlist.channel.ChannelTabRequest;
import io.github.dsheirer.gui.playlist.manager.PlaylistManagerEditor;
import io.github.dsheirer.gui.playlist.radioreference.RadioReferenceEditor;
import io.github.dsheirer.gui.playlist.streaming.StreamingEditor;
import io.github.dsheirer.gui.preference.PreferenceEditorType;
import io.github.dsheirer.gui.preference.UserPreferenceEditorViewRequest;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.util.ThreadPool;
import io.github.dsheirer.util.TimeStamp;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.image.WritableImage;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Path;
/**
* JavaFX playlist, channels, aliases, streaming and radioreference.com import editor
*/
public class PlaylistEditor extends BorderPane
{
private static final Logger mLog = LoggerFactory.getLogger(PlaylistEditor.class);
private PlaylistManager mPlaylistManager;
private UserPreferences mUserPreferences;
private MenuBar mMenuBar;
private TabPane mTabPane;
private Tab mPlaylistsTab;
private Tab mChannelsTab;
private Tab mAliasesTab;
private Tab mRadioReferenceTab;
private Tab mStreamingTab;
private AliasEditor mAliasEditor;
private ChannelEditor mChannelEditor;
/**
* Constructs an instance
* @param playlistManager for alias and channel models
* @param userPreferences for settings
*/
public PlaylistEditor(PlaylistManager playlistManager, UserPreferences userPreferences)
{
mPlaylistManager = playlistManager;
mUserPreferences = userPreferences;
//Throw a new runnable back onto the FX thread to lazy load the editor content after the editor has been
//constructed and shown.
Platform.runLater(() -> {
setTop(getMenuBar());
setCenter(getTabPane());
});
}
/**
* Process requests for sub-editor actions like view an alias or view a channel.
*
* Note: this method must be invoked on the JavaFX platform thread
* @param request to process
*/
public void process(PlaylistEditorRequest request)
{
switch(request.getTabName())
{
case ALIAS:
if(request instanceof AliasTabRequest)
{
getTabPane().getSelectionModel().select(getAliasesTab());
getAliasEditor().process((AliasTabRequest)request);
}
break;
case CHANNEL:
if(request instanceof ChannelTabRequest)
{
getTabPane().getSelectionModel().select(getChannelsTab());
getChannelEditor().process((ChannelTabRequest)request);
}
break;
case PLAYLIST:
//Ignore - this is a request to simply show te playlist editor
break;
default:
mLog.warn("Unrecognized playlist editor request: " + request.getClass());
break;
}
}
private MenuBar getMenuBar()
{
if(mMenuBar == null)
{
mMenuBar = new MenuBar();
//File Menu
Menu fileMenu = new Menu("_File");
fileMenu.setAccelerator(new KeyCodeCombination(KeyCode.F, KeyCombination.ALT_ANY));
MenuItem closeItem = new MenuItem("_Close");
closeItem.setAccelerator(new KeyCodeCombination(KeyCode.C, KeyCombination.ALT_ANY));
closeItem.setOnAction(event -> getMenuBar().getParent().getScene().getWindow().hide());
fileMenu.getItems().add(closeItem);
mMenuBar.getMenus().add(fileMenu);
Menu viewMenu = new Menu("_View");
viewMenu.setAccelerator(new KeyCodeCombination(KeyCode.V, KeyCombination.ALT_ANY));
MenuItem userPreferenceItem = new MenuItem("_User Preferences");
userPreferenceItem.setAccelerator(new KeyCodeCombination(KeyCode.U, KeyCombination.ALT_ANY));
userPreferenceItem.setOnAction(event -> MyEventBus.getEventBus()
.post(new UserPreferenceEditorViewRequest(PreferenceEditorType.TALKGROUP_FORMAT)));
viewMenu.getItems().add(userPreferenceItem);
mMenuBar.getMenus().add(viewMenu);
Menu screenShot = new Menu("_Screenshot");
IconNode cameraNode = new IconNode(FontAwesome.CAMERA);
cameraNode.setFill(Color.DARKGRAY);
screenShot.setGraphic(cameraNode);
MenuItem menuItem = new MenuItem();
screenShot.getItems().add(menuItem);
screenShot.setOnShowing(event -> screenShot.hide());
screenShot.setOnShown(event -> menuItem.fire());
menuItem.setOnAction(event -> {
WritableImage image = getMenuBar().getScene().snapshot(null);
final BufferedImage bufferedImage = SwingFXUtils.fromFXImage(image, null);
String filename = TimeStamp.getTimeStamp("_") + "_screen_capture.png";
final Path captureFile = mUserPreferences.getDirectoryPreference().getDirectoryScreenCapture().resolve(filename);
ThreadPool.SCHEDULED.execute(() -> {
try
{
ImageIO.write(bufferedImage, "png", captureFile.toFile());
}
catch(IOException e)
{
mLog.error("Couldn't write screen capture to file [" + captureFile.toString() + "]", e);
}
});
});
mMenuBar.getMenus().add(screenShot);
}
return mMenuBar;
}
private TabPane getTabPane()
{
if(mTabPane == null)
{
mTabPane = new TabPane();
mTabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
mTabPane.getTabs().addAll(getPlaylistsTab(), getChannelsTab(), getAliasesTab(), getStreamingTab(),
getRadioReferenceTab());
}
return mTabPane;
}
private Tab getAliasesTab()
{
if(mAliasesTab == null)
{
mAliasesTab = new Tab("Aliases");
mAliasesTab.setContent(getAliasEditor());
}
return mAliasesTab;
}
private AliasEditor getAliasEditor()
{
if(mAliasEditor == null)
{
mAliasEditor = new AliasEditor(mPlaylistManager, mUserPreferences);
}
return mAliasEditor;
}
private Tab getChannelsTab()
{
if(mChannelsTab == null)
{
mChannelsTab = new Tab("Channels");
mChannelsTab.setContent(getChannelEditor());
}
return mChannelsTab;
}
private ChannelEditor getChannelEditor()
{
if(mChannelEditor == null)
{
mChannelEditor = new ChannelEditor(mPlaylistManager);
}
return mChannelEditor;
}
private Tab getPlaylistsTab()
{
if(mPlaylistsTab == null)
{
mPlaylistsTab = new Tab("Playlists");
mPlaylistsTab.setContent(new PlaylistManagerEditor(mPlaylistManager, mUserPreferences));
}
return mPlaylistsTab;
}
private Tab getRadioReferenceTab()
{
if(mRadioReferenceTab == null)
{
mRadioReferenceTab = new Tab("Radio Reference");
mRadioReferenceTab.setContent(new RadioReferenceEditor(mUserPreferences, mPlaylistManager));
}
return mRadioReferenceTab;
}
private Tab getStreamingTab()
{
if(mStreamingTab == null)
{
mStreamingTab = new Tab("Streaming");
mStreamingTab.setContent(new StreamingEditor(mPlaylistManager));
}
return mStreamingTab;
}
}

View File

@@ -0,0 +1,90 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
import io.github.dsheirer.gui.JavaFxWindowManager;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.module.log.EventLogManager;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.SourceManager;
import io.github.dsheirer.source.tuner.TunerModel;
import io.github.dsheirer.source.tuner.configuration.TunerConfigurationModel;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
/**
* Simple application wrapper for testing the Playlist Editor individually.
*/
public class PlaylistEditorApplication extends Application
{
private Stage mStage;
private Parent mPlaylistEditor;
private UserPreferences mUserPreferences = new UserPreferences();
private PlaylistManager mPlaylistManager;
private JavaFxWindowManager mJavaFxWindowManager;
public PlaylistEditorApplication()
{
AliasModel aliasModel = new AliasModel();
BroadcastModel broadcastModel = new BroadcastModel(aliasModel, new IconManager(), mUserPreferences);
ChannelMapModel channelMapModel = new ChannelMapModel();
TunerConfigurationModel tunerConfigurationModel = new TunerConfigurationModel();
TunerModel tunerModel = new TunerModel(tunerConfigurationModel);
SourceManager sourceManager = new SourceManager(tunerModel, new SettingsManager(tunerConfigurationModel),
mUserPreferences);
EventLogManager eventLogManager = new EventLogManager(aliasModel, mUserPreferences);
mPlaylistManager = new PlaylistManager(mUserPreferences, sourceManager, aliasModel, eventLogManager, new IconManager());
mPlaylistManager.init();
mJavaFxWindowManager = new JavaFxWindowManager(mUserPreferences, mPlaylistManager);
}
@Override
public void start(Stage primaryStage) throws Exception
{
mStage = primaryStage;
mStage.setTitle("Playlist Editor");
Scene scene = new Scene(getPlaylistEditor(), 1000, 750);
mStage.setScene(scene);
mStage.show();
}
private Parent getPlaylistEditor()
{
if(mPlaylistEditor == null)
{
mPlaylistEditor = new PlaylistEditor(mPlaylistManager, mUserPreferences);
}
return mPlaylistEditor;
}
public static void main(String[] args)
{
launch(args);
}
}

View File

@@ -0,0 +1,30 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist;
/**
* Request to view a facet of the playlist editor
*/
public abstract class PlaylistEditorRequest
{
public enum TabName {ALIAS, CHANNEL, PLAYLIST, RADIOREFERENCE, STREAM}
public abstract TabName getTabName();
}

View File

@@ -0,0 +1,32 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist;
/**
* Request to view the playlist editor
*/
public class ViewPlaylistRequest extends PlaylistEditorRequest
{
@Override
public TabName getTabName()
{
return TabName.PLAYLIST;
}
}

View File

@@ -0,0 +1,811 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.alias;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasFactory;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.gui.control.MaxLengthUnaryOperator;
import io.github.dsheirer.icon.Icon;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import javafx.application.Platform;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ObservableValue;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextInputDialog;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.util.Callback;
import jiconfont.IconCode;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconNode;
import org.controlsfx.control.textfield.TextFields;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* Editor for aliases
*/
public class AliasConfigurationEditor extends SplitPane
{
private static final Logger mLog = LoggerFactory.getLogger(AliasConfigurationEditor.class);
private PlaylistManager mPlaylistManager;
private UserPreferences mUserPreferences;
private AliasItemEditor mAliasItemEditor;
private TableView<Alias> mAliasTableView;
private Label mPlaceholderLabel;
private Button mNewAliasButton;
private Button mDeleteAliasButton;
private Button mCloneAliasButton;
private MenuButton mMoveToAliasButton;
private VBox mButtonBox;
private HBox mSearchAndListSelectionBox;
private TextField mSearchField;
private ComboBox<String> mAliasListNameComboBox;
private Button mNewAliasListButton;
private FilteredList<Alias> mAliasFilteredList;
private AliasFilterMonitor mAliasFilterMonitor;
public AliasConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences)
{
mPlaylistManager = playlistManager;
mUserPreferences = userPreferences;
VBox leftBox = new VBox();
VBox.setVgrow(getAliasTableView(), Priority.ALWAYS);
leftBox.getChildren().addAll(getSearchAndListSelectionBox(), getAliasTableView());
HBox topBox = new HBox();
HBox.setHgrow(leftBox, Priority.ALWAYS);
topBox.getChildren().addAll(leftBox, getButtonBox());
setOrientation(Orientation.VERTICAL);
getItems().addAll(topBox, getAliasItemEditor());
}
/**
* Request to show the specified alias in the editor.
*
* Note: this must be called on the FX platform thread
* @param alias to show
*/
public void show(Alias alias)
{
mLog.debug("Showing: " + alias.getName());
if(alias != null)
{
String aliasList = alias.getAliasListName();
if(aliasList == null || aliasList.isEmpty())
{
aliasList = AliasModel.NO_ALIAS_LIST;
}
getAliasListNameComboBox().getSelectionModel().select(aliasList);
getAliasTableView().getSelectionModel().clearSelection();
getAliasTableView().getSelectionModel().select(alias);
getAliasTableView().scrollTo(alias);
}
}
private void setAlias(Alias alias)
{
//Prompt the user to save if the contents of the current channel editor have been modified
if(getAliasItemEditor().modifiedProperty().get())
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.getButtonTypes().clear();
alert.getButtonTypes().addAll(ButtonType.NO, ButtonType.YES);
alert.setTitle("Save Changes");
alert.setHeaderText("Alias configuration has been modified");
alert.setContentText("Do you want to save these changes?");
alert.initOwner(((Node)getButtonBox()).getScene().getWindow());
//Workaround for JavaFX KDE on Linux bug in FX 10/11: https://bugs.openjdk.java.net/browse/JDK-8179073
alert.setResizable(true);
alert.onShownProperty().addListener(e -> {
Platform.runLater(() -> alert.setResizable(false));
});
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.YES)
{
getAliasItemEditor().save();
}
}
getCloneAliasButton().setDisable(alias == null || getAliasTableView().getSelectionModel().getSelectedItems().size() > 1);
getDeleteAliasButton().setDisable(alias == null);
getMoveToAliasButton().setDisable(alias == null);
if(getAliasTableView().getSelectionModel().getSelectedItems().size() <= 1)
{
getAliasItemEditor().setItem(alias);
}
else
{
getAliasItemEditor().setItem(null);
}
}
private AliasItemEditor getAliasItemEditor()
{
if(mAliasItemEditor == null)
{
mAliasItemEditor = new AliasItemEditor(mPlaylistManager, mUserPreferences);
}
return mAliasItemEditor;
}
private HBox getSearchAndListSelectionBox()
{
if(mSearchAndListSelectionBox == null)
{
mSearchAndListSelectionBox = new HBox();
mSearchAndListSelectionBox.setAlignment(Pos.CENTER_LEFT);
mSearchAndListSelectionBox.setPadding(new Insets(10, 0, 10, 10));
mSearchAndListSelectionBox.setSpacing(5);
Label listLabel = new Label("Alias List");
Label searchLabel = new Label("Search");
searchLabel.setAlignment(Pos.CENTER_RIGHT);
HBox searchBox = new HBox();
searchBox.setSpacing(5);
searchBox.getChildren().addAll(searchLabel, getSearchField());
HBox.setHgrow(searchBox, Priority.ALWAYS);
searchBox.setAlignment(Pos.BASELINE_RIGHT);
mSearchAndListSelectionBox.getChildren().addAll(listLabel, getAliasListNameComboBox(),
getNewAliasListButton(), searchBox);
}
return mSearchAndListSelectionBox;
}
private TextField getSearchField()
{
if(mSearchField == null)
{
mSearchField = TextFields.createClearableTextField();
}
return mSearchField;
}
private ComboBox<String> getAliasListNameComboBox()
{
if(mAliasListNameComboBox == null)
{
mAliasListNameComboBox = new ComboBox<>(mPlaylistManager.getAliasModel().aliasListNames());
mAliasListNameComboBox.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> {
getNewAliasButton().setDisable(newValue == null || newValue.contentEquals(AliasModel.NO_ALIAS_LIST));
});
if(mAliasListNameComboBox.getItems().size() > 1)
{
if(!mAliasListNameComboBox.getItems().get(0).contentEquals(AliasModel.NO_ALIAS_LIST))
{
mAliasListNameComboBox.getSelectionModel().select(0);
}
else
{
mAliasListNameComboBox.getSelectionModel().select(1);
}
}
else if(mAliasListNameComboBox.getItems().size() == 1)
{
mAliasListNameComboBox.getSelectionModel().select(0);
}
}
return mAliasListNameComboBox;
}
private Button getNewAliasListButton()
{
if(mNewAliasListButton == null)
{
mNewAliasListButton = new Button("New Alias List");
mNewAliasListButton.setOnAction(event -> {
TextInputDialog dialog = new TextInputDialog();
dialog.setTitle("Create New Alias List");
dialog.setHeaderText("Please enter an alias list name (max 25 chars).");
dialog.setContentText("Name:");
dialog.getEditor().setTextFormatter(new TextFormatter<String>(new MaxLengthUnaryOperator(25)));
Optional<String> result = dialog.showAndWait();
result.ifPresent(s -> {
String name = result.get();
if(name != null && !name.isEmpty())
{
name = name.trim();
mPlaylistManager.getAliasModel().addAliasList(name);
getAliasListNameComboBox().getSelectionModel().select(name);
}
});
});
}
return mNewAliasListButton;
}
private TableView<Alias> getAliasTableView()
{
if(mAliasTableView == null)
{
mAliasTableView = new TableView<>();
TableColumn nameColumn = new TableColumn();
nameColumn.setText("Alias");
nameColumn.setCellValueFactory(new PropertyValueFactory<Alias,String>("name"));
nameColumn.setPrefWidth(140);
TableColumn groupColumn = new TableColumn();
groupColumn.setText("Group");
groupColumn.setCellValueFactory(new PropertyValueFactory<>("group"));
groupColumn.setPrefWidth(140);
TableColumn<Alias,Integer> colorColumn = new TableColumn("Color");
colorColumn.setCellValueFactory(new PropertyValueFactory<>("color"));
colorColumn.setCellFactory(new ColorizedCell());
TableColumn<Alias,String> iconColumn = new TableColumn("Icon");
iconColumn.setCellValueFactory(new PropertyValueFactory<>("iconName"));
iconColumn.setCellFactory(new IconTableCellFactory());
TableColumn<Alias,Integer> priorityColumn = new TableColumn("Listen");
priorityColumn.setCellFactory(new PriorityCellFactory());
priorityColumn.setCellValueFactory(new PropertyValueFactory<>("priority"));
TableColumn<Alias,Boolean> recordColumn = new TableColumn("Record");
recordColumn.setCellValueFactory(new PropertyValueFactory<>("recordable"));
recordColumn.setCellFactory(new IconCell(FontAwesome.SQUARE, Color.RED));
TableColumn<Alias,Boolean> streamColumn = new TableColumn("Stream");
streamColumn.setCellValueFactory(new PropertyValueFactory<>("streamable"));
streamColumn.setCellFactory(new IconCell(FontAwesome.VOLUME_UP, Color.DARKBLUE));
TableColumn<Alias,Integer> idsColumn = new TableColumn("IDs");
idsColumn.setCellValueFactory(new IdentifierCountCell());
TableColumn<Alias,Boolean> errorsColumn = new TableColumn<>("Error");
errorsColumn.setPrefWidth(80);
errorsColumn.setCellValueFactory(new PropertyValueFactory<>("overlap"));
errorsColumn.setCellFactory(param -> {
TableCell<Alias,Boolean> tableCell = new TableCell<>()
{
@Override
protected void updateItem(Boolean item, boolean empty)
{
setAlignment(Pos.CENTER);
setText(null);
if(empty || item == null || !item)
{
setGraphic(null);
}
else
{
IconNode iconNode = new IconNode(FontAwesome.EXCLAMATION_CIRCLE);
iconNode.setFill(Color.RED);
setGraphic(iconNode);
setText("Identifier Overlap");
}
}
};
return tableCell;
});
mAliasTableView.getColumns().addAll(nameColumn, groupColumn, colorColumn, iconColumn, priorityColumn,
recordColumn, streamColumn, idsColumn, errorsColumn);
mAliasTableView.setPlaceholder(getPlaceholderLabel());
//Sorting and filtering for the table
mAliasFilteredList = new FilteredList<>(mPlaylistManager.getAliasModel().aliasList(),
alias -> alias.matchesAliasList(getAliasListNameComboBox().getSelectionModel().getSelectedItem()));
mAliasFilterMonitor = new AliasFilterMonitor();
SortedList<Alias> sortedList = new SortedList<>(mAliasFilteredList);
sortedList.comparatorProperty().bind(mAliasTableView.comparatorProperty());
mAliasTableView.setItems(sortedList);
mAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
mAliasTableView.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> {
setAlias(newValue);
});
}
return mAliasTableView;
}
private Label getPlaceholderLabel()
{
if(mPlaceholderLabel == null)
{
mPlaceholderLabel = new Label("Select an Alias List and click the New button to create new aliases");
}
return mPlaceholderLabel;
}
private VBox getButtonBox()
{
if(mButtonBox == null)
{
mButtonBox = new VBox();
mButtonBox.setPadding(new Insets(10, 10, 10, 10));
mButtonBox.setSpacing(10);
Button fillerButton = new Button();
fillerButton.setVisible(false);
mButtonBox.getChildren().addAll(fillerButton, getNewAliasButton(), getCloneAliasButton(),
getMoveToAliasButton(), getDeleteAliasButton());
}
return mButtonBox;
}
private Button getNewAliasButton()
{
if(mNewAliasButton == null)
{
mNewAliasButton = new Button("New");
mNewAliasButton.setDisable(true);
mNewAliasButton.setAlignment(Pos.CENTER);
mNewAliasButton.setMaxWidth(Double.MAX_VALUE);
mNewAliasButton.setOnAction(event -> {
Alias alias = new Alias();
alias.setAliasListName(getAliasListNameComboBox().getSelectionModel().getSelectedItem());
mPlaylistManager.getAliasModel().addAlias(alias);
//Queue a select alias action to allow table to update filter predicate and display the alias
Platform.runLater(() -> {
getAliasTableView().getSelectionModel().clearSelection();
getAliasTableView().getSelectionModel().select(alias);
getAliasTableView().scrollTo(alias);
});
});
}
return mNewAliasButton;
}
private Button getDeleteAliasButton()
{
if(mDeleteAliasButton == null)
{
mDeleteAliasButton = new Button("Delete");
mDeleteAliasButton.setDisable(true);
mDeleteAliasButton.setMaxWidth(Double.MAX_VALUE);
mDeleteAliasButton.setOnAction(event -> {
boolean multiple = getAliasTableView().getSelectionModel().getSelectedItems().size() > 1;
Alert alert = new Alert(Alert.AlertType.CONFIRMATION,
"Do you want to delete the selected alias" + (multiple ? "es?" : "?"),
ButtonType.NO, ButtonType.YES);
alert.setTitle("Delete Alias");
alert.setHeaderText("Are you sure?");
alert.initOwner(((Node)getDeleteAliasButton()).getScene().getWindow());
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.YES)
{
List<Alias> selectedAliases = new ArrayList<>(getAliasTableView().getSelectionModel().getSelectedItems());
for(Alias selected: selectedAliases)
{
mPlaylistManager.getAliasModel().removeAlias(selected);
}
}
});
}
return mDeleteAliasButton;
}
private Button getCloneAliasButton()
{
if(mCloneAliasButton == null)
{
mCloneAliasButton = new Button("Clone");
mCloneAliasButton.setDisable(true);
mCloneAliasButton.setMaxWidth(Double.MAX_VALUE);
mCloneAliasButton.setOnAction(event -> {
Alias original = getAliasTableView().getSelectionModel().getSelectedItem();
Alias copy = AliasFactory.copyOf(original);
mPlaylistManager.getAliasModel().addAlias(copy);
getAliasTableView().getSelectionModel().clearSelection();
getAliasTableView().getSelectionModel().select(copy);
getAliasTableView().scrollTo(copy);
});
}
return mCloneAliasButton;
}
private MenuButton getMoveToAliasButton()
{
if(mMoveToAliasButton == null)
{
mMoveToAliasButton = new MenuButton("Move To");
mMoveToAliasButton.setDisable(true);
mMoveToAliasButton.setOnShowing(event -> {
mMoveToAliasButton.getItems().clear();
MenuItem emptyItem = new MenuItem("Alias Lists");
emptyItem.setDisable(true);
mMoveToAliasButton.getItems().addAll(emptyItem, new SeparatorMenuItem());
List<String> aliasLists = mPlaylistManager.getAliasModel().getListNames();
for(String aliasList: aliasLists)
{
if(!aliasList.contentEquals(AliasModel.NO_ALIAS_LIST) &&
!aliasList.contentEquals(getAliasListNameComboBox().getSelectionModel().getSelectedItem()))
{
mMoveToAliasButton.getItems().add(new MoveToAliasListItem(aliasList));
}
}
});
}
return mMoveToAliasButton;
}
public class MoveToAliasListItem extends MenuItem
{
public MoveToAliasListItem(String aliasList)
{
super(aliasList);
setOnAction(event -> {
List<Alias> selectedAliases = new ArrayList<>(getAliasTableView().getSelectionModel().getSelectedItems());
for(Alias selected: selectedAliases)
{
AliasList existing = mPlaylistManager.getAliasModel().getAliasList(selected.getAliasListName());
existing.removeAlias(selected);
selected.setAliasListName(getText());
AliasList moveToList = mPlaylistManager.getAliasModel().getAliasList(selected.getAliasListName());
moveToList.addAlias(selected);
}
});
}
}
public class ColorizedCell implements Callback<TableColumn<Alias,Integer>,TableCell<Alias,Integer>>
{
@Override
public TableCell<Alias,Integer> call(TableColumn<Alias,Integer> param)
{
final Rectangle rectangle = new Rectangle(20,20);
rectangle.setArcHeight(10);
rectangle.setArcWidth(10);
TableCell<Alias,Integer> tableCell = new TableCell<>()
{
@Override
protected void updateItem(Integer item, boolean empty)
{
super.updateItem(item, empty);
if(!empty && getTableRow() != null)
{
Alias alias = getTableRow().getItem();
if(alias != null)
{
rectangle.setVisible(true);
rectangle.setFill(ColorUtil.fromInteger(alias.getColor()));
}
else
{
rectangle.setVisible(false);
}
}
else
{
rectangle.setVisible(false);
}
}
};
tableCell.setAlignment(Pos.CENTER);
tableCell.setGraphic(rectangle);
return tableCell;
}
}
/**
* Boolean table cell with an icon visibility bound to the boolean value
*/
public class IconCell implements Callback<TableColumn<Alias,Boolean>,TableCell<Alias,Boolean>>
{
private IconCode mIconCode;
private Color mColor;
public IconCell(IconCode iconCode, Color color)
{
mIconCode = iconCode;
mColor = color;
}
@Override
public TableCell<Alias,Boolean> call(TableColumn<Alias,Boolean> param)
{
final IconNode iconNode = new IconNode(mIconCode);
iconNode.setIconSize(20);
iconNode.setFill(mColor);
TableCell<Alias,Boolean> tableCell = new TableCell<>()
{
@Override
protected void updateItem(Boolean item, boolean empty)
{
super.updateItem(item, empty);
if(!empty && getTableRow() != null)
{
iconNode.setVisible(item);
}
else
{
iconNode.setVisible(false);
}
}
};
tableCell.setAlignment(Pos.CENTER);
tableCell.setGraphic(iconNode);
return tableCell;
}
}
public class IdentifierCountCell implements Callback<TableColumn.CellDataFeatures<Alias,Integer>,ObservableValue<Integer>>
{
@Override
public ObservableValue<Integer> call(TableColumn.CellDataFeatures<Alias,Integer> param)
{
if(param.getValue() != null)
{
return param.getValue().nonAudioIdentifierCountProperty().asObject();
}
return null;
}
}
public class ActionCountCell implements Callback<TableColumn.CellDataFeatures<Alias,Integer>,ObservableValue<Integer>>
{
@Override
public ObservableValue<Integer> call(TableColumn.CellDataFeatures<Alias,Integer> param)
{
Integer count = null;
if(param.getValue() != null && param.getValue().getAliasActions().size() > 0)
{
count = param.getValue().getAliasActions().size();
}
return new ReadOnlyObjectWrapper<>(count);
}
}
public class CenteredCountCellFactory implements Callback<TableColumn<Alias,Integer>,TableCell<Alias,Integer>>
{
@Override
public TableCell<Alias,Integer> call(TableColumn<Alias,Integer> param)
{
return new CenteredCountCell();
}
}
public class CenteredCountCell extends TableCell<Alias,Integer>
{
public CenteredCountCell()
{
setAlignment(Pos.CENTER);
}
}
public class PriorityCellFactory implements Callback<TableColumn<Alias, Integer>, TableCell<Alias, Integer>>
{
@Override
public TableCell<Alias, Integer> call(TableColumn<Alias, Integer> param)
{
TableCell tableCell = new TableCell<Alias,Integer>()
{
@Override
protected void updateItem(Integer item, boolean empty)
{
if(empty)
{
setText(null);
setGraphic(null);
}
else if(item == io.github.dsheirer.alias.id.priority.Priority.DO_NOT_MONITOR)
{
setText("Mute");
final IconNode iconNode = new IconNode(FontAwesome.VOLUME_OFF);
iconNode.setIconSize(20);
iconNode.setFill(Color.RED);
setGraphic(iconNode);
}
else if(item == io.github.dsheirer.alias.id.priority.Priority.DEFAULT_PRIORITY)
{
setText("Default");
final IconNode iconNode = new IconNode(FontAwesome.VOLUME_UP);
iconNode.setIconSize(20);
iconNode.setFill(Color.GREEN);
setGraphic(iconNode);
}
else
{
setText(item.toString());
final IconNode iconNode = new IconNode(FontAwesome.VOLUME_UP);
iconNode.setIconSize(20);
iconNode.setFill(Color.GREEN);
setGraphic(iconNode);
}
}
};
return tableCell;
}
}
public class IconTableCellFactory implements Callback<TableColumn<Alias, String>, TableCell<Alias, String>>
{
@Override
public TableCell<Alias, String> call(TableColumn<Alias, String> param)
{
TableCell<Alias,String> tableCell = new TableCell<>()
{
@Override
protected void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
setAlignment(Pos.CENTER);
if(empty)
{
setGraphic(null);
}
else
{
if(getTableRow() != null)
{
Alias alias = getTableRow().getItem();
if(alias != null)
{
Icon icon = mPlaylistManager.getIconManager().getModel().getIcon(alias.getIconName());
try
{
Image image = new Image(icon.getPath(), 0, 16, true, true);
setGraphic(new ImageView(image));
}
catch(Exception e)
{
}
}
}
}
}
};
return tableCell;
}
}
/**
* Updates the filtered set of aliases any time there is a change in the selected alias list name box, or when the
* user types text in the search box.
*/
public class AliasFilterMonitor
{
public AliasFilterMonitor()
{
getAliasListNameComboBox().getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> {
refresh();
});
getSearchField().textProperty().addListener((observable, oldValue, newValue) -> {
refresh();
});
}
public void refresh()
{
final String aliasListName = getAliasListNameComboBox().getSelectionModel().getSelectedItem();
final String filter = getSearchField().getText();
mAliasFilteredList.setPredicate(alias -> {
if(filter == null || filter.isEmpty())
{
return alias.matchesAliasList(aliasListName);
}
String lowerFilter = filter.toLowerCase();
if(alias.getName() != null && alias.getName().toLowerCase().contains(lowerFilter))
{
return alias.matchesAliasList(aliasListName);
}
else if(alias.getGroup() != null && alias.getGroup().toLowerCase().contains(lowerFilter))
{
return alias.matchesAliasList(aliasListName);
}
return false;
});
}
}
}

View File

@@ -0,0 +1,143 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import javafx.geometry.Insets;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
/**
* Primary alias editor with tabbed panes for view-by alias editing support
*/
public class AliasEditor extends TabPane
{
private PlaylistManager mPlaylistManager;
private UserPreferences mUserPreferences;
private AliasConfigurationEditor mAliasConfigurationEditor;
private AliasViewByIdentifierEditor mAliasViewByIdentifierEditor;
private Tab mAliasConfigurationTab;
private Tab mAliasIdentifierTab;
private Tab mAliasRecordingTab;
/**
* Constructs an instance
* @param playlistManager for alias model access
* @param userPreferences for settings
*/
public AliasEditor(PlaylistManager playlistManager, UserPreferences userPreferences)
{
mPlaylistManager = playlistManager;
mUserPreferences = userPreferences;
setPadding(new Insets(4,0,0,0));
setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
Tab viewByTab = new Tab("View By:");
viewByTab.setDisable(true);
getTabs().addAll(viewByTab, getAliasConfigurationTab(), getAliasIdentifierTab(), getAliasRecordingTab());
}
/**
* Processes the alias view request.
*
* Note: this method must be invoked on the JavaFX platform thread
*
* @param aliasTabRequest to process
*/
public void process(AliasTabRequest aliasTabRequest)
{
if(aliasTabRequest instanceof ViewAliasRequest)
{
Alias alias = ((ViewAliasRequest)aliasTabRequest).getAlias();
if(alias != null)
{
getSelectionModel().select(getAliasConfigurationTab());
getAliasConfigurationEditor().show(alias);
}
}
else if(aliasTabRequest instanceof ViewAliasIdentifierRequest)
{
AliasID aliasID = ((ViewAliasIdentifierRequest)aliasTabRequest).getAliasId();
if(aliasID != null)
{
getSelectionModel().select(getAliasIdentifierTab());
getAliasViewByIdentifierEditor().show(aliasID);
}
}
}
private Tab getAliasConfigurationTab()
{
if(mAliasConfigurationTab == null)
{
mAliasConfigurationTab = new Tab("Alias");
mAliasConfigurationTab.setContent(getAliasConfigurationEditor());
}
return mAliasConfigurationTab;
}
private AliasConfigurationEditor getAliasConfigurationEditor()
{
if(mAliasConfigurationEditor == null)
{
mAliasConfigurationEditor = new AliasConfigurationEditor(mPlaylistManager, mUserPreferences);
}
return mAliasConfigurationEditor;
}
private Tab getAliasIdentifierTab()
{
if(mAliasIdentifierTab == null)
{
mAliasIdentifierTab = new Tab("Identifier");
mAliasIdentifierTab.setContent(getAliasViewByIdentifierEditor());
}
return mAliasIdentifierTab;
}
private AliasViewByIdentifierEditor getAliasViewByIdentifierEditor()
{
if(mAliasViewByIdentifierEditor == null)
{
mAliasViewByIdentifierEditor = new AliasViewByIdentifierEditor(mPlaylistManager);
}
return mAliasViewByIdentifierEditor;
}
private Tab getAliasRecordingTab()
{
if(mAliasRecordingTab == null)
{
mAliasRecordingTab = new Tab("Record");
mAliasRecordingTab.setContent(new AliasViewByRecordingEditor(mPlaylistManager));
}
return mAliasRecordingTab;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias;
import io.github.dsheirer.gui.playlist.PlaylistEditorRequest;
public abstract class AliasTabRequest extends PlaylistEditorRequest
{
@Override
public TabName getTabName()
{
return TabName.ALIAS;
}
}

View File

@@ -0,0 +1,314 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.playlist.PlaylistManager;
import javafx.beans.Observable;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.transformation.FilteredList;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
public class AliasViewByIdentifierEditor extends VBox
{
private static final Logger mLog = LoggerFactory.getLogger(AliasViewByIdentifierEditor.class);
private PlaylistManager mPlaylistManager;
private ComboBox<String> mAliasListNameComboBox;
private ComboBox<AliasIDType> mAliasIDTypeComboBox;
private TableView<AliasAndIdentifier> mAliasAndIdentifierTableView;
private TableColumn<AliasAndIdentifier,String> mIdentifierColumn;
private Button mViewAliasButton;
public AliasViewByIdentifierEditor(PlaylistManager playlistManager)
{
mPlaylistManager = playlistManager;
mPlaylistManager.getAliasModel().aliasList().addListener((ListChangeListener<Alias>)c -> {
//Refresh the list any time that the aliases change since we're using a static snapshot
AliasIDType selected = getAliasIDTypeComboBox().getSelectionModel().getSelectedItem();
getAliasIDTypeComboBox().getSelectionModel().select(null);
getAliasIDTypeComboBox().getSelectionModel().select(selected);
});
GridPane gridPane = new GridPane();
gridPane.setPadding(new Insets(10,10,10,10));
gridPane.setVgap(10);
gridPane.setHgap(10);
Label aliasListLabel = new Label("Alias List");
GridPane.setHalignment(aliasListLabel, HPos.RIGHT);
GridPane.setConstraints(aliasListLabel, 0, 0);
gridPane.getChildren().add(aliasListLabel);
GridPane.setConstraints(getAliasListNameComboBox(), 1, 0);
GridPane.setHgrow(getAliasListNameComboBox(), Priority.ALWAYS);
gridPane.getChildren().add(getAliasListNameComboBox());
Label identifierLabel = new Label("Identifier Type");
GridPane.setHalignment(identifierLabel, HPos.RIGHT);
GridPane.setConstraints(identifierLabel, 0, 1);
gridPane.getChildren().add(identifierLabel);
GridPane.setConstraints(getAliasIDTypeComboBox(), 1, 1);
GridPane.setHgrow(getAliasIDTypeComboBox(), Priority.ALWAYS);
gridPane.getChildren().add(getAliasIDTypeComboBox());
HBox tableAndButtonBox = new HBox();
tableAndButtonBox.setPadding(new Insets(0,10,0,0));
tableAndButtonBox.setSpacing(10);
HBox.setHgrow(getAliasAndIdentifierTableView(), Priority.ALWAYS);
tableAndButtonBox.getChildren().addAll(getAliasAndIdentifierTableView(), getViewAliasButton());
VBox.setVgrow(tableAndButtonBox, Priority.ALWAYS);
getChildren().addAll(gridPane, tableAndButtonBox);
updateList();
}
private void updateList()
{
getAliasAndIdentifierTableView().getItems().clear();
String aliasList = getAliasListNameComboBox().getSelectionModel().getSelectedItem();
AliasIDType type = getAliasIDTypeComboBox().getSelectionModel().getSelectedItem();
List<Alias> aliases = mPlaylistManager.getAliasModel().getAliases(aliasList, type);
for(Alias alias: aliases)
{
for(AliasID aliasID: alias.getAliasIdentifiers())
{
if(aliasID.getType() == type)
{
getAliasAndIdentifierTableView().getItems().add(new AliasAndIdentifier(alias, aliasID));
}
}
}
getAliasAndIdentifierTableView().getSortOrder().setAll(mIdentifierColumn);
}
/**
* Selects the Alias and Identifier that matches the alias ID in the list.
* @param aliasID
*/
public void show(AliasID aliasID)
{
if(aliasID != null)
{
AliasIDType type = aliasID.getType();
if(getAliasIDTypeComboBox().getItems().contains(type))
{
getAliasIDTypeComboBox().getSelectionModel().select(type);
for(AliasAndIdentifier aliasAndIdentifier: getAliasAndIdentifierTableView().getItems())
{
if(aliasAndIdentifier.getAliasIdentifier().matches(aliasID))
{
getAliasAndIdentifierTableView().getSelectionModel().select(aliasAndIdentifier);
getAliasAndIdentifierTableView().scrollTo(aliasAndIdentifier);
return;
}
}
}
Alert alert = new Alert(Alert.AlertType.INFORMATION, "Can't find matching identifier in list",
ButtonType.OK);
alert.setTitle("No Match Found");
alert.setContentText("No Match Found");
alert.initOwner(getAliasAndIdentifierTableView().getScene().getWindow());
alert.showAndWait();
}
}
private Button getViewAliasButton()
{
if(mViewAliasButton == null)
{
mViewAliasButton = new Button(("View Alias"));
mViewAliasButton.setDisable(true);
mViewAliasButton.setOnAction(event -> {
AliasAndIdentifier selected = getAliasAndIdentifierTableView().getSelectionModel().getSelectedItem();
if(selected != null)
{
MyEventBus.getEventBus().post(new ViewAliasRequest(selected.getAlias()));
}
});
}
return mViewAliasButton;
}
private TableView<AliasAndIdentifier> getAliasAndIdentifierTableView()
{
if(mAliasAndIdentifierTableView == null)
{
mAliasAndIdentifierTableView = new TableView(FXCollections.observableArrayList(AliasAndIdentifier.extractor()));
mAliasAndIdentifierTableView.setPlaceholder(new Label("No aliases or identifiers available"));
mIdentifierColumn = new TableColumn<>("Identifier");
mIdentifierColumn.setCellValueFactory(new PropertyValueFactory<>("identifier"));
mIdentifierColumn.setPrefWidth(350);
TableColumn aliasColumn = new TableColumn();
aliasColumn.setText("Alias");
aliasColumn.setCellValueFactory(new PropertyValueFactory<>("alias"));
aliasColumn.setPrefWidth(200);
TableColumn groupColumn = new TableColumn();
groupColumn.setText("Group");
groupColumn.setCellValueFactory(new PropertyValueFactory<>("group"));
groupColumn.setPrefWidth(200);
mAliasAndIdentifierTableView.getColumns().addAll(mIdentifierColumn, aliasColumn, groupColumn);
mAliasAndIdentifierTableView.setMaxHeight(Double.MAX_VALUE);
mAliasAndIdentifierTableView.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> {
getViewAliasButton().setDisable(newValue == null);
});
}
return mAliasAndIdentifierTableView;
}
private ComboBox<String> getAliasListNameComboBox()
{
if(mAliasListNameComboBox == null)
{
Predicate<String> filterPredicate = s -> !s.contentEquals(AliasModel.NO_ALIAS_LIST);
FilteredList<String> filteredChannelList =
new FilteredList<>(mPlaylistManager.getAliasModel().aliasListNames(), filterPredicate);
mAliasListNameComboBox = new ComboBox<>(filteredChannelList);
mAliasListNameComboBox.setPadding(new Insets(0,10,0,0));
if(mAliasListNameComboBox.getItems().size() > 0)
{
mAliasListNameComboBox.getSelectionModel().select(0);
}
mAliasListNameComboBox.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> {
updateList();
});
}
return mAliasListNameComboBox;
}
private ComboBox<AliasIDType> getAliasIDTypeComboBox()
{
if(mAliasIDTypeComboBox == null)
{
mAliasIDTypeComboBox = new ComboBox<>();
List<AliasIDType> values = new ArrayList<>(AliasIDType.VIEW_BY_VALUES);
Collections.sort(values, (o1, o2) -> o1.toString().compareTo(o2.toString()));
mAliasIDTypeComboBox.getItems().addAll(values);
if(mAliasIDTypeComboBox.getItems().size() > 0)
{
mAliasIDTypeComboBox.getSelectionModel().select(AliasIDType.TALKGROUP);
}
mAliasIDTypeComboBox.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> updateList());
}
return mAliasIDTypeComboBox;
}
public static class AliasAndIdentifier
{
private Alias mAlias;
private AliasID mAliasIdentifier;
private StringProperty mName = new SimpleStringProperty();
private StringProperty mGroup = new SimpleStringProperty();
private StringProperty mIdentifier = new SimpleStringProperty();
public AliasAndIdentifier(Alias alias, AliasID aliasID)
{
mAlias = alias;
mAliasIdentifier = aliasID;
mName.bind(mAlias.nameProperty());
mGroup.bind(mAlias.groupProperty());
mIdentifier.bind(mAliasIdentifier.valueProperty());
}
public StringProperty nameProperty()
{
return mName;
}
public StringProperty groupProperty()
{
return mGroup;
}
public StringProperty identifierProperty()
{
return mIdentifier;
}
public Alias getAlias()
{
return mAlias;
}
public AliasID getAliasIdentifier()
{
return mAliasIdentifier;
}
/**
* Creates an observable property extractor for use with observable lists to detect changes internal to this object.
*/
public static Callback<AliasAndIdentifier,Observable[]> extractor()
{
return (AliasAndIdentifier a) -> new Observable[] {a.groupProperty(), a.identifierProperty(), a.nameProperty()};
}
}
}

View File

@@ -0,0 +1,510 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasEvent;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.playlist.PlaylistManager;
import javafx.collections.ListChangeListener;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconNode;
import org.controlsfx.control.textfield.TextFields;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class AliasViewByRecordingEditor extends VBox
{
private final static Logger mLog = LoggerFactory.getLogger(AliasViewByRecordingEditor.class);
private PlaylistManager mPlaylistManager;
private ComboBox<String> mAliasListNameComboBox;
private HBox mTopBox;
private TextField mSearchField;
private TableView<Alias> mNoRecordAliasTableView;
private TableView<Alias> mRecordAliasTableView;
private Button mAddButton;
private Button mAddAllButton;
private Button mRemoveButton;
private Button mRemoveAllButton;
private FilteredList<Alias> mNoRecordFilteredList;
private FilteredList<Alias> mRecordFilteredList;
private AvailablePredicate mNoRecordPredicate;
private SelectedPredicate mRecordPredicate;
public AliasViewByRecordingEditor(PlaylistManager playlistManager)
{
mPlaylistManager = playlistManager;
VBox buttonBox = new VBox();
buttonBox.setMaxHeight(Double.MAX_VALUE);
buttonBox.setAlignment(Pos.CENTER);
buttonBox.setSpacing(5);
buttonBox.getChildren().addAll(getAddAllButton(), getAddButton(), getRemoveButton(), getRemoveAllButton());
VBox availableBox = new VBox();
availableBox.setMaxHeight(Double.MAX_VALUE);
VBox.setVgrow(getNoRecordAliasTableView(), Priority.ALWAYS);
availableBox.getChildren().addAll(new Label("Don't Record Audio"), getNoRecordAliasTableView());
VBox selectedBox = new VBox();
selectedBox.setMaxHeight(Double.MAX_VALUE);
VBox.setVgrow(getRecordAliasTableView(), Priority.ALWAYS);
selectedBox.getChildren().addAll(new Label("Record Audio"), getRecordAliasTableView());
HBox listsBox = new HBox();
listsBox.setMaxHeight(Double.MAX_VALUE);
listsBox.setPadding(new Insets(0,10,10,10));
listsBox.setSpacing(10);
HBox.setHgrow(availableBox, Priority.ALWAYS);
HBox.setHgrow(selectedBox, Priority.ALWAYS);
listsBox.getChildren().addAll(availableBox, buttonBox, selectedBox);
VBox.setVgrow(listsBox, Priority.ALWAYS);
getChildren().addAll(getTopBox(), listsBox);
}
private ComboBox<String> getAliasListNameComboBox()
{
if(mAliasListNameComboBox == null)
{
Predicate<String> filterPredicate = s -> !s.contentEquals(AliasModel.NO_ALIAS_LIST);
FilteredList<String> filteredChannelList =
new FilteredList<>(mPlaylistManager.getAliasModel().aliasListNames(), filterPredicate);
mAliasListNameComboBox = new ComboBox<>(filteredChannelList);
mAliasListNameComboBox.setPadding(new Insets(0,10,0,0));
mAliasListNameComboBox.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> {
updateListFilters();
});
if(mAliasListNameComboBox.getItems().size() > 0)
{
mAliasListNameComboBox.getSelectionModel().select(0);
}
}
return mAliasListNameComboBox;
}
private FilteredList<Alias> getNoRecordFilteredList()
{
if(mNoRecordFilteredList == null)
{
mNoRecordFilteredList = new FilteredList<>(mPlaylistManager.getAliasModel().aliasList(),
getNoRecordPredicate());
}
return mNoRecordFilteredList;
}
private FilteredList<Alias> getRecordFilteredList()
{
if(mRecordFilteredList == null)
{
mRecordFilteredList = new FilteredList<>(mPlaylistManager.getAliasModel().aliasList(),
getRecordPredicate());
}
return mRecordFilteredList;
}
private HBox getTopBox()
{
if(mTopBox == null)
{
mTopBox = new HBox();
mTopBox.setAlignment(Pos.CENTER_LEFT);
mTopBox.setPadding(new Insets(10, 10, 10, 10));
mTopBox.setSpacing(15);
HBox aliasListBox = new HBox();
aliasListBox.setAlignment(Pos.CENTER);
aliasListBox.setSpacing(5);
Label aliasListLabel = new Label("Alias List");
aliasListBox.getChildren().addAll(aliasListLabel, getAliasListNameComboBox());
HBox searchBox = new HBox();
searchBox.setAlignment(Pos.CENTER);
searchBox.setSpacing(5);
Label searchLabel = new Label("Search");
searchBox.getChildren().addAll(searchLabel, getSearchField());
mTopBox.getChildren().addAll(aliasListBox, searchBox);
}
return mTopBox;
}
private TextField getSearchField()
{
if(mSearchField == null)
{
mSearchField = TextFields.createClearableTextField();
mSearchField.textProperty().addListener((observable, oldValue, newValue) -> {
updateListFilters();
});
}
return mSearchField;
}
/**
* Updates the filters applied to the available and selected alias lists
*/
private void updateListFilters()
{
String aliasList = getAliasListNameComboBox().getSelectionModel().getSelectedItem();
String filterText = getSearchField().getText();
mNoRecordPredicate.setAliasListName(aliasList);
mNoRecordPredicate.setFilterText(filterText);
getNoRecordFilteredList().setPredicate(null);
getNoRecordFilteredList().setPredicate(mNoRecordPredicate);
mRecordPredicate.setAliasListName(aliasList);
mRecordPredicate.setFilterText(filterText);
getRecordFilteredList().setPredicate(null);
getRecordFilteredList().setPredicate(mRecordPredicate);
}
private AvailablePredicate getNoRecordPredicate()
{
if(mNoRecordPredicate == null)
{
mNoRecordPredicate = new AvailablePredicate();
}
return mNoRecordPredicate;
}
private SelectedPredicate getRecordPredicate()
{
if(mRecordPredicate == null)
{
mRecordPredicate = new SelectedPredicate();
}
return mRecordPredicate;
}
private TableView<Alias> getNoRecordAliasTableView()
{
if(mNoRecordAliasTableView == null)
{
mNoRecordAliasTableView = new TableView<>();
mNoRecordAliasTableView.setMaxHeight(Double.MAX_VALUE);
mNoRecordAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
TableColumn nameColumn = new TableColumn();
nameColumn.setText("Alias");
nameColumn.setPrefWidth(200);
nameColumn.setCellValueFactory(new PropertyValueFactory<Alias,String>("name"));
TableColumn groupColumn = new TableColumn();
groupColumn.setText("Group");
groupColumn.setPrefWidth(200);
groupColumn.setCellValueFactory(new PropertyValueFactory<>("group"));
mNoRecordAliasTableView.getColumns().addAll(nameColumn, groupColumn);
mNoRecordAliasTableView.setPlaceholder(new Label("No non-recordable aliases available"));
mNoRecordAliasTableView.getSelectionModel().getSelectedItems().addListener((ListChangeListener<Alias>)c -> {
int selectedCount = mNoRecordAliasTableView.getSelectionModel().getSelectedItems().size();
getAddButton().setDisable(selectedCount != 1);
getAddAllButton().setDisable(selectedCount < 2);
if(selectedCount > 0)
{
getRecordAliasTableView().getSelectionModel().clearSelection();
}
});
SortedList<Alias> sortedList = new SortedList<>(getNoRecordFilteredList());
sortedList.comparatorProperty().bind(mNoRecordAliasTableView.comparatorProperty());
mNoRecordAliasTableView.setItems(sortedList);
}
return mNoRecordAliasTableView;
}
private TableView<Alias> getRecordAliasTableView()
{
if(mRecordAliasTableView == null)
{
mRecordAliasTableView = new TableView<>();
mRecordAliasTableView.setMaxHeight(Double.MAX_VALUE);
mRecordAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
TableColumn nameColumn = new TableColumn();
nameColumn.setText("Alias");
nameColumn.setPrefWidth(200);
nameColumn.setCellValueFactory(new PropertyValueFactory<Alias,String>("name"));
TableColumn groupColumn = new TableColumn();
groupColumn.setText("Group");
groupColumn.setPrefWidth(200);
groupColumn.setCellValueFactory(new PropertyValueFactory<>("group"));
mRecordAliasTableView.getColumns().addAll(nameColumn, groupColumn);
mRecordAliasTableView.setPlaceholder(new Label("No recordable aliases available"));
mRecordAliasTableView.getSelectionModel().getSelectedItems().addListener((ListChangeListener<Alias>)c -> {
int selectedCount = mRecordAliasTableView.getSelectionModel().getSelectedItems().size();
getRemoveButton().setDisable(selectedCount != 1);
getRemoveAllButton().setDisable(selectedCount < 2);
if(selectedCount > 0)
{
getNoRecordAliasTableView().getSelectionModel().clearSelection();
}
});
SortedList<Alias> sortedList = new SortedList<>(getRecordFilteredList());
sortedList.comparatorProperty().bind(mRecordAliasTableView.comparatorProperty());
mRecordAliasTableView.setItems(sortedList);
}
return mRecordAliasTableView;
}
private Button getAddButton()
{
if(mAddButton == null)
{
mAddButton = new Button();
mAddButton.setDisable(true);
mAddButton.setMaxWidth(Double.MAX_VALUE);
mAddButton.setGraphic(new IconNode(FontAwesome.ANGLE_RIGHT));
mAddButton.setAlignment(Pos.CENTER);
mAddButton.setOnAction(event -> {
Alias selectedAlias = getNoRecordAliasTableView().getSelectionModel().getSelectedItem();
if(selectedAlias != null)
{
selectedAlias.setRecordable(true);
mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE));
updateListFilters();
}
});
}
return mAddButton;
}
private Button getAddAllButton()
{
if(mAddAllButton == null)
{
mAddAllButton = new Button();
mAddAllButton.setDisable(true);
mAddAllButton.setMaxWidth(Double.MAX_VALUE);
mAddAllButton.setGraphic(new IconNode(FontAwesome.ANGLE_DOUBLE_RIGHT));
mAddAllButton.setAlignment(Pos.CENTER);
mAddAllButton.setOnAction(event -> {
List<Alias> selectedAliases =
new ArrayList(getNoRecordAliasTableView().getSelectionModel().getSelectedItems());
if(!selectedAliases.isEmpty())
{
for(Alias selectedAlias: selectedAliases)
{
selectedAlias.setRecordable(true);
mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE));
}
updateListFilters();
}
});
}
return mAddAllButton;
}
private Button getRemoveButton()
{
if(mRemoveButton == null)
{
mRemoveButton = new Button();
mRemoveButton.setDisable(true);
mRemoveButton.setMaxWidth(Double.MAX_VALUE);
mRemoveButton.setGraphic(new IconNode(FontAwesome.ANGLE_LEFT));
mRemoveButton.setAlignment(Pos.CENTER);
mRemoveButton.setOnAction(event -> {
Alias selectedAlias = getRecordAliasTableView().getSelectionModel().getSelectedItem();
if(selectedAlias != null)
{
selectedAlias.setRecordable(false);
mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE));
updateListFilters();
}
});
}
return mRemoveButton;
}
private Button getRemoveAllButton()
{
if(mRemoveAllButton == null)
{
mRemoveAllButton = new Button();
mRemoveAllButton.setDisable(true);
mRemoveAllButton.setMaxWidth(Double.MAX_VALUE);
mRemoveAllButton.setGraphic(new IconNode(FontAwesome.ANGLE_DOUBLE_LEFT));
mRemoveAllButton.setAlignment(Pos.CENTER);
mRemoveAllButton.setOnAction(event -> {
List<Alias> selectedAliases =
new ArrayList(getRecordAliasTableView().getSelectionModel().getSelectedItems());
if(!selectedAliases.isEmpty())
{
for(Alias selectedAlias: selectedAliases)
{
selectedAlias.setRecordable(false);
mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE));
}
updateListFilters();
}
});
}
return mRemoveAllButton;
}
public static class AvailablePredicate implements Predicate<Alias>
{
private String mAliasListName;
private String mFilterText;
public AvailablePredicate()
{
}
public void setAliasListName(String aliasListName)
{
mAliasListName = aliasListName;
}
public void setFilterText(String filterText)
{
mFilterText = (filterText == null || filterText.isEmpty()) ? null : filterText.toLowerCase();
}
@Override
public boolean test(Alias alias)
{
if(mAliasListName == null || alias.isRecordable() || alias.getAliasListName() == null)
{
return false;
}
if(mAliasListName.contentEquals(alias.getAliasListName()))
{
if(mFilterText == null)
{
return true;
}
else
{
if(alias.getGroup() != null && alias.getGroup().toLowerCase().contains(mFilterText))
{
return true;
}
if(alias.getName() != null && alias.getName().toLowerCase().contains(mFilterText))
{
return true;
}
}
}
return false;
}
}
public static class SelectedPredicate implements Predicate<Alias>
{
private String mAliasListName;
private String mFilterText;
public SelectedPredicate()
{
}
public void setAliasListName(String aliasListName)
{
mAliasListName = aliasListName;
}
public void setFilterText(String filterText)
{
mFilterText = (filterText == null || filterText.isEmpty()) ? null : filterText.toLowerCase();
}
@Override
public boolean test(Alias alias)
{
if(mAliasListName == null || !alias.isRecordable() || alias.getAliasListName() == null)
{
return false;
}
if(mAliasListName.contentEquals(alias.getAliasListName()))
{
if(mFilterText == null)
{
return true;
}
else
{
if(alias.getGroup() != null && alias.getGroup().toLowerCase().contains(mFilterText))
{
return true;
}
if(alias.getName() != null && alias.getName().toLowerCase().contains(mFilterText))
{
return true;
}
}
}
return false;
}
}
}

View File

@@ -0,0 +1,97 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.alias;
import javafx.scene.paint.Color;
/**
* Utilities for JavaFX colors
*
* Note: the 32-bit integer representation used by this class is compatible with Swing color representations where the
* value represents: ARGB (alpha, red, green, blue). This is different from the way that JavaFX represents colors
* which is RGBA. This utility uses the Swing representation for backward compatiblity with existing user playlists.
*/
public class ColorUtil
{
/**
* Converts the color to a 32 bit integer value
* @param color to convert
* @return 32 bit integer representation
*/
public static int toInteger(Color color)
{
return to32BitInteger((int)(color.getRed() * 255),
(int)(color.getGreen() * 255),
(int)(color.getBlue() * 255),
(int)(color.getOpacity() * 255));
}
/**
* Converts the RGBA values to a 32-bit integer representation
* @param red 0-255
* @param green 0-255
* @param blue 0-255
* @param alpha 0-255
* @return integer representation
*/
private static int to32BitInteger(int red, int green, int blue, int alpha)
{
int i = alpha;
i = i << 8;
i = i | red;
i = i << 8;
i = i | green;
i = i << 8;
i = i | blue;
return i;
}
/**
* Creates a color object from the 32-bit integer representation
* @param value that represents an RGBA color
* @return color instance
*/
public static Color fromInteger(int value)
{
int blue = value & 0xFF;
value = value >> 8;
int green = value & 0xFF;
value = value >> 8;
int red = value & 0xFF;
value = value >> 8;
double alpha = (double)(value & 0xFF) / 255.0;
Color color = Color.rgb(red, green, blue, alpha);
return color;
}
public static void main(String[] args)
{
int rgb = -1;
Color color = ColorUtil.fromInteger(rgb);
int converted = ColorUtil.toInteger(color);
}
}

View File

@@ -0,0 +1,44 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias;
import io.github.dsheirer.alias.id.AliasID;
/**
* Request to view an alias identifier in the alias view by identifier editor
*/
public class ViewAliasIdentifierRequest extends AliasTabRequest
{
private AliasID mAliasId;
/**
* Constructs an instance
* @param aliasId to view
*/
public ViewAliasIdentifierRequest(AliasID aliasId)
{
mAliasId = aliasId;
}
public AliasID getAliasId()
{
return mAliasId;
}
}

View File

@@ -0,0 +1,44 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias;
import io.github.dsheirer.alias.Alias;
/**
* Request to view an alias in the alias editor
*/
public class ViewAliasRequest extends AliasTabRequest
{
private Alias mAlias;
/**
* Constructs an instance
* @param alias to view
*/
public ViewAliasRequest(Alias alias)
{
mAlias = alias;
}
public Alias getAlias()
{
return mAlias;
}
}

View File

@@ -0,0 +1,31 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.action;
import io.github.dsheirer.alias.action.AliasAction;
import io.github.dsheirer.gui.playlist.Editor;
/**
* Base action editor class
* @param <T> aliasAction to edit
*/
public abstract class ActionEditor<T extends AliasAction> extends Editor<T>
{
}

View File

@@ -0,0 +1,48 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.action;
import io.github.dsheirer.alias.action.AliasActionType;
import io.github.dsheirer.preference.UserPreferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Factory for creating JavaFX editors for alias identifiers
*/
public class ActionEditorFactory
{
private static final Logger mLog = LoggerFactory.getLogger(ActionEditorFactory.class);
public static ActionEditor getEditor(AliasActionType type, UserPreferences userPreferences)
{
switch(type)
{
case BEEP:
return new BeepEditor();
case CLIP:
return new ClipEditor();
case SCRIPT:
return new ScriptEditor();
default:
return new UnrecognizedActionEditor();
}
}
}

View File

@@ -0,0 +1,313 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.action;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasFactory;
import io.github.dsheirer.alias.action.AliasAction;
import io.github.dsheirer.alias.action.RecurringAction;
import io.github.dsheirer.alias.action.beep.BeepAction;
import io.github.dsheirer.gui.control.IntegerFormatter;
import io.github.dsheirer.util.ThreadPool;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Beep action editor
*/
public class BeepEditor extends ActionEditor<BeepAction>
{
private static final Logger mLog = LoggerFactory.getLogger(BeepEditor.class);
private static final String UNTIL_DISMISSED_LABEL = "second intervals (1-60)";
private static final String DELAY_LABEL = "second delay (1-60)";
private ComboBoxChangeListener mComboBoxChangeListener = new ComboBoxChangeListener();
private PeriodChangeListener mPeriodChangeListener = new PeriodChangeListener();
private ComboBox<RecurringAction.Interval> mIntervalComboBox;
private TextField mPeriodTextField;
private IntegerFormatter mPeriodFormatter;
private Label mSecondsLabel;
private Button mTestButton;
public BeepEditor()
{
GridPane gridPane = new GridPane();
gridPane.setMaxWidth(Double.MAX_VALUE);
gridPane.setHgap(10);
gridPane.setVgap(10);
int row = 0;
Label intervalLabel = new Label(("Beep"));
GridPane.setHalignment(intervalLabel, HPos.RIGHT);
GridPane.setConstraints(intervalLabel, 0, row);
gridPane.getChildren().add(intervalLabel);
GridPane.setConstraints(getIntervalComboBox(), 1, row);
gridPane.getChildren().add(getIntervalComboBox());
GridPane.setConstraints(getPeriodTextField(), 2, row);
gridPane.getChildren().add(getPeriodTextField());
GridPane.setHalignment(getSecondsLabel(), HPos.LEFT);
GridPane.setConstraints(getSecondsLabel(), 3, row);
GridPane.setHgrow(getSecondsLabel(), Priority.ALWAYS);
gridPane.getChildren().add(getSecondsLabel());
GridPane.setConstraints(getTestButton(), 4, row);
gridPane.getChildren().add(getTestButton());
getChildren().add(gridPane);
}
@Override
public void setItem(BeepAction beepAction)
{
super.setItem(beepAction);
mComboBoxChangeListener.disable();
mPeriodChangeListener.disable();
getIntervalComboBox().setDisable(beepAction == null);
if(beepAction != null)
{
RecurringAction.Interval interval = beepAction.getInterval();
getIntervalComboBox().getSelectionModel().select(interval);
getPeriodFormatter().setValue(beepAction.getPeriod());
}
else
{
getIntervalComboBox().getSelectionModel().select(null);
getPeriodFormatter().setValue(null);
}
updatePeriodAndLabel();
mComboBoxChangeListener.enable();
mPeriodChangeListener.enable();
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
/**
* Tests the currently loaded clip action
*/
private void test()
{
if(getItem() != null)
{
final AliasAction action = AliasFactory.copyOf(getItem());
final TestMessage testMessage = new TestMessage();
final Alias testAlias = new Alias("Test Alias");
ThreadPool.SCHEDULED.submit(() -> {
try
{
action.execute(testAlias, testMessage);
}
catch(Exception e)
{
mLog.error("Error testing beep action", e);
}
});
}
}
private Button getTestButton()
{
if(mTestButton == null)
{
mTestButton = new Button("Test");
mTestButton.setMaxWidth(Double.MAX_VALUE);
mTestButton.setOnAction(event -> test());
}
return mTestButton;
}
private Label getSecondsLabel()
{
if(mSecondsLabel == null)
{
mSecondsLabel = new Label("second delay (1 - 60)");
mSecondsLabel.setMaxWidth(Double.MAX_VALUE);
mSecondsLabel.setVisible(false);
mSecondsLabel.setAlignment(Pos.CENTER_LEFT);
}
return mSecondsLabel;
}
private ComboBox<RecurringAction.Interval> getIntervalComboBox()
{
if(mIntervalComboBox == null)
{
mIntervalComboBox = new ComboBox<>();
mIntervalComboBox.setDisable(true);
mIntervalComboBox.getItems().setAll(RecurringAction.Interval.values());
mIntervalComboBox.getSelectionModel().selectedItemProperty().addListener(mComboBoxChangeListener);
}
return mIntervalComboBox;
}
private TextField getPeriodTextField()
{
if(mPeriodTextField == null)
{
mPeriodTextField = new TextField();
mPeriodTextField.setPrefWidth(40);
mPeriodTextField.setVisible(false);
mPeriodTextField.setTextFormatter(getPeriodFormatter());
}
return mPeriodTextField;
}
private IntegerFormatter getPeriodFormatter()
{
if(mPeriodFormatter == null)
{
mPeriodFormatter = new IntegerFormatter(1,60);
mPeriodFormatter.valueProperty().addListener(mPeriodChangeListener);
}
return mPeriodFormatter;
}
/**
* Updates the period text control and the accompanying seconds label.
*/
private void updatePeriodAndLabel()
{
RecurringAction.Interval selected = getIntervalComboBox().getSelectionModel().getSelectedItem();
if(selected != null)
{
switch(selected)
{
case ONCE:
getPeriodTextField().setVisible(false);
getSecondsLabel().setVisible(false);
break;
case DELAYED_RESET:
getPeriodTextField().setVisible(true);
getSecondsLabel().setVisible(true);
getSecondsLabel().setText(DELAY_LABEL);
break;
case UNTIL_DISMISSED:
getPeriodTextField().setVisible(true);
getSecondsLabel().setVisible(true);
getSecondsLabel().setText(UNTIL_DISMISSED_LABEL);
break;
}
}
else
{
getPeriodTextField().setVisible(false);
getSecondsLabel().setVisible(false);
}
}
private class PeriodChangeListener implements ChangeListener<Integer>
{
private boolean mEnabled = false;
@Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
{
if(mEnabled)
{
if(newValue != null)
{
getItem().setPeriod(newValue);
}
else
{
getItem().setPeriod(5);
}
}
}
public void enable()
{
mEnabled = true;
}
public void disable()
{
mEnabled = false;
}
}
private class ComboBoxChangeListener implements ChangeListener<RecurringAction.Interval>
{
private boolean mEnabled = false;
@Override
public void changed(ObservableValue<? extends RecurringAction.Interval> observable, RecurringAction.Interval oldValue, RecurringAction.Interval newValue)
{
mLog.debug("I Changed - new value: " + newValue + " not null:" + (getItem() != null));
if(mEnabled)
{
RecurringAction.Interval selected = getIntervalComboBox().getSelectionModel().getSelectedItem();
if(selected != null && getItem() != null)
{
mLog.debug("Setting interval to " + selected);
getItem().setInterval(selected);
modifiedProperty().set(true);
}
updatePeriodAndLabel();
}
}
public void enable()
{
mEnabled = true;
}
public void disable()
{
mEnabled = false;
}
}
}

View File

@@ -0,0 +1,416 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.action;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasFactory;
import io.github.dsheirer.alias.action.AliasAction;
import io.github.dsheirer.alias.action.RecurringAction;
import io.github.dsheirer.alias.action.clip.ClipAction;
import io.github.dsheirer.gui.control.IntegerFormatter;
import io.github.dsheirer.util.ThreadPool;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.FileChooser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
/**
* Audio clip action editor
*/
public class ClipEditor extends ActionEditor<ClipAction>
{
private static final Logger mLog = LoggerFactory.getLogger(ClipEditor.class);
private static final String UNTIL_DISMISSED_LABEL = "second intervals (1-60)";
private static final String DELAY_LABEL = "second delay (1-60)";
private static final String PLEASE_SELECT_A_FILE = "(please select a file)";
private ComboBoxChangeListener mComboBoxChangeListener = new ComboBoxChangeListener();
private PeriodChangeListener mPeriodChangeListener = new PeriodChangeListener();
private ComboBox<RecurringAction.Interval> mIntervalComboBox;
private TextField mPeriodTextField;
private IntegerFormatter mPeriodFormatter;
private Label mSecondsLabel;
private Button mFileChooser;
private Button mTestButton;
private TextField mFilePath;
public ClipEditor()
{
GridPane gridPane = new GridPane();
gridPane.setMaxWidth(Double.MAX_VALUE);
gridPane.setHgap(10);
gridPane.setVgap(10);
int row = 0;
Label fileLabel = new Label("Audio File");
GridPane.setHalignment(fileLabel, HPos.RIGHT);
GridPane.setConstraints(fileLabel, 0, row);
gridPane.getChildren().add(fileLabel);
GridPane.setConstraints(getFilePath(), 1, row, 3, 1);
GridPane.setHgrow(getFilePath(), Priority.ALWAYS);
gridPane.getChildren().add(getFilePath());
GridPane.setConstraints(getFileChooser(), 4, row);
gridPane.getChildren().add(getFileChooser());
Label intervalLabel = new Label(("Play Audio Clip"));
GridPane.setHalignment(intervalLabel, HPos.RIGHT);
GridPane.setConstraints(intervalLabel, 0, ++row);
gridPane.getChildren().add(intervalLabel);
GridPane.setConstraints(getIntervalComboBox(), 1, row);
gridPane.getChildren().add(getIntervalComboBox());
GridPane.setConstraints(getPeriodTextField(), 2, row);
gridPane.getChildren().add(getPeriodTextField());
GridPane.setHalignment(getSecondsLabel(), HPos.LEFT);
GridPane.setConstraints(getSecondsLabel(), 3, row);
GridPane.setHgrow(getSecondsLabel(), Priority.ALWAYS);
gridPane.getChildren().add(getSecondsLabel());
GridPane.setConstraints(getTestButton(), 4, row);
gridPane.getChildren().add(getTestButton());
getChildren().add(gridPane);
}
@Override
public void setItem(ClipAction clipAction)
{
super.setItem(clipAction);
mComboBoxChangeListener.disable();
mPeriodChangeListener.disable();
getIntervalComboBox().setDisable(clipAction == null);
getFileChooser().setDisable(clipAction == null);
if(clipAction != null)
{
RecurringAction.Interval interval = clipAction.getInterval();
getIntervalComboBox().getSelectionModel().select(interval);
getPeriodFormatter().setValue(clipAction.getPeriod());
if(clipAction.getPath() != null)
{
getFilePath().setText(clipAction.getPath());
}
else
{
getFilePath().setText(PLEASE_SELECT_A_FILE);
}
}
else
{
getIntervalComboBox().getSelectionModel().select(null);
getPeriodFormatter().setValue(null);
getFilePath().setText(PLEASE_SELECT_A_FILE);
}
updatePeriodAndLabel();
mComboBoxChangeListener.enable();
mPeriodChangeListener.enable();
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
/**
* Updates the period text control and the accompanying seconds label.
*/
private void updatePeriodAndLabel()
{
RecurringAction.Interval selected = getIntervalComboBox().getSelectionModel().getSelectedItem();
if(selected != null)
{
switch(selected)
{
case ONCE:
getPeriodTextField().setVisible(false);
getSecondsLabel().setVisible(false);
break;
case DELAYED_RESET:
getPeriodTextField().setVisible(true);
getSecondsLabel().setVisible(true);
getSecondsLabel().setText(DELAY_LABEL);
break;
case UNTIL_DISMISSED:
getPeriodTextField().setVisible(true);
getSecondsLabel().setVisible(true);
getSecondsLabel().setText(UNTIL_DISMISSED_LABEL);
break;
}
}
else
{
getPeriodTextField().setVisible(false);
getSecondsLabel().setVisible(false);
}
}
/**
* Tests the currently loaded clip action
*/
private void test()
{
if(getItem() != null)
{
final AliasAction clipAction = AliasFactory.copyOf(getItem());
final TestMessage testMessage = new TestMessage();
final Alias testAlias = new Alias("Test Alias");
ThreadPool.SCHEDULED.submit(new Runnable()
{
@Override
public void run()
{
try
{
clipAction.execute(testAlias, testMessage);
}
catch(Exception e)
{
mLog.error("Error testing clip action", e);
}
}
});
}
}
private Button getTestButton()
{
if(mTestButton == null)
{
mTestButton = new Button("Test");
mTestButton.setMaxWidth(Double.MAX_VALUE);
mTestButton.setDisable(true);
mTestButton.setOnAction(event -> test());
}
return mTestButton;
}
private Button getFileChooser()
{
if(mFileChooser == null)
{
mFileChooser = new Button(("Select ..."));
mFileChooser.setMaxWidth(Double.MAX_VALUE);
mFileChooser.setDisable(true);
mFileChooser.setOnAction(event -> {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select Audio File");
String selected = mFilePath.getText();
if(selected != null && !selected.isEmpty() && !selected.contentEquals(PLEASE_SELECT_A_FILE))
{
try
{
File testFile = new File(selected);
if(testFile.exists())
{
fileChooser.setInitialDirectory(testFile.getParentFile());
fileChooser.setInitialFileName(testFile.getName());
}
}
catch(Exception e)
{
mLog.error("Error trying to set default directory for file chooser", e);
}
}
File audioFile = fileChooser.showOpenDialog(null);
if(audioFile != null)
{
getFilePath().setText(audioFile.getAbsolutePath());
}
});
}
return mFileChooser;
}
private TextField getFilePath()
{
if(mFilePath == null)
{
mFilePath = new TextField(PLEASE_SELECT_A_FILE);
mFilePath.setDisable(true);
mFilePath.setMaxWidth(Double.MAX_VALUE);
mFilePath.textProperty().addListener((observable, oldValue, newValue) -> {
if(newValue != null && !newValue.isEmpty() && !newValue.contentEquals(PLEASE_SELECT_A_FILE))
{
getItem().setPath(newValue);
getTestButton().setDisable(false);
}
else
{
getItem().setPath(null);
getTestButton().setDisable(true);
}
modifiedProperty().set(true);
});
}
return mFilePath;
}
private Label getSecondsLabel()
{
if(mSecondsLabel == null)
{
mSecondsLabel = new Label("second delay (1 - 60)");
mSecondsLabel.setMaxWidth(Double.MAX_VALUE);
mSecondsLabel.setVisible(false);
mSecondsLabel.setAlignment(Pos.CENTER_LEFT);
}
return mSecondsLabel;
}
private ComboBox<RecurringAction.Interval> getIntervalComboBox()
{
if(mIntervalComboBox == null)
{
mIntervalComboBox = new ComboBox<>();
mIntervalComboBox.setDisable(true);
mIntervalComboBox.getItems().setAll(RecurringAction.Interval.values());
mIntervalComboBox.getSelectionModel().selectedItemProperty().addListener(mComboBoxChangeListener);
}
return mIntervalComboBox;
}
private TextField getPeriodTextField()
{
if(mPeriodTextField == null)
{
mPeriodTextField = new TextField();
mPeriodTextField.setPrefWidth(40);
mPeriodTextField.setVisible(false);
mPeriodTextField.setTextFormatter(getPeriodFormatter());
}
return mPeriodTextField;
}
private IntegerFormatter getPeriodFormatter()
{
if(mPeriodFormatter == null)
{
mPeriodFormatter = new IntegerFormatter(1,60);
mPeriodFormatter.valueProperty().addListener(mPeriodChangeListener);
}
return mPeriodFormatter;
}
private class PeriodChangeListener implements ChangeListener<Integer>
{
private boolean mEnabled = false;
@Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
{
if(mEnabled)
{
if(newValue != null)
{
getItem().setPeriod(newValue);
}
else
{
getItem().setPeriod(1);
}
modifiedProperty().set(true);
}
}
public void enable()
{
mEnabled = true;
}
public void disable()
{
mEnabled = false;
}
}
private class ComboBoxChangeListener implements ChangeListener<RecurringAction.Interval>
{
private boolean mEnabled = false;
@Override
public void changed(ObservableValue<? extends RecurringAction.Interval> observable, RecurringAction.Interval oldValue, RecurringAction.Interval newValue)
{
if(mEnabled)
{
RecurringAction.Interval selected = getIntervalComboBox().getSelectionModel().getSelectedItem();
if(selected != null && getItem() != null)
{
getItem().setInterval(selected);
modifiedProperty().set(true);
}
updatePeriodAndLabel();
}
}
public void enable()
{
mEnabled = true;
}
public void disable()
{
mEnabled = false;
}
}
}

View File

@@ -1,7 +1,6 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2019 Dennis Sheirer
* *****************************************************************************
* Copyright (C) 2014-2020 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
@@ -15,34 +14,32 @@
*
* 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.editor;
package io.github.dsheirer.gui.playlist.alias.action;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javafx.scene.control.Label;
public abstract class DocumentListenerEditor<T> extends Editor<T> implements DocumentListener
/**
* Empty alias actopm editor
*/
public class EmptyActionEditor extends ActionEditor
{
private static final long serialVersionUID = 1L;
public DocumentListenerEditor()
{
}
public EmptyActionEditor()
{
getChildren().add(new Label("Please select an action to view or edit"));
}
@Override
public void insertUpdate( DocumentEvent e )
{
setModified( true );
}
public void save()
{
//no-op
}
@Override
public void removeUpdate( DocumentEvent e )
{
setModified( true );
}
@Override
public void changedUpdate( DocumentEvent e ) { }
@Override
public void dispose()
{
//no-op
}
}

View File

@@ -0,0 +1,416 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.action;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasFactory;
import io.github.dsheirer.alias.action.AliasAction;
import io.github.dsheirer.alias.action.RecurringAction;
import io.github.dsheirer.alias.action.script.ScriptAction;
import io.github.dsheirer.gui.control.IntegerFormatter;
import io.github.dsheirer.util.ThreadPool;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.FileChooser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
/**
* Script action editor
*/
public class ScriptEditor extends ActionEditor<ScriptAction>
{
private static final Logger mLog = LoggerFactory.getLogger(ScriptEditor.class);
private static final String UNTIL_DISMISSED_LABEL = "second intervals (1-60)";
private static final String DELAY_LABEL = "second delay (1-60)";
private static final String PLEASE_SELECT_A_FILE = "(please select a file)";
private ComboBoxChangeListener mComboBoxChangeListener = new ComboBoxChangeListener();
private PeriodChangeListener mPeriodChangeListener = new PeriodChangeListener();
private ComboBox<RecurringAction.Interval> mIntervalComboBox;
private TextField mPeriodTextField;
private IntegerFormatter mPeriodFormatter;
private Label mSecondsLabel;
private Button mFileChooser;
private Button mTestButton;
private TextField mFilePath;
public ScriptEditor()
{
GridPane gridPane = new GridPane();
gridPane.setMaxWidth(Double.MAX_VALUE);
gridPane.setHgap(10);
gridPane.setVgap(10);
int row = 0;
Label fileLabel = new Label("Script File");
GridPane.setHalignment(fileLabel, HPos.RIGHT);
GridPane.setConstraints(fileLabel, 0, row);
gridPane.getChildren().add(fileLabel);
GridPane.setConstraints(getFilePath(), 1, row, 3, 1);
GridPane.setHgrow(getFilePath(), Priority.ALWAYS);
gridPane.getChildren().add(getFilePath());
GridPane.setConstraints(getFileChooser(), 4, row);
gridPane.getChildren().add(getFileChooser());
Label intervalLabel = new Label(("Run Script"));
GridPane.setHalignment(intervalLabel, HPos.RIGHT);
GridPane.setConstraints(intervalLabel, 0, ++row);
gridPane.getChildren().add(intervalLabel);
GridPane.setConstraints(getIntervalComboBox(), 1, row);
gridPane.getChildren().add(getIntervalComboBox());
GridPane.setConstraints(getPeriodTextField(), 2, row);
gridPane.getChildren().add(getPeriodTextField());
GridPane.setHalignment(getSecondsLabel(), HPos.LEFT);
GridPane.setConstraints(getSecondsLabel(), 3, row);
GridPane.setHgrow(getSecondsLabel(), Priority.ALWAYS);
gridPane.getChildren().add(getSecondsLabel());
GridPane.setConstraints(getTestButton(), 4, row);
gridPane.getChildren().add(getTestButton());
getChildren().add(gridPane);
}
@Override
public void setItem(ScriptAction scriptAction)
{
super.setItem(scriptAction);
mComboBoxChangeListener.disable();
mPeriodChangeListener.disable();
getIntervalComboBox().setDisable(scriptAction == null);
getFileChooser().setDisable(scriptAction == null);
if(scriptAction != null)
{
RecurringAction.Interval interval = scriptAction.getInterval();
getIntervalComboBox().getSelectionModel().select(interval);
getPeriodFormatter().setValue(scriptAction.getPeriod());
if(scriptAction.getScript() != null)
{
getFilePath().setText(scriptAction.getScript());
}
else
{
getFilePath().setText(PLEASE_SELECT_A_FILE);
}
}
else
{
getIntervalComboBox().getSelectionModel().select(null);
getPeriodFormatter().setValue(null);
getFilePath().setText(PLEASE_SELECT_A_FILE);
}
updatePeriodAndLabel();
mComboBoxChangeListener.enable();
mPeriodChangeListener.enable();
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
/**
* Updates the period text control and the accompanying seconds label.
*/
private void updatePeriodAndLabel()
{
RecurringAction.Interval selected = getIntervalComboBox().getSelectionModel().getSelectedItem();
if(selected != null)
{
switch(selected)
{
case ONCE:
getPeriodTextField().setVisible(false);
getSecondsLabel().setVisible(false);
break;
case DELAYED_RESET:
getPeriodTextField().setVisible(true);
getSecondsLabel().setVisible(true);
getSecondsLabel().setText(DELAY_LABEL);
break;
case UNTIL_DISMISSED:
getPeriodTextField().setVisible(true);
getSecondsLabel().setVisible(true);
getSecondsLabel().setText(UNTIL_DISMISSED_LABEL);
break;
}
}
else
{
getPeriodTextField().setVisible(false);
getSecondsLabel().setVisible(false);
}
}
/**
* Tests the currently loaded script action
*/
private void test()
{
if(getItem() != null)
{
final AliasAction clipAction = AliasFactory.copyOf(getItem());
final TestMessage testMessage = new TestMessage();
final Alias testAlias = new Alias("Test Alias");
ThreadPool.SCHEDULED.submit(new Runnable()
{
@Override
public void run()
{
try
{
clipAction.execute(testAlias, testMessage);
}
catch(Exception e)
{
mLog.error("Error testing script action", e);
}
}
});
}
}
private Button getTestButton()
{
if(mTestButton == null)
{
mTestButton = new Button("Test");
mTestButton.setMaxWidth(Double.MAX_VALUE);
mTestButton.setDisable(true);
mTestButton.setOnAction(event -> test());
}
return mTestButton;
}
private Button getFileChooser()
{
if(mFileChooser == null)
{
mFileChooser = new Button(("Select ..."));
mFileChooser.setMaxWidth(Double.MAX_VALUE);
mFileChooser.setDisable(true);
mFileChooser.setOnAction(event -> {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select Script File");
String selected = mFilePath.getText();
if(selected != null && !selected.isEmpty() && !selected.contentEquals(PLEASE_SELECT_A_FILE))
{
try
{
File testFile = new File(selected);
if(testFile.exists())
{
fileChooser.setInitialDirectory(testFile.getParentFile());
fileChooser.setInitialFileName(testFile.getName());
}
}
catch(Exception e)
{
mLog.error("Error trying to set default directory for file chooser", e);
}
}
File audioFile = fileChooser.showOpenDialog(null);
if(audioFile != null)
{
getFilePath().setText(audioFile.getAbsolutePath());
}
});
}
return mFileChooser;
}
private TextField getFilePath()
{
if(mFilePath == null)
{
mFilePath = new TextField(PLEASE_SELECT_A_FILE);
mFilePath.setDisable(true);
mFilePath.setMaxWidth(Double.MAX_VALUE);
mFilePath.textProperty().addListener((observable, oldValue, newValue) -> {
if(newValue != null && !newValue.isEmpty() && !newValue.contentEquals(PLEASE_SELECT_A_FILE))
{
getItem().setScript(newValue);
getTestButton().setDisable(false);
}
else
{
getItem().setScript(null);
getTestButton().setDisable(true);
}
modifiedProperty().set(true);
});
}
return mFilePath;
}
private Label getSecondsLabel()
{
if(mSecondsLabel == null)
{
mSecondsLabel = new Label("second delay (1 - 60)");
mSecondsLabel.setMaxWidth(Double.MAX_VALUE);
mSecondsLabel.setVisible(false);
mSecondsLabel.setAlignment(Pos.CENTER_LEFT);
}
return mSecondsLabel;
}
private ComboBox<RecurringAction.Interval> getIntervalComboBox()
{
if(mIntervalComboBox == null)
{
mIntervalComboBox = new ComboBox<>();
mIntervalComboBox.setDisable(true);
mIntervalComboBox.getItems().setAll(RecurringAction.Interval.values());
mIntervalComboBox.getSelectionModel().selectedItemProperty().addListener(mComboBoxChangeListener);
}
return mIntervalComboBox;
}
private TextField getPeriodTextField()
{
if(mPeriodTextField == null)
{
mPeriodTextField = new TextField();
mPeriodTextField.setPrefWidth(40);
mPeriodTextField.setVisible(false);
mPeriodTextField.setTextFormatter(getPeriodFormatter());
}
return mPeriodTextField;
}
private IntegerFormatter getPeriodFormatter()
{
if(mPeriodFormatter == null)
{
mPeriodFormatter = new IntegerFormatter(1,60);
mPeriodFormatter.valueProperty().addListener(mPeriodChangeListener);
}
return mPeriodFormatter;
}
private class PeriodChangeListener implements ChangeListener<Integer>
{
private boolean mEnabled = false;
@Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
{
if(mEnabled)
{
if(newValue != null)
{
getItem().setPeriod(newValue);
}
else
{
getItem().setPeriod(1);
}
modifiedProperty().set(true);
}
}
public void enable()
{
mEnabled = true;
}
public void disable()
{
mEnabled = false;
}
}
private class ComboBoxChangeListener implements ChangeListener<RecurringAction.Interval>
{
private boolean mEnabled = false;
@Override
public void changed(ObservableValue<? extends RecurringAction.Interval> observable, RecurringAction.Interval oldValue, RecurringAction.Interval newValue)
{
if(mEnabled)
{
RecurringAction.Interval selected = getIntervalComboBox().getSelectionModel().getSelectedItem();
if(selected != null && getItem() != null)
{
getItem().setInterval(selected);
modifiedProperty().set(true);
}
updatePeriodAndLabel();
}
}
public void enable()
{
mEnabled = true;
}
public void disable()
{
mEnabled = false;
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.action;
import io.github.dsheirer.identifier.Identifier;
import io.github.dsheirer.message.IMessage;
import io.github.dsheirer.protocol.Protocol;
import java.util.Collections;
import java.util.List;
/**
* Simple test message for testing alias actions
*/
public class TestMessage implements IMessage
{
@Override
public String toString()
{
return "This is a test message";
}
@Override
public long getTimestamp()
{
return System.currentTimeMillis();
}
@Override
public boolean isValid()
{
return true;
}
@Override
public Protocol getProtocol()
{
return Protocol.UNKNOWN;
}
@Override
public int getTimeslot()
{
return 0;
}
@Override
public List<Identifier> getIdentifiers()
{
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,47 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.action;
import javafx.scene.control.Label;
/**
* Unrecognized alias action editor
*/
public class UnrecognizedActionEditor extends ActionEditor
{
public UnrecognizedActionEditor()
{
Label label = new Label("Unrecognized action type.");
label.setWrapText(true);
getChildren().add(label);
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
}

View File

@@ -0,0 +1,46 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.identifier;
import io.github.dsheirer.alias.id.AliasID;
import javafx.scene.control.Label;
/**
* Empty alias identifier editor
*/
public class EmptyIdentifierEditor extends IdentifierEditor<AliasID>
{
public EmptyIdentifierEditor()
{
getChildren().add(new Label("Please select an identifier to view or edit"));
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
}

View File

@@ -0,0 +1,99 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.identifier;
import io.github.dsheirer.alias.id.esn.Esn;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.gui.control.HexFormatter;
import io.github.dsheirer.gui.control.IntegerFormatter;
import io.github.dsheirer.gui.control.LtrFormatter;
import io.github.dsheirer.gui.control.PrefixIdentFormatter;
import io.github.dsheirer.preference.identifier.IntegerFormat;
import io.github.dsheirer.protocol.Protocol;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Editor for ESN alias identifiers
*/
public class EsnEditor extends IdentifierEditor<Esn>
{
private static final Logger mLog = LoggerFactory.getLogger(EsnEditor.class);
private TextField mEsnField;
/**
* Constructs an instance
*/
public EsnEditor()
{
GridPane gridPane = new GridPane();
gridPane.setHgap(5);
Label typeLabel = new Label("ESN");
GridPane.setConstraints(typeLabel, 0, 0);
gridPane.getChildren().add(typeLabel);
GridPane.setConstraints(getEsnField(), 1, 0);
gridPane.getChildren().add(getEsnField());
getChildren().add(gridPane);
}
@Override
public void setItem(Esn item)
{
super.setItem(item);
modifiedProperty().set(false);
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
private TextField getEsnField()
{
if(mEsnField == null)
{
mEsnField = new TextField();
mEsnField.textProperty()
.addListener((observable, oldValue, newValue) -> {
getItem().setEsn(getEsnField().getText());
modifiedProperty().set(true);
});
}
return mEsnField;
}
}

View File

@@ -0,0 +1,31 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.identifier;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.gui.playlist.Editor;
/**
* Base identifier editor class
* @param <T> aliasID to edit
*/
public abstract class IdentifierEditor<T extends AliasID> extends Editor<T>
{
}

View File

@@ -0,0 +1,60 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.identifier;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.preference.UserPreferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Factory for creating JavaFX editors for alias identifiers
*/
public class IdentifierEditorFactory
{
private static final Logger mLog = LoggerFactory.getLogger(IdentifierEditorFactory.class);
public static IdentifierEditor getEditor(AliasIDType type, UserPreferences userPreferences)
{
switch(type)
{
case ESN:
return new EsnEditor();
case LOJACK:
return new LojackEditor();
case RADIO_ID:
return new RadioIdEditor(userPreferences);
case RADIO_ID_RANGE:
return new RadioIdRangeEditor(userPreferences);
case TALKGROUP:
return new TalkgroupEditor(userPreferences);
case TALKGROUP_RANGE:
return new TalkgroupRangeEditor(userPreferences);
case STATUS:
return new UserStatusEditor();
case TONES:
return new TonesEditor();
case UNIT_STATUS:
return new UnitStatusEditor();
default:
return new UnrecognizedIdentifierEditor();
}
}
}

View File

@@ -0,0 +1,142 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.identifier;
import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID;
import io.github.dsheirer.module.decode.lj1200.LJ1200Message;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Editor for LoJack Function and ID alias identifiers
*/
public class LojackEditor extends IdentifierEditor<LoJackFunctionAndID>
{
private static final Logger mLog = LoggerFactory.getLogger(LojackEditor.class);
private ComboBox<LJ1200Message.Function> mFunctionComboBox;
private TextField mIdentifierField;
/**
* Constructs an instance
*/
public LojackEditor()
{
GridPane gridPane = new GridPane();
gridPane.setHgap(5);
Label typeLabel = new Label("LoJack Function");
GridPane.setConstraints(typeLabel, 0, 0);
gridPane.getChildren().add(typeLabel);
GridPane.setConstraints(getFunctionComboBox(), 1, 0);
gridPane.getChildren().add(getFunctionComboBox());
GridPane.setConstraints(getIdentifierField(), 2, 0);
gridPane.getChildren().add(getIdentifierField());
Label helpLabel = new Label("Format: 5 numbers, characters or (*)wildcard - e.g. AB*12");
GridPane.setConstraints(helpLabel, 3, 0);
gridPane.getChildren().add(helpLabel);
getChildren().add(gridPane);
}
@Override
public void setItem(LoJackFunctionAndID item)
{
super.setItem(item);
getFunctionComboBox().setDisable(item == null);
getIdentifierField().setDisable(item == null);
if(item != null)
{
getFunctionComboBox().getSelectionModel().select(item.getFunction());
getIdentifierField().setText(item.getID());
}
else
{
getFunctionComboBox().getSelectionModel().select(null);
getIdentifierField().setText(null);
}
modifiedProperty().set(false);
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
private ComboBox<LJ1200Message.Function> getFunctionComboBox()
{
if(mFunctionComboBox == null)
{
mFunctionComboBox = new ComboBox<>();
mFunctionComboBox.getItems().addAll(LJ1200Message.Function.values());
mFunctionComboBox.setDisable(true);
mFunctionComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
getItem().setFunction(newValue);
modifiedProperty().set(true);
});
}
return mFunctionComboBox;
}
private TextField getIdentifierField()
{
if(mIdentifierField == null)
{
mIdentifierField = new TextField();
mIdentifierField.setDisable(true);
mIdentifierField.textProperty()
.addListener((observable, oldValue, newValue) -> {
getItem().setID(newValue);
modifiedProperty().set(true);
});
String text = "ID: 5 numbers or characters (e.g. 1BN47)\n" +
"Wildcard: asterisk (*) for any character (e.g. AB*12)\n" +
"Middle character in a reply ID code identifies the entity\n" +
"Valid middle character entities are:\n" +
" Tower: X,Y\n" +
" Transponder: 0-9,A,C-H,J-N,P-W\n" +
" Not Used: B,I,O,Z";
Tooltip toolTip = new Tooltip(text);
toolTip.wrapTextProperty().set(true);
mIdentifierField.setTooltip(toolTip);
}
return mIdentifierField;
}
}

View File

@@ -0,0 +1,288 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.identifier;
import io.github.dsheirer.alias.id.radio.Radio;
import io.github.dsheirer.gui.control.HexFormatter;
import io.github.dsheirer.gui.control.IntegerFormatter;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.identifier.IntegerFormat;
import io.github.dsheirer.protocol.Protocol;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.HPos;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Editor for talkgroup alias identifiers
*/
public class RadioIdEditor extends IdentifierEditor<Radio>
{
private static final Logger mLog = LoggerFactory.getLogger(RadioIdEditor.class);
private UserPreferences mUserPreferences;
private Label mProtocolLabel;
private Label mFormatLabel;
private TextField mRadioIdField;
private TextFormatter<Integer> mIntegerTextFormatter;
private List<RadioDetail> mRadioDetails = new ArrayList<>();
private RadioValueChangeListener mRadioValueChangeListener = new RadioValueChangeListener();
/**
* Constructs an instance
* @param userPreferences for determining user preferred talkgroup formats
*/
public RadioIdEditor(UserPreferences userPreferences)
{
mUserPreferences = userPreferences;
loadRadioDetails();
GridPane gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(3);
GridPane.setConstraints(getProtocolLabel(), 0, 0);
gridPane.getChildren().add(getProtocolLabel());
Label valueLabel = new Label("Radio ID");
GridPane.setHalignment(valueLabel, HPos.RIGHT);
GridPane.setConstraints(valueLabel, 1, 0);
gridPane.getChildren().add(valueLabel);
GridPane.setConstraints(getRadioIdField(), 2, 0);
gridPane.getChildren().add(getRadioIdField());
GridPane.setConstraints(getFormatLabel(), 3, 0);
gridPane.getChildren().add(getFormatLabel());
getChildren().add(gridPane);
}
@Override
public void setItem(Radio item)
{
super.setItem(item);
Radio radioId = getItem();
getProtocolLabel().setDisable(radioId == null);
getRadioIdField().setDisable(radioId == null);
if(radioId != null)
{
getProtocolLabel().setText(radioId.getProtocol().toString());
updateTextFormatter();
}
else
{
getRadioIdField().setText(null);
}
modifiedProperty().set(false);
}
private void updateTextFormatter()
{
if(mIntegerTextFormatter != null)
{
mIntegerTextFormatter.valueProperty().removeListener(mRadioValueChangeListener);
}
IntegerFormat format = mUserPreferences.getTalkgroupFormatPreference().getTalkgroupFormat(getItem().getProtocol());
if(format != null)
{
RadioDetail radioDetail = getRadioDetail(getItem().getProtocol(), format);
if(radioDetail != null)
{
mIntegerTextFormatter = radioDetail.getTextFormatter();
Integer value = getItem() != null ? getItem().getValue() : null;
mRadioIdField.setTextFormatter(mIntegerTextFormatter);
mRadioIdField.setTooltip(new Tooltip(radioDetail.getTooltip()));
mIntegerTextFormatter.setValue(value);
mIntegerTextFormatter.valueProperty().addListener(mRadioValueChangeListener);
}
else
{
mLog.warn("Couldn't find radio detail for protocol [" + getItem().getProtocol() +
"] and format [" + format + "]");
}
getFormatLabel().setText(radioDetail.getTooltip());
}
else
{
getFormatLabel().setText(" ");
mLog.warn("Integer format combo box does not have a selected value.");
}
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
private Label getFormatLabel()
{
if(mFormatLabel == null)
{
mFormatLabel = new Label(" ");
}
return mFormatLabel;
}
private Label getProtocolLabel()
{
if(mProtocolLabel == null)
{
mProtocolLabel = new Label();
}
return mProtocolLabel;
}
private TextField getRadioIdField()
{
if(mRadioIdField == null)
{
mRadioIdField = new TextField();
mRadioIdField.setTextFormatter(mIntegerTextFormatter);
}
return mRadioIdField;
}
public class RadioValueChangeListener implements ChangeListener<Integer>
{
@Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
{
if(getItem() != null)
{
getItem().setValue(newValue != null ? newValue : 0);
modifiedProperty().set(true);
}
}
}
/**
* Radio detail formatting details for the specified protocol and format
* @param protocol for the radio ID
* @param integerFormat for formatting the value
* @return detail or null
*/
private RadioDetail getRadioDetail(Protocol protocol, IntegerFormat integerFormat)
{
for(RadioDetail detail: mRadioDetails)
{
if(detail.getProtocol() == protocol && detail.getIntegerFormat() == integerFormat)
{
return detail;
}
}
//Use a default instance
for(RadioDetail detail: mRadioDetails)
{
if(detail.getProtocol() == Protocol.UNKNOWN && detail.getIntegerFormat() == integerFormat)
{
return detail;
}
}
mLog.warn("No Radio Detail is configured for protocol [" + protocol + "] and format [" + integerFormat + "]");
return null;
}
private void loadRadioDetails()
{
mRadioDetails.clear();
mRadioDetails.add(new RadioDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
"Format: 0 - 16777215"));
mRadioDetails.add(new RadioDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
"Format: 0 - FFFFFF"));
mRadioDetails.add(new RadioDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0x7FFFFF),
"Format: 0 - 8388607"));
mRadioDetails.add(new RadioDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0x7FFFFF),
"Format: 0 - 7FFFFF"));
mRadioDetails.add(new RadioDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
"Format: 0 - FFFFFF"));
mRadioDetails.add(new RadioDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
"Format: 0 - FFFFFF"));
mRadioDetails.add(new RadioDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
"Format: 0 - FFFFFF"));
}
public class RadioDetail
{
private Protocol mProtocol;
private IntegerFormat mIntegerFormat;
private TextFormatter<Integer> mTextFormatter;
private String mTooltip;
public RadioDetail(Protocol protocol, IntegerFormat integerFormat, TextFormatter<Integer> textFormatter,
String tooltip)
{
mProtocol = protocol;
mIntegerFormat = integerFormat;
mTextFormatter = textFormatter;
mTooltip = tooltip;
}
public Protocol getProtocol()
{
return mProtocol;
}
public IntegerFormat getIntegerFormat()
{
return mIntegerFormat;
}
public TextFormatter<Integer> getTextFormatter()
{
return mTextFormatter;
}
public String getTooltip()
{
return mTooltip;
}
}
}

View File

@@ -0,0 +1,350 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.identifier;
import io.github.dsheirer.alias.id.radio.RadioRange;
import io.github.dsheirer.gui.control.HexFormatter;
import io.github.dsheirer.gui.control.IntegerFormatter;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.identifier.IntegerFormat;
import io.github.dsheirer.protocol.Protocol;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.HPos;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Editor for radio ID range alias identifiers
*/
public class RadioIdRangeEditor extends IdentifierEditor<RadioRange>
{
private static final Logger mLog = LoggerFactory.getLogger(RadioIdRangeEditor.class);
private UserPreferences mUserPreferences;
private Label mProtocolLabel;
private Label mFormatLabel;
private TextField mMinRadioIdField;
private TextField mMaxRadioIdField;
private TextFormatter<Integer> mMinIntegerTextFormatter;
private TextFormatter<Integer> mMaxIntegerTextFormatter;
private List<RadioIdDetail> mRadioDetails;
private MinRadioValueChangeListener mMinRadioValueChangeListener = new MinRadioValueChangeListener();
private MaxRadioValueChangeListener mMaxRadioValueChangeListener = new MaxRadioValueChangeListener();
/**
* Constructs an instance
* @param userPreferences for determining user preferred radio ID formats
*/
public RadioIdRangeEditor(UserPreferences userPreferences)
{
mUserPreferences = userPreferences;
mRadioDetails = createRadioDetails();
GridPane gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(3);
GridPane.setConstraints(getProtocolLabel(), 0, 0);
gridPane.getChildren().add(getProtocolLabel());
Label valueLabel = new Label("Radio ID Range");
GridPane.setHalignment(valueLabel, HPos.RIGHT);
GridPane.setConstraints(valueLabel, 1, 0);
gridPane.getChildren().add(valueLabel);
GridPane.setConstraints(getMinRadioIdField(), 2, 0);
gridPane.getChildren().add(getMinRadioIdField());
Label dashLabel = new Label("-");
GridPane.setConstraints(dashLabel, 3, 0);
gridPane.getChildren().add(dashLabel);
GridPane.setConstraints(getMaxRadioIdField(), 4, 0);
gridPane.getChildren().add(getMaxRadioIdField());
GridPane.setConstraints(getFormatLabel(), 5, 0);
gridPane.getChildren().add(getFormatLabel());
getChildren().add(gridPane);
}
@Override
public void setItem(RadioRange item)
{
super.setItem(item);
RadioRange radioRange = getItem();
getProtocolLabel().setDisable(radioRange == null);
getMinRadioIdField().setDisable(radioRange == null);
getMaxRadioIdField().setDisable(radioRange == null);
if(radioRange != null)
{
getProtocolLabel().setText(radioRange.getProtocol().toString());
updateTextFormatter();
}
else
{
getProtocolLabel().setText("Unrecognized Protocol");
getMinRadioIdField().setText(null);
getMaxRadioIdField().setText(null);
}
modifiedProperty().set(false);
}
private void updateTextFormatter()
{
if(mMinIntegerTextFormatter != null)
{
mMinIntegerTextFormatter.valueProperty().removeListener(mMinRadioValueChangeListener);
}
if(mMaxIntegerTextFormatter != null)
{
mMaxIntegerTextFormatter.valueProperty().removeListener(mMaxRadioValueChangeListener);
}
IntegerFormat format = mUserPreferences.getTalkgroupFormatPreference().getTalkgroupFormat(getItem().getProtocol());
if(format != null)
{
RadioIdDetail radioIdDetail = getRadioDetail(getItem().getProtocol(), format);
if(radioIdDetail != null)
{
mMinIntegerTextFormatter = radioIdDetail.getMinTextFormatter();
Integer minValue = getItem() != null ? getItem().getMinRadio() : null;
mMinRadioIdField.setTextFormatter(mMinIntegerTextFormatter);
mMinRadioIdField.setTooltip(new Tooltip(radioIdDetail.getTooltip()));
mMinIntegerTextFormatter.setValue(minValue);
mMinIntegerTextFormatter.valueProperty().addListener(mMinRadioValueChangeListener);
mMaxIntegerTextFormatter = radioIdDetail.getMaxTextFormatter();
Integer maxValue = getItem() != null ? getItem().getMaxRadio() : null;
mMaxRadioIdField.setTextFormatter(mMaxIntegerTextFormatter);
mMaxRadioIdField.setTooltip(new Tooltip(radioIdDetail.getTooltip()));
mMaxIntegerTextFormatter.setValue(maxValue);
mMaxIntegerTextFormatter.valueProperty().addListener(mMaxRadioValueChangeListener);
getFormatLabel().setText(radioIdDetail.getTooltip());
}
else
{
mLog.warn("Couldn't find radio ID detail for protocol [" + getItem().getProtocol() +
"] and format [" + format + "]");
getFormatLabel().setText(" ");
}
}
else
{
getFormatLabel().setText(" ");
mMinIntegerTextFormatter.setValue(null);
mMaxIntegerTextFormatter.setValue(null);
}
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
private Label getFormatLabel()
{
if(mFormatLabel == null)
{
mFormatLabel = new Label(" ");
}
return mFormatLabel;
}
private Label getProtocolLabel()
{
if(mProtocolLabel == null)
{
mProtocolLabel = new Label();
}
return mProtocolLabel;
}
private TextField getMinRadioIdField()
{
if(mMinRadioIdField == null)
{
mMinRadioIdField = new TextField();
mMinRadioIdField.setTextFormatter(mMinIntegerTextFormatter);
}
return mMinRadioIdField;
}
private TextField getMaxRadioIdField()
{
if(mMaxRadioIdField == null)
{
mMaxRadioIdField = new TextField();
mMaxRadioIdField.setTextFormatter(mMinIntegerTextFormatter);
}
return mMaxRadioIdField;
}
public class MinRadioValueChangeListener implements ChangeListener<Integer>
{
@Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
{
if(getItem() != null)
{
getItem().setMinRadio(newValue != null ? newValue : 0);
modifiedProperty().set(true);
}
}
}
public class MaxRadioValueChangeListener implements ChangeListener<Integer>
{
@Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
{
if(getItem() != null)
{
getItem().setMaxRadio(newValue != null ? newValue : 0);
modifiedProperty().set(true);
}
}
}
/**
* Radio ID detail formatting details for the specified protocol and format
* @param protocol for the talkgroup
* @param integerFormat for formatting the value
* @return detail or null
*/
private RadioIdDetail getRadioDetail(Protocol protocol, IntegerFormat integerFormat)
{
for(RadioIdDetail detail: mRadioDetails)
{
if(detail.getProtocol() == protocol && detail.getIntegerFormat() == integerFormat)
{
return detail;
}
}
//Use a default instance
for(RadioIdDetail detail: mRadioDetails)
{
if(detail.getProtocol() == Protocol.UNKNOWN && detail.getIntegerFormat() == integerFormat)
{
return detail;
}
}
mLog.warn("No Radio ID Detail is configured for protocol [" + protocol + "] and format [" + integerFormat + "]");
return null;
}
/**
* Creates a set of radio ID details with integer text formatters
*/
private List<RadioIdDetail> createRadioDetails()
{
List<RadioIdDetail> details = new ArrayList<>();
details.add(new RadioIdDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
new IntegerFormatter(0,0xFFFFFF), "Format: 0 - 16777215"));
details.add(new RadioIdDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
new HexFormatter(0,0xFFFFFF), "Format: 0 - FFFFFF"));
details.add(new RadioIdDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0x7FFFFF),
new IntegerFormatter(0,0x7FFFFF), "Format: 0 - 8388607"));
details.add(new RadioIdDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0x7FFFFF),
new HexFormatter(0,0x7FFFFF), "Format: 0 - 7FFFFF"));
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
details.add(new RadioIdDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
return details;
}
public class RadioIdDetail
{
private Protocol mProtocol;
private IntegerFormat mIntegerFormat;
private TextFormatter<Integer> mMinTextFormatter;
private TextFormatter<Integer> mMaxTextFormatter;
private String mTooltip;
public RadioIdDetail(Protocol protocol, IntegerFormat integerFormat, TextFormatter<Integer> minTextFormatter,
TextFormatter<Integer> maxTextFormatter, String tooltip)
{
mProtocol = protocol;
mIntegerFormat = integerFormat;
mMinTextFormatter = minTextFormatter;
mMaxTextFormatter = maxTextFormatter;
mTooltip = tooltip;
}
public Protocol getProtocol()
{
return mProtocol;
}
public IntegerFormat getIntegerFormat()
{
return mIntegerFormat;
}
public TextFormatter<Integer> getMinTextFormatter()
{
return mMinTextFormatter;
}
public TextFormatter<Integer> getMaxTextFormatter()
{
return mMaxTextFormatter;
}
public String getTooltip()
{
return mTooltip;
}
}
}

View File

@@ -0,0 +1,300 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.identifier;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.gui.control.HexFormatter;
import io.github.dsheirer.gui.control.IntegerFormatter;
import io.github.dsheirer.gui.control.LtrFormatter;
import io.github.dsheirer.gui.control.PrefixIdentFormatter;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.identifier.IntegerFormat;
import io.github.dsheirer.protocol.Protocol;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.HPos;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Editor for talkgroup alias identifiers
*/
public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
{
private static final Logger mLog = LoggerFactory.getLogger(TalkgroupEditor.class);
private UserPreferences mUserPreferences;
private Label mProtocolLabel;
private Label mFormatLabel;
private TextField mTalkgroupField;
private TextFormatter<Integer> mIntegerTextFormatter;
private List<TalkgroupDetail> mTalkgroupDetails = new ArrayList<>();
private TalkgroupValueChangeListener mTalkgroupValueChangeListener = new TalkgroupValueChangeListener();
/**
* Constructs an instance
* @param userPreferences for determining user preferred talkgroup formats
*/
public TalkgroupEditor(UserPreferences userPreferences)
{
mUserPreferences = userPreferences;
loadTalkgroupDetails();
GridPane gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(3);
GridPane.setConstraints(getProtocolLabel(), 0, 0);
gridPane.getChildren().add(getProtocolLabel());
Label valueLabel = new Label("Talkgroup");
GridPane.setHalignment(valueLabel, HPos.RIGHT);
GridPane.setConstraints(valueLabel, 1, 0);
gridPane.getChildren().add(valueLabel);
GridPane.setConstraints(getTalkgroupField(), 2, 0);
gridPane.getChildren().add(getTalkgroupField());
GridPane.setConstraints(getFormatLabel(), 3, 0);
gridPane.getChildren().add(getFormatLabel());
getChildren().add(gridPane);
}
@Override
public void setItem(Talkgroup item)
{
super.setItem(item);
Talkgroup talkgroup = getItem();
getProtocolLabel().setDisable(talkgroup == null);
getTalkgroupField().setDisable(talkgroup == null);
if(talkgroup != null)
{
getProtocolLabel().setText(talkgroup.getProtocol().toString());
updateTextFormatter();
}
else
{
getTalkgroupField().setText(null);
}
modifiedProperty().set(false);
}
private void updateTextFormatter()
{
if(mIntegerTextFormatter != null)
{
mIntegerTextFormatter.valueProperty().removeListener(mTalkgroupValueChangeListener);
}
IntegerFormat format = mUserPreferences.getTalkgroupFormatPreference().getTalkgroupFormat(getItem().getProtocol());
if(format != null)
{
TalkgroupDetail talkgroupDetail = getTalkgroupDetail(getItem().getProtocol(), format);
if(talkgroupDetail != null)
{
mIntegerTextFormatter = talkgroupDetail.getTextFormatter();
Integer value = getItem() != null ? getItem().getValue() : null;
mTalkgroupField.setTextFormatter(mIntegerTextFormatter);
mTalkgroupField.setTooltip(new Tooltip(talkgroupDetail.getTooltip()));
mIntegerTextFormatter.setValue(value);
mIntegerTextFormatter.valueProperty().addListener(mTalkgroupValueChangeListener);
}
else
{
mLog.warn("Couldn't find talkgroup detail for protocol [" + getItem().getProtocol() +
"] and format [" + format + "]");
}
getFormatLabel().setText(talkgroupDetail.getTooltip());
}
else
{
getFormatLabel().setText(" ");
mLog.warn("Integer format combobox does not have a selected value.");
}
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
private Label getFormatLabel()
{
if(mFormatLabel == null)
{
mFormatLabel = new Label(" ");
}
return mFormatLabel;
}
private Label getProtocolLabel()
{
if(mProtocolLabel == null)
{
mProtocolLabel = new Label();
}
return mProtocolLabel;
}
private TextField getTalkgroupField()
{
if(mTalkgroupField == null)
{
mTalkgroupField = new TextField();
mTalkgroupField.setTextFormatter(mIntegerTextFormatter);
}
return mTalkgroupField;
}
public class TalkgroupValueChangeListener implements ChangeListener<Integer>
{
@Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
{
if(getItem() != null)
{
getItem().setValue(newValue != null ? newValue : 0);
modifiedProperty().set(true);
}
}
}
/**
* Talkgroup detail formatting details for the specified protocol and format
* @param protocol for the talkgroup
* @param integerFormat for formatting the value
* @return detail or null
*/
private TalkgroupDetail getTalkgroupDetail(Protocol protocol, IntegerFormat integerFormat)
{
for(TalkgroupDetail detail: mTalkgroupDetails)
{
if(detail.getProtocol() == protocol && detail.getIntegerFormat() == integerFormat)
{
return detail;
}
}
//Use a default instance
for(TalkgroupDetail detail: mTalkgroupDetails)
{
if(detail.getProtocol() == Protocol.UNKNOWN && detail.getIntegerFormat() == integerFormat)
{
return detail;
}
}
mLog.warn("No Talkgroup Detail is configured for protocol [" + protocol + "] and format [" + integerFormat + "]");
return null;
}
private void loadTalkgroupDetails()
{
mTalkgroupDetails.clear();
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
"Format: 0 - 65535"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
"Format: 0 - FFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.FLEETSYNC, IntegerFormat.FORMATTED,
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.LTR, IntegerFormat.FORMATTED, new LtrFormatter(0,0x3FFF),
"Format: A-HH-TTT = Area (0-1), Home (1-31), Talkgroup (1-255)"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
"Format: 0 - 65535"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
"Format: 0 - FFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.MPT1327, IntegerFormat.FORMATTED,
new PrefixIdentFormatter(0,0xFFFFF), "Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
"Format: 0 - 65535"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
"Format: 0 - FFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
"Format: 0 - FFFFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
"Format: 0 - FFFFFF"));
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
"Format: 0 - FFFFFF"));
}
public class TalkgroupDetail
{
private Protocol mProtocol;
private IntegerFormat mIntegerFormat;
private TextFormatter<Integer> mTextFormatter;
private String mTooltip;
public TalkgroupDetail(Protocol protocol, IntegerFormat integerFormat, TextFormatter<Integer> textFormatter,
String tooltip)
{
mProtocol = protocol;
mIntegerFormat = integerFormat;
mTextFormatter = textFormatter;
mTooltip = tooltip;
}
public Protocol getProtocol()
{
return mProtocol;
}
public IntegerFormat getIntegerFormat()
{
return mIntegerFormat;
}
public TextFormatter<Integer> getTextFormatter()
{
return mTextFormatter;
}
public String getTooltip()
{
return mTooltip;
}
}
}

View File

@@ -0,0 +1,364 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.identifier;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.gui.control.HexFormatter;
import io.github.dsheirer.gui.control.IntegerFormatter;
import io.github.dsheirer.gui.control.LtrFormatter;
import io.github.dsheirer.gui.control.PrefixIdentFormatter;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.identifier.IntegerFormat;
import io.github.dsheirer.protocol.Protocol;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.HPos;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Editor for talkgroup range alias identifiers
*/
public class TalkgroupRangeEditor extends IdentifierEditor<TalkgroupRange>
{
private static final Logger mLog = LoggerFactory.getLogger(TalkgroupRangeEditor.class);
private UserPreferences mUserPreferences;
private Label mProtocolLabel;
private Label mFormatLabel;
private TextField mMinTalkgroupField;
private TextField mMaxTalkgroupField;
private TextFormatter<Integer> mMinIntegerTextFormatter;
private TextFormatter<Integer> mMaxIntegerTextFormatter;
private List<TalkgroupDetail> mTalkgroupDetails;
private MinTalkgroupValueChangeListener mMinTalkgroupValueChangeListener = new MinTalkgroupValueChangeListener();
private MaxTalkgroupValueChangeListener mMaxTalkgroupValueChangeListener = new MaxTalkgroupValueChangeListener();
/**
* Constructs an instance
* @param userPreferences for determining user preferred talkgroup formats
*/
public TalkgroupRangeEditor(UserPreferences userPreferences)
{
mUserPreferences = userPreferences;
mTalkgroupDetails = createTalkgroupDetails();
GridPane gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(3);
GridPane.setConstraints(getProtocolLabel(), 0, 0);
gridPane.getChildren().add(getProtocolLabel());
Label valueLabel = new Label("Talkgroup Range");
GridPane.setHalignment(valueLabel, HPos.RIGHT);
GridPane.setConstraints(valueLabel, 1, 0);
gridPane.getChildren().add(valueLabel);
GridPane.setConstraints(getMinTalkgroupField(), 2, 0);
gridPane.getChildren().add(getMinTalkgroupField());
Label dashLabel = new Label("-");
GridPane.setConstraints(dashLabel, 3, 0);
gridPane.getChildren().add(dashLabel);
GridPane.setConstraints(getMaxTalkgroupField(), 4, 0);
gridPane.getChildren().add(getMaxTalkgroupField());
GridPane.setConstraints(getFormatLabel(), 5, 0);
gridPane.getChildren().add(getFormatLabel());
getChildren().add(gridPane);
}
@Override
public void setItem(TalkgroupRange item)
{
super.setItem(item);
TalkgroupRange talkgroupRange = getItem();
getProtocolLabel().setDisable(talkgroupRange == null);
getMinTalkgroupField().setDisable(talkgroupRange == null);
getMaxTalkgroupField().setDisable(talkgroupRange == null);
if(talkgroupRange != null)
{
getProtocolLabel().setText(talkgroupRange.getProtocol().toString());
updateTextFormatter();
}
else
{
getProtocolLabel().setText("Unrecognized Protocol");
getMinTalkgroupField().setText(null);
getMaxTalkgroupField().setText(null);
}
modifiedProperty().set(false);
}
private void updateTextFormatter()
{
if(mMinIntegerTextFormatter != null)
{
mMinIntegerTextFormatter.valueProperty().removeListener(mMinTalkgroupValueChangeListener);
}
if(mMaxIntegerTextFormatter != null)
{
mMaxIntegerTextFormatter.valueProperty().removeListener(mMaxTalkgroupValueChangeListener);
}
IntegerFormat format = mUserPreferences.getTalkgroupFormatPreference().getTalkgroupFormat(getItem().getProtocol());
if(format != null)
{
TalkgroupDetail talkgroupDetail = getTalkgroupDetail(getItem().getProtocol(), format);
if(talkgroupDetail != null)
{
mMinIntegerTextFormatter = talkgroupDetail.getMinTextFormatter();
Integer minValue = getItem() != null ? getItem().getMinTalkgroup() : null;
mMinTalkgroupField.setTextFormatter(mMinIntegerTextFormatter);
mMinTalkgroupField.setTooltip(new Tooltip(talkgroupDetail.getTooltip()));
mMinIntegerTextFormatter.setValue(minValue);
mMinIntegerTextFormatter.valueProperty().addListener(mMinTalkgroupValueChangeListener);
mMaxIntegerTextFormatter = talkgroupDetail.getMaxTextFormatter();
Integer maxValue = getItem() != null ? getItem().getMaxTalkgroup() : null;
mMaxTalkgroupField.setTextFormatter(mMaxIntegerTextFormatter);
mMaxTalkgroupField.setTooltip(new Tooltip(talkgroupDetail.getTooltip()));
mMaxIntegerTextFormatter.setValue(maxValue);
mMaxIntegerTextFormatter.valueProperty().addListener(mMaxTalkgroupValueChangeListener);
getFormatLabel().setText(talkgroupDetail.getTooltip());
}
else
{
mLog.warn("Couldn't find talkgroup detail for protocol [" + getItem().getProtocol() +
"] and format [" + format + "]");
getFormatLabel().setText(" ");
}
}
else
{
getFormatLabel().setText(" ");
mMinIntegerTextFormatter.setValue(null);
mMaxIntegerTextFormatter.setValue(null);
}
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
private Label getFormatLabel()
{
if(mFormatLabel == null)
{
mFormatLabel = new Label(" ");
}
return mFormatLabel;
}
private Label getProtocolLabel()
{
if(mProtocolLabel == null)
{
mProtocolLabel = new Label();
}
return mProtocolLabel;
}
private TextField getMinTalkgroupField()
{
if(mMinTalkgroupField == null)
{
mMinTalkgroupField = new TextField();
mMinTalkgroupField.setTextFormatter(mMinIntegerTextFormatter);
}
return mMinTalkgroupField;
}
private TextField getMaxTalkgroupField()
{
if(mMaxTalkgroupField == null)
{
mMaxTalkgroupField = new TextField();
mMaxTalkgroupField.setTextFormatter(mMinIntegerTextFormatter);
}
return mMaxTalkgroupField;
}
public class MinTalkgroupValueChangeListener implements ChangeListener<Integer>
{
@Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
{
if(getItem() != null)
{
getItem().setMinTalkgroup(newValue != null ? newValue : 0);
modifiedProperty().set(true);
}
}
}
public class MaxTalkgroupValueChangeListener implements ChangeListener<Integer>
{
@Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
{
if(getItem() != null)
{
getItem().setMaxTalkgroup(newValue != null ? newValue : 0);
modifiedProperty().set(true);
}
}
}
/**
* Talkgroup detail formatting details for the specified protocol and format
* @param protocol for the talkgroup
* @param integerFormat for formatting the value
* @return detail or null
*/
private TalkgroupDetail getTalkgroupDetail(Protocol protocol, IntegerFormat integerFormat)
{
for(TalkgroupDetail detail: mTalkgroupDetails)
{
if(detail.getProtocol() == protocol && detail.getIntegerFormat() == integerFormat)
{
return detail;
}
}
//Use a default instance
for(TalkgroupDetail detail: mTalkgroupDetails)
{
if(detail.getProtocol() == Protocol.UNKNOWN && detail.getIntegerFormat() == integerFormat)
{
return detail;
}
}
mLog.warn("No Talkgroup Detail is configured for protocol [" + protocol + "] and format [" + integerFormat + "]");
return null;
}
/**
* Creates a set of talkgroup details with integer text formatters
*/
private List<TalkgroupDetail> createTalkgroupDetails()
{
List<TalkgroupDetail> details = new ArrayList<>();
details.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.DECIMAL, new IntegerFormatter(0,65535),
new IntegerFormatter(0,65535), "Format: 0 - 65535"));
details.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
new HexFormatter(0,65535), "Format: 0 - FFFF"));
details.add(new TalkgroupDetail(Protocol.FLEETSYNC, IntegerFormat.FORMATTED,
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
"Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
details.add(new TalkgroupDetail(Protocol.LTR, IntegerFormat.FORMATTED, new LtrFormatter(0,0x3FFF),
new LtrFormatter(0,0x3FFF), "Format: A-HH-TTT = Area (0-1), Home (1-31), Talkgroup (1-255)"));
details.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
details.add(new TalkgroupDetail(Protocol.MDC1200, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
details.add(new TalkgroupDetail(Protocol.MPT1327, IntegerFormat.FORMATTED,
new PrefixIdentFormatter(0,0xFFFFF), new PrefixIdentFormatter(0,0xFFFFF),
"Format: PPP-IIII = Prefix (0-127), Ident (0-8191)"));
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFF),
new IntegerFormatter(0,0xFFFF), "Format: 0 - 65535"));
details.add(new TalkgroupDetail(Protocol.PASSPORT, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFF),
new HexFormatter(0,0xFFFF), "Format: 0 - FFFF"));
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.DECIMAL, new IntegerFormatter(0,16777215),
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.FORMATTED, new IntegerFormatter(0,16777215),
new IntegerFormatter(0,16777215), "Format: 0 - FFFFFF"));
details.add(new TalkgroupDetail(Protocol.UNKNOWN, IntegerFormat.HEXADECIMAL, new HexFormatter(0,16777215),
new HexFormatter(0,16777215), "Format: 0 - FFFFFF"));
return details;
}
public class TalkgroupDetail
{
private Protocol mProtocol;
private IntegerFormat mIntegerFormat;
private TextFormatter<Integer> mMinTextFormatter;
private TextFormatter<Integer> mMaxTextFormatter;
private String mTooltip;
public TalkgroupDetail(Protocol protocol, IntegerFormat integerFormat, TextFormatter<Integer> minTextFormatter,
TextFormatter<Integer> maxTextFormatter, String tooltip)
{
mProtocol = protocol;
mIntegerFormat = integerFormat;
mMinTextFormatter = minTextFormatter;
mMaxTextFormatter = maxTextFormatter;
mTooltip = tooltip;
}
public Protocol getProtocol()
{
return mProtocol;
}
public IntegerFormat getIntegerFormat()
{
return mIntegerFormat;
}
public TextFormatter<Integer> getMinTextFormatter()
{
return mMinTextFormatter;
}
public TextFormatter<Integer> getMaxTextFormatter()
{
return mMaxTextFormatter;
}
public String getTooltip()
{
return mTooltip;
}
}
}

View File

@@ -0,0 +1,383 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.identifier;
import io.github.dsheirer.alias.id.tone.TonesID;
import io.github.dsheirer.gui.control.IntegerFormatter;
import io.github.dsheirer.identifier.tone.AmbeTone;
import io.github.dsheirer.identifier.tone.Tone;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TonesEditor extends IdentifierEditor<TonesID>
{
private final static Logger mLog = LoggerFactory.getLogger(TonesEditor.class);
private ListView<Tone> mToneListView;
private ComboBox<AmbeTone> mAmbeToneComboBox;
private TextField mDurationTextField;
private IntegerFormatter mDurationIntegerFormatter;
private Button mAddToneButton;
private Button mDeleteToneButton;
private Button mMoveUpButton;
private Button mMoveDownButton;
private DurationValueChangeListener mDurationValueChangeListener = new DurationValueChangeListener();
private AmbeToneComboBoxEventHandler mAmbeToneComboBoxEventHandler = new AmbeToneComboBoxEventHandler();
public TonesEditor()
{
VBox upDownBox = new VBox();
upDownBox.setAlignment(Pos.CENTER);
upDownBox.setSpacing(10);
upDownBox.getChildren().addAll(getMoveUpButton(), getMoveDownButton());
GridPane editorsPane = new GridPane();
editorsPane.setAlignment(Pos.CENTER);
editorsPane.setHgap(5);
editorsPane.setVgap(10);
Label toneLabel = new Label("Tone");
GridPane.setConstraints(toneLabel, 0, 0);
GridPane.setHalignment(toneLabel, HPos.RIGHT);
editorsPane.getChildren().add(toneLabel);
GridPane.setConstraints(getAmbeToneComboBox(), 1, 0);
editorsPane.getChildren().add(getAmbeToneComboBox());
Label durationLabel = new Label("Duration 1-50");
GridPane.setConstraints(durationLabel, 0, 1);
GridPane.setHalignment(durationLabel, HPos.RIGHT);
editorsPane.getChildren().add(durationLabel);
GridPane.setConstraints(getDurationTextField(), 1, 1);
editorsPane.getChildren().add(getDurationTextField());
VBox buttonBox = new VBox();
buttonBox.setAlignment(Pos.CENTER);
buttonBox.setSpacing(10);
buttonBox.getChildren().addAll(getAddToneButton(), getDeleteToneButton());
HBox editorBox = new HBox();
editorBox.setSpacing(10);
HBox.setHgrow(getToneListView(), Priority.ALWAYS);
editorBox.getChildren().addAll(getToneListView(), upDownBox, editorsPane, buttonBox);
getChildren().add(editorBox);
}
@Override
public void setItem(TonesID item)
{
super.setItem(item);
getToneListView().setDisable(item == null);
getAddToneButton().setDisable(item == null);
if(item != null)
{
getToneListView().setItems(item.getToneSequence().tonesProperty());
}
else
{
getToneListView().setItems(FXCollections.emptyObservableList());
}
}
@Override
public void save()
{
//No-op
}
@Override
public void dispose()
{
//No-op
}
private void setTone(Tone tone)
{
getDeleteToneButton().setDisable(tone == null);
mAmbeToneComboBoxEventHandler.disable();
mDurationValueChangeListener.disable();
getAmbeToneComboBox().setDisable(tone == null);
getDurationTextField().setDisable(tone == null);
if(tone != null)
{
getAmbeToneComboBox().getSelectionModel().select(tone.getAmbeTone());
getDurationIntegerFormatter().setValue(tone.getDuration());
}
else
{
getAmbeToneComboBox().getSelectionModel().select(null);
getDurationIntegerFormatter().setValue(2);
}
mAmbeToneComboBoxEventHandler.enable();
mDurationValueChangeListener.enable();
updateMoveButtons();
}
private Button getAddToneButton()
{
if(mAddToneButton == null)
{
mAddToneButton = new Button("Add Tone");
mAddToneButton.setMaxWidth(Double.MAX_VALUE);
mAddToneButton.setDisable(true);
mAddToneButton.setOnAction(event -> {
Tone tone = new Tone(AmbeTone.DTMF_0, 2);
getItem().getToneSequence().addTone(tone);
modifiedProperty().set(true);
getToneListView().getSelectionModel().select(tone);
getToneListView().scrollTo(tone);
});
}
return mAddToneButton;
}
private Button getDeleteToneButton()
{
if(mDeleteToneButton == null)
{
mDeleteToneButton = new Button("Delete Tone");
mDeleteToneButton.setMaxWidth(Double.MAX_VALUE);
mDeleteToneButton.setDisable(true);
mDeleteToneButton.setOnAction(event -> {
Tone selected = getToneListView().getSelectionModel().getSelectedItem();
if(selected != null)
{
getItem().getToneSequence().removeTone(selected);
modifiedProperty().set(true);
}
});
}
return mDeleteToneButton;
}
private void updateMoveButtons()
{
if(getToneListView().getItems().size() >= 2 && getToneListView().getSelectionModel().getSelectedItem() != null)
{
Tone selected = getToneListView().getSelectionModel().getSelectedItem();
getMoveUpButton().setDisable(getToneListView().getItems().indexOf(selected) == 0);
getMoveDownButton().setDisable(getToneListView().getItems().indexOf(selected) >= getToneListView().getItems().size() - 1);
}
else
{
getMoveDownButton().setDisable(true);
getMoveUpButton().setDisable(true);
}
}
private Button getMoveUpButton()
{
if(mMoveUpButton == null)
{
mMoveUpButton = new Button();
IconNode iconNode = new IconNode(FontAwesome.ARROW_UP);
iconNode.setIconSize(12);
iconNode.setFill(getMoveUpButton().getTextFill());
mMoveUpButton.setGraphic(iconNode);
mMoveUpButton.setMaxWidth(Double.MAX_VALUE);
mMoveUpButton.setDisable(true);
mMoveUpButton.setOnAction(event -> {
Tone selected = getToneListView().getSelectionModel().getSelectedItem();
if(selected != null)
{
int index = getItem().getToneSequence().getTones().indexOf(selected);
getItem().getToneSequence().getTones().remove(selected);
getItem().getToneSequence().getTones().add(index - 1, selected);
getToneListView().getSelectionModel().select(selected);
}
});
}
return mMoveUpButton;
}
private Button getMoveDownButton()
{
if(mMoveDownButton == null)
{
mMoveDownButton = new Button();
IconNode iconNode = new IconNode(FontAwesome.ARROW_DOWN);
iconNode.setIconSize(12);
iconNode.setFill(getMoveDownButton().getTextFill());
mMoveDownButton.setGraphic(iconNode);
mMoveDownButton.setMaxWidth(Double.MAX_VALUE);
mMoveDownButton.setDisable(true);
mMoveDownButton.setOnAction(event -> {
Tone selected = getToneListView().getSelectionModel().getSelectedItem();
if(selected != null)
{
int index = getItem().getToneSequence().getTones().indexOf(selected);
getItem().getToneSequence().getTones().remove(selected);
getItem().getToneSequence().getTones().add(index + 1, selected);
getToneListView().getSelectionModel().select(selected);
}
});
}
return mMoveDownButton;
}
private ListView<Tone> getToneListView()
{
if(mToneListView == null)
{
mToneListView = new ListView<>();
mToneListView.setDisable(true);
mToneListView.setPrefHeight(75);
mToneListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
setTone(newValue);
});
}
return mToneListView;
}
private ComboBox<AmbeTone> getAmbeToneComboBox()
{
if(mAmbeToneComboBox == null)
{
mAmbeToneComboBox = new ComboBox<>();
mAmbeToneComboBox.setTooltip(new Tooltip("Tone to match"));
mAmbeToneComboBox.getItems().addAll(AmbeTone.ALL_VALID_TONES);
mAmbeToneComboBox.setDisable(true);
mAmbeToneComboBox.setOnAction(mAmbeToneComboBoxEventHandler);
}
return mAmbeToneComboBox;
}
private TextField getDurationTextField()
{
if(mDurationTextField == null)
{
mDurationTextField = new TextField();
mDurationTextField.setTooltip(new Tooltip("Duration in 20 millisecond units"));
mDurationTextField.setMaxWidth(Double.MAX_VALUE);
mDurationTextField.setTextFormatter(getDurationIntegerFormatter());
mDurationTextField.setDisable(true);
}
return mDurationTextField;
}
private IntegerFormatter getDurationIntegerFormatter()
{
if(mDurationIntegerFormatter == null)
{
mDurationIntegerFormatter = new IntegerFormatter(1, 50);
mDurationIntegerFormatter.valueProperty().addListener(mDurationValueChangeListener);
}
return mDurationIntegerFormatter;
}
private class DurationValueChangeListener implements ChangeListener<Integer>
{
private boolean mEnabled = false;
@Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
{
if(mEnabled && newValue != null)
{
Tone selected = getToneListView().getSelectionModel().getSelectedItem();
if(selected != null)
{
selected.setDuration(newValue);
}
}
}
public void enable()
{
mEnabled = true;
}
public void disable()
{
mEnabled = false;
}
}
private class AmbeToneComboBoxEventHandler implements EventHandler<ActionEvent>
{
private boolean mEnabled = false;
@Override
public void handle(ActionEvent event)
{
if(mEnabled)
{
Tone selectedTone = getToneListView().getSelectionModel().getSelectedItem();
if(selectedTone != null)
{
AmbeTone ambeTone = mAmbeToneComboBox.getSelectionModel().getSelectedItem();
selectedTone.setAmbeTone(ambeTone);
modifiedProperty().set(true);
}
}
}
public void enable()
{
mEnabled = true;
}
public void disable()
{
mEnabled = false;
}
}
}

View File

@@ -0,0 +1,140 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.identifier;
import io.github.dsheirer.alias.id.status.UnitStatusID;
import io.github.dsheirer.alias.id.talkgroup.Talkgroup;
import io.github.dsheirer.gui.control.HexFormatter;
import io.github.dsheirer.gui.control.IntegerFormatter;
import io.github.dsheirer.gui.control.LtrFormatter;
import io.github.dsheirer.gui.control.PrefixIdentFormatter;
import io.github.dsheirer.preference.identifier.IntegerFormat;
import io.github.dsheirer.protocol.Protocol;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.HPos;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Editor for unit status alias identifiers
*/
public class UnitStatusEditor extends IdentifierEditor<UnitStatusID>
{
private static final Logger mLog = LoggerFactory.getLogger(UnitStatusEditor.class);
private TextField mUnitStatusField;
private TextFormatter<Integer> mIntegerTextFormatter;
/**
* Constructs an instance
*/
public UnitStatusEditor()
{
GridPane gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(3);
Label valueLabel = new Label("Unit Status");
GridPane.setHalignment(valueLabel, HPos.RIGHT);
GridPane.setConstraints(valueLabel, 0, 0);
gridPane.getChildren().add(valueLabel);
GridPane.setConstraints(getUnitStatusField(), 1, 0);
gridPane.getChildren().add(getUnitStatusField());
Label helpLabel = new Label("Format: 0 - 255");
GridPane.setConstraints(helpLabel, 2, 0);
gridPane.getChildren().add(helpLabel);
getChildren().add(gridPane);
}
@Override
public void setItem(UnitStatusID item)
{
super.setItem(item);
UnitStatusID unitStatus = getItem();
getUnitStatusField().setDisable(unitStatus == null);
if(unitStatus != null)
{
getTextFormatter().setValue(unitStatus.getStatus());
}
else
{
getTextFormatter().setValue(null);
}
modifiedProperty().set(false);
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
private TextField getUnitStatusField()
{
if(mUnitStatusField == null)
{
mUnitStatusField = new TextField();
mUnitStatusField.setTextFormatter(getTextFormatter());
}
return mUnitStatusField;
}
private TextFormatter<Integer> getTextFormatter()
{
if(mIntegerTextFormatter == null)
{
mIntegerTextFormatter = new IntegerFormatter(0, 255);
mIntegerTextFormatter.valueProperty().addListener(new ChangeListener<Integer>()
{
@Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
{
if(getItem() != null)
{
getItem().setStatus(newValue != null ? newValue : 0);
modifiedProperty().set(true);
}
}
});
}
return mIntegerTextFormatter;
}
}

View File

@@ -0,0 +1,49 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.identifier;
import io.github.dsheirer.alias.id.AliasID;
import javafx.scene.control.Label;
/**
* Empty alias identifier editor
*/
public class UnrecognizedIdentifierEditor extends IdentifierEditor<AliasID>
{
public UnrecognizedIdentifierEditor()
{
Label label = new Label("Unrecognized identifier type. For unsupported legacy types, please delete and " +
"recreate with a supported identifier type.");
label.setWrapText(true);
getChildren().add(label);
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
}

View File

@@ -0,0 +1,133 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.alias.identifier;
import io.github.dsheirer.alias.id.status.UserStatusID;
import io.github.dsheirer.gui.control.IntegerFormatter;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.HPos;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.layout.GridPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Editor for user status alias identifiers
*/
public class UserStatusEditor extends IdentifierEditor<UserStatusID>
{
private static final Logger mLog = LoggerFactory.getLogger(UserStatusEditor.class);
private TextField mUserStatusField;
private TextFormatter<Integer> mIntegerTextFormatter;
/**
* Constructs an instance
*/
public UserStatusEditor()
{
GridPane gridPane = new GridPane();
gridPane.setHgap(5);
gridPane.setVgap(3);
Label valueLabel = new Label("User Status");
GridPane.setHalignment(valueLabel, HPos.RIGHT);
GridPane.setConstraints(valueLabel, 0, 0);
gridPane.getChildren().add(valueLabel);
GridPane.setConstraints(getUserStatusField(), 1, 0);
gridPane.getChildren().add(getUserStatusField());
Label helpLabel = new Label("Format: 0 - 255");
GridPane.setConstraints(helpLabel, 2, 0);
gridPane.getChildren().add(helpLabel);
getChildren().add(gridPane);
}
@Override
public void setItem(UserStatusID item)
{
super.setItem(item);
UserStatusID userStatus = getItem();
getUserStatusField().setDisable(userStatus == null);
if(userStatus != null)
{
getTextFormatter().setValue(userStatus.getStatus());
}
else
{
getTextFormatter().setValue(null);
}
modifiedProperty().set(false);
}
@Override
public void save()
{
//no-op
}
@Override
public void dispose()
{
//no-op
}
private TextField getUserStatusField()
{
if(mUserStatusField == null)
{
mUserStatusField = new TextField();
mUserStatusField.setTextFormatter(getTextFormatter());
}
return mUserStatusField;
}
private TextFormatter<Integer> getTextFormatter()
{
if(mIntegerTextFormatter == null)
{
mIntegerTextFormatter = new IntegerFormatter(0, 255);
mIntegerTextFormatter.valueProperty().addListener(new ChangeListener<Integer>()
{
@Override
public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue)
{
if(getItem() != null)
{
getItem().setStatus(newValue != null ? newValue : 0);
modifiedProperty().set(true);
}
}
});
}
return mIntegerTextFormatter;
}
}

View File

@@ -0,0 +1,277 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.channel;
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
import io.github.dsheirer.gui.playlist.source.SourceConfigurationEditor;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.am.DecodeConfigAM;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.source.config.SourceConfiguration;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.GridPane;
import javafx.scene.text.TextAlignment;
import org.controlsfx.control.ToggleSwitch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* AM channel configuration editor
*/
public class AMConfigurationEditor extends ChannelConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger(AMConfigurationEditor.class);
private TitledPane mDecoderPane;
private TitledPane mRecordPane;
private TitledPane mSourcePane;
private ToggleSwitch mAudioRecordSwitch;
private ToggleSwitch mBasebandRecordSwitch;
private SourceConfigurationEditor mSourceConfigurationEditor;
/**
* Constructs an instance
* @param playlistManager
*/
public AMConfigurationEditor(PlaylistManager playlistManager)
{
super(playlistManager);
getTitledPanesBox().getChildren().add(getSourcePane());
getTitledPanesBox().getChildren().add(getDecoderPane());
getTitledPanesBox().getChildren().add(getRecordPane());
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.AM;
}
private TitledPane getSourcePane()
{
if(mSourcePane == null)
{
mSourcePane = new TitledPane("Source", getSourceConfigurationEditor());
mSourcePane.setExpanded(true);
}
return mSourcePane;
}
private TitledPane getDecoderPane()
{
if(mDecoderPane == null)
{
mDecoderPane = new TitledPane();
mDecoderPane.setText("Decoder: AM");
mDecoderPane.setExpanded(false);
mDecoderPane.setDisable(true);
}
return mDecoderPane;
}
private TitledPane getRecordPane()
{
if(mRecordPane == null)
{
mRecordPane = new TitledPane();
mRecordPane.setText("Recording");
mRecordPane.setExpanded(false);
GridPane gridPane = new GridPane();
gridPane.setPadding(new Insets(10,10,10,10));
gridPane.setHgap(10);
gridPane.setVgap(10);
GridPane.setConstraints(getAudioRecordSwitch(), 0, 0);
gridPane.getChildren().add(getAudioRecordSwitch());
Label recordAudioLabel = new Label("Audio");
GridPane.setHalignment(recordAudioLabel, HPos.LEFT);
GridPane.setConstraints(recordAudioLabel, 1, 0);
gridPane.getChildren().add(recordAudioLabel);
GridPane.setConstraints(getBasebandRecordSwitch(), 0, 1);
gridPane.getChildren().add(getBasebandRecordSwitch());
Label recordBasebandLabel = new Label("Channel (Baseband I&Q)");
GridPane.setHalignment(recordBasebandLabel, HPos.LEFT);
GridPane.setConstraints(recordBasebandLabel, 1, 1);
gridPane.getChildren().add(recordBasebandLabel);
mRecordPane.setContent(gridPane);
}
return mRecordPane;
}
private SourceConfigurationEditor getSourceConfigurationEditor()
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mSourceConfigurationEditor;
}
private ToggleSwitch getAudioRecordSwitch()
{
if(mAudioRecordSwitch == null)
{
mAudioRecordSwitch = new ToggleSwitch();
mAudioRecordSwitch.setDisable(true);
mAudioRecordSwitch.setTextAlignment(TextAlignment.RIGHT);
mAudioRecordSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mAudioRecordSwitch;
}
private ToggleSwitch getBasebandRecordSwitch()
{
if(mBasebandRecordSwitch == null)
{
mBasebandRecordSwitch = new ToggleSwitch();
mBasebandRecordSwitch.setDisable(true);
mBasebandRecordSwitch.setTextAlignment(TextAlignment.RIGHT);
mBasebandRecordSwitch.selectedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mBasebandRecordSwitch;
}
@Override
protected void setDecoderConfiguration(DecodeConfiguration config)
{
if(config instanceof DecodeConfigAM)
{
DecodeConfigAM decodeConfig = (DecodeConfigAM)config;
getAudioRecordSwitch().setDisable(false);
getAudioRecordSwitch().selectedProperty().set(decodeConfig.getRecordAudio());
}
else
{
getAudioRecordSwitch().setDisable(true);
getAudioRecordSwitch().selectedProperty().set(false);
}
}
@Override
protected void saveDecoderConfiguration()
{
DecodeConfigAM config;
if(getItem().getDecodeConfiguration() instanceof DecodeConfigAM)
{
config = (DecodeConfigAM)getItem().getDecodeConfiguration();
}
else
{
config = new DecodeConfigAM();
}
config.setRecordAudio(getAudioRecordSwitch().isSelected());
getItem().setDecodeConfiguration(config);
}
@Override
protected void setEventLogConfiguration(EventLogConfiguration config)
{
//no-op
}
@Override
protected void saveEventLogConfiguration()
{
//no-op
}
@Override
protected void setAuxDecoderConfiguration(AuxDecodeConfiguration config)
{
//no-op
}
@Override
protected void saveAuxDecoderConfiguration()
{
//no-op
}
@Override
protected void setRecordConfiguration(RecordConfiguration config)
{
if(config != null)
{
getBasebandRecordSwitch().setDisable(false);
getBasebandRecordSwitch().selectedProperty().set(config.contains(RecorderType.BASEBAND));
}
else
{
getBasebandRecordSwitch().selectedProperty().set(false);
getBasebandRecordSwitch().setDisable(true);
}
}
@Override
protected void saveRecordConfiguration()
{
RecordConfiguration config = new RecordConfiguration();
if(getBasebandRecordSwitch().selectedProperty().get())
{
config.addRecorder(RecorderType.BASEBAND);
}
getItem().setRecordConfiguration(config);
}
@Override
protected void setSourceConfiguration(SourceConfiguration config)
{
getSourceConfigurationEditor().setSourceConfiguration(config);
}
@Override
protected void saveSourceConfiguration()
{
getSourceConfigurationEditor().save();
SourceConfiguration sourceConfiguration = getSourceConfigurationEditor().getSourceConfiguration();
getItem().setSourceConfiguration(sourceConfiguration);
}
}

View File

@@ -0,0 +1,687 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.channel;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.controller.channel.ChannelException;
import io.github.dsheirer.gui.control.MaxLengthUnaryOperator;
import io.github.dsheirer.gui.playlist.Editor;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.TunerModel;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextInputDialog;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconNode;
import org.controlsfx.control.ToggleSwitch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Optional;
import java.util.function.Predicate;
/**
* Channel configuration editor
*/
public abstract class ChannelConfigurationEditor extends Editor<Channel>
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelConfigurationEditor.class);
private PlaylistManager mPlaylistManager;
protected EditorModificationListener mEditorModificationListener = new EditorModificationListener();
private Button mPlayButton;
private TextField mSystemField;
private TextField mSiteField;
private TextField mNameField;
private ComboBox<String> mAliasListComboBox;
private Button mNewAliasListButton;
private GridPane mTextFieldPane;
private Button mSaveButton;
private Button mResetButton;
private VBox mButtonBox;
private ScrollPane mTitledPanesScrollPane;
private VBox mTitledPanesBox;
private ToggleSwitch mAutoStartSwitch;
private Spinner<Integer> mAutoStartOrderSpinner;
private IconNode mPlayGraphicNode;
private IconNode mStopGraphicNode;
private ChannelProcessingMonitor mChannelProcessingMonitor = new ChannelProcessingMonitor();
public ChannelConfigurationEditor(PlaylistManager playlistManager)
{
mPlaylistManager = playlistManager;
setMaxWidth(Double.MAX_VALUE);
HBox hbox = new HBox();
hbox.setSpacing(10);
hbox.setPadding(new Insets(10,10,10,10));
hbox.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(getTextFieldPane(), Priority.ALWAYS);
HBox.setHgrow(getButtonBox(), Priority.NEVER);
hbox.getChildren().addAll(getTextFieldPane(), getButtonBox());
VBox.setVgrow(getTitledPanesScrollPane(), Priority.ALWAYS);
getChildren().addAll(hbox, getTitledPanesScrollPane());
}
/**
* Provides subclass access to the playlist manager and related components
*/
protected PlaylistManager getPlaylistManager()
{
return mPlaylistManager;
}
@Override
public void dispose()
{
}
public abstract DecoderType getDecoderType();
@Override
public void setItem(Channel channel)
{
if(getItem() != null)
{
getItem().processingProperty().removeListener(mChannelProcessingMonitor);
}
super.setItem(channel);
if(getItem() != null)
{
setPlayButtonState(getItem().processingProperty().get());
getItem().processingProperty().addListener(mChannelProcessingMonitor);
}
boolean disable = (channel == null);
getPlayButton().setDisable(disable);
getSystemField().setDisable(disable);
getSiteField().setDisable(disable);
getNameField().setDisable(disable);
getAliasListComboBox().setDisable(disable);
getNewAliasListButton().setDisable(disable);
getAutoStartSwitch().setDisable(disable);
if(channel != null)
{
getSystemField().setText(channel.getSystem());
getSiteField().setText(channel.getSite());
getNameField().setText(channel.getName());
String aliasListName = channel.getAliasListName();
if(aliasListName != null)
{
if(!getAliasListComboBox().getItems().contains(aliasListName))
{
mPlaylistManager.getAliasModel().addAliasList(aliasListName);
}
getAliasListComboBox().getSelectionModel().select(aliasListName);
}
else
{
getAliasListComboBox().getSelectionModel().select(null);
}
getAutoStartSwitch().selectedProperty().set(channel.isAutoStart());
getAutoStartOrderSpinner().setDisable(!channel.isAutoStart());
Integer order = channel.getAutoStartOrder();
getAutoStartOrderSpinner().getValueFactory().setValue(order != null ? order : 0);
setDecoderConfiguration(channel.getDecodeConfiguration());
SourceConfiguration sourceConfiguration = channel.getSourceConfiguration();
if(sourceConfiguration == null)
{
sourceConfiguration = new SourceConfigTuner();
}
setSourceConfiguration(sourceConfiguration);
AuxDecodeConfiguration auxDecodeConfiguration = channel.getAuxDecodeConfiguration();
if(auxDecodeConfiguration == null)
{
auxDecodeConfiguration = new AuxDecodeConfiguration();
}
setAuxDecoderConfiguration(auxDecodeConfiguration);
EventLogConfiguration eventLogConfiguration = channel.getEventLogConfiguration();
if(eventLogConfiguration == null)
{
eventLogConfiguration = new EventLogConfiguration();
}
setEventLogConfiguration(eventLogConfiguration);
RecordConfiguration recordConfiguration = channel.getRecordConfiguration();
if(recordConfiguration == null)
{
recordConfiguration = new RecordConfiguration();
}
setRecordConfiguration(recordConfiguration);
}
else
{
getSystemField().setText(null);
getSiteField().setText(null);
getNameField().setText(null);
getAliasListComboBox().getSelectionModel().select(null);
getAutoStartSwitch().selectedProperty().set(false);
getAutoStartOrderSpinner().setDisable(true);
getAutoStartOrderSpinner().getValueFactory().setValue(0);
setDecoderConfiguration(null);
setAuxDecoderConfiguration(null);
setEventLogConfiguration(null);
setRecordConfiguration(null);
setSourceConfiguration(null);
}
modifiedProperty().setValue(false);
}
protected TunerModel getTunerModel()
{
return mPlaylistManager.getTunerModel();
}
@Override
public void save()
{
if(modifiedProperty().get())
{
getItem().setSystem(getSystemField().getText());
getItem().setSite(getSiteField().getText());
getItem().setName(getNameField().getText());
getItem().setAliasListName(getAliasListComboBox().getSelectionModel().getSelectedItem());
getItem().setAutoStart(getAutoStartSwitch().isSelected());
Integer order = getAutoStartOrderSpinner().getValue();
if(order == null || order < 1)
{
getItem().setAutoStartOrder(null);
}
else
{
getItem().setAutoStartOrder(getAutoStartOrderSpinner().getValue());
}
saveDecoderConfiguration();
saveAuxDecoderConfiguration();
saveEventLogConfiguration();
saveRecordConfiguration();
saveSourceConfiguration();
modifiedProperty().set(false);
}
}
protected abstract void setAuxDecoderConfiguration(AuxDecodeConfiguration config);
protected abstract void saveAuxDecoderConfiguration();
protected abstract void setDecoderConfiguration(DecodeConfiguration config);
protected abstract void saveDecoderConfiguration();
protected abstract void setEventLogConfiguration(EventLogConfiguration config);
protected abstract void saveEventLogConfiguration();
protected abstract void setRecordConfiguration(RecordConfiguration config);
protected abstract void saveRecordConfiguration();
protected abstract void setSourceConfiguration(SourceConfiguration config);
protected abstract void saveSourceConfiguration();
private Button getPlayButton()
{
if(mPlayButton == null)
{
mPlayGraphicNode = new IconNode(FontAwesome.PLAY);
mPlayGraphicNode.setFill(Color.GREEN);
mPlayGraphicNode.setIconSize(24);
mStopGraphicNode = new IconNode(FontAwesome.STOP);
mStopGraphicNode.setFill(Color.RED);
mStopGraphicNode.setIconSize(24);
mPlayButton = new Button("Play");
mPlayButton.setMaxWidth(Double.MAX_VALUE);
mPlayButton.setMaxHeight(Double.MAX_VALUE);
mPlayButton.setDisable(true);
mPlayButton.setOnAction((ActionEvent event) -> {
if(getItem() != null)
{
if(getItem().processingProperty().get())
{
try
{
mPlaylistManager.getChannelProcessingManager().stop(getItem());
}
catch(ChannelException ce)
{
mLog.error("Error stopping channel [" + getItem().getName() + "] - " + ce.getMessage());
Alert alert = new Alert(Alert.AlertType.ERROR, "Error: " + ce.getMessage(), ButtonType.OK);
alert.setTitle("Channel Stop Error");
alert.setHeaderText("Can't stop channel");
alert.initOwner((getPlayButton()).getScene().getWindow());
alert.showAndWait();
}
}
else
{
try
{
if(modifiedProperty().get())
{
Alert alert = new Alert(Alert.AlertType.WARNING,
"Do you want to save these changes before play?", ButtonType.YES, ButtonType.NO);
alert.setTitle("Channel Configuration Modified");
alert.setHeaderText("Channel configuration has unsaved changes");
alert.initOwner((getPlayButton()).getScene().getWindow());
alert.showAndWait().ifPresent(buttonType -> {
if(buttonType == ButtonType.YES)
{
save();
}
});
}
mPlaylistManager.getChannelProcessingManager().start(getItem());
}
catch(ChannelException ce)
{
Alert alert = new Alert(Alert.AlertType.ERROR, "Error: " + ce.getMessage(), ButtonType.OK);
alert.setTitle("Channel Play Error");
alert.setHeaderText("Can't play channel");
alert.initOwner((getPlayButton()).getScene().getWindow());
alert.showAndWait();
}
}
}
});
}
return mPlayButton;
}
/**
* Toggles the text and graphic of the channel play button to reflect the playing state of the channel
*/
private void setPlayButtonState(boolean playing)
{
if(playing)
{
getPlayButton().setText("Stop");
getPlayButton().setGraphic(mStopGraphicNode);
}
else
{
getPlayButton().setText("Play");
getPlayButton().setGraphic(mPlayGraphicNode);
}
}
private ToggleSwitch getAutoStartSwitch()
{
if(mAutoStartSwitch == null)
{
mAutoStartSwitch = new ToggleSwitch();
mAutoStartSwitch.setDisable(true);
mAutoStartSwitch.setDisable(true);
mAutoStartSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mAutoStartSwitch;
}
private Spinner<Integer> getAutoStartOrderSpinner()
{
if(mAutoStartOrderSpinner == null)
{
mAutoStartOrderSpinner = new Spinner();
mAutoStartOrderSpinner.setPrefWidth(100);
mAutoStartOrderSpinner.setDisable(true);
getAutoStartSwitch().selectedProperty().addListener(new ChangeListener<Boolean>()
{
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
{
getAutoStartOrderSpinner().setDisable(!getAutoStartSwitch().selectedProperty().getValue());
}
});
SpinnerValueFactory svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 99);
mAutoStartOrderSpinner.setValueFactory(svf);
mAutoStartOrderSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
mAutoStartOrderSpinner.valueProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mAutoStartOrderSpinner;
}
protected VBox getTitledPanesBox()
{
if(mTitledPanesBox == null)
{
mTitledPanesBox = new VBox();
mTitledPanesBox.setMaxWidth(Double.MAX_VALUE);
}
return mTitledPanesBox;
}
private ScrollPane getTitledPanesScrollPane()
{
if(mTitledPanesScrollPane == null)
{
mTitledPanesScrollPane = new ScrollPane();
mTitledPanesScrollPane.setFitToWidth(true);
mTitledPanesScrollPane.setContent(getTitledPanesBox());
}
return mTitledPanesScrollPane;
}
private GridPane getTextFieldPane()
{
if(mTextFieldPane == null)
{
mTextFieldPane = new GridPane();
mTextFieldPane.setVgap(10);
mTextFieldPane.setHgap(10);
int row = 0;
Label systemLabel = new Label("System");
GridPane.setHalignment(systemLabel, HPos.RIGHT);
GridPane.setConstraints(systemLabel, 0, row);
mTextFieldPane.getChildren().add(systemLabel);
GridPane.setConstraints(getSystemField(), 1, row);
GridPane.setHgrow(getSystemField(), Priority.ALWAYS);
mTextFieldPane.getChildren().add(getSystemField());
Label autoStartLabel = new Label("Auto-Start");
GridPane.setHalignment(autoStartLabel, HPos.RIGHT);
GridPane.setConstraints(autoStartLabel, 2, row);
mTextFieldPane.getChildren().add(autoStartLabel);
GridPane.setConstraints(getAutoStartSwitch(), 3, row);
GridPane.setHalignment(getAutoStartSwitch(), HPos.LEFT);
mTextFieldPane.getChildren().add(getAutoStartSwitch());
GridPane.setConstraints(getPlayButton(), 4, row, 1, 2);
mTextFieldPane.getChildren().add(getPlayButton());
Label siteLabel = new Label("Site");
GridPane.setHalignment(siteLabel, HPos.RIGHT);
GridPane.setConstraints(siteLabel, 0, ++row);
mTextFieldPane.getChildren().add(siteLabel);
GridPane.setConstraints(getSiteField(), 1, row);
GridPane.setHgrow(getSiteField(), Priority.ALWAYS);
mTextFieldPane.getChildren().add(getSiteField());
Label autoStartOrderLabel = new Label("Start Order");
GridPane.setHalignment(autoStartOrderLabel, HPos.RIGHT);
GridPane.setConstraints(autoStartOrderLabel, 2, row);
mTextFieldPane.getChildren().add(autoStartOrderLabel);
GridPane.setConstraints(getAutoStartOrderSpinner(), 3, row);
GridPane.setHalignment(getAutoStartOrderSpinner(), HPos.LEFT);
mTextFieldPane.getChildren().add(getAutoStartOrderSpinner());
Label nameLabel = new Label("Name");
GridPane.setHalignment(nameLabel, HPos.RIGHT);
GridPane.setConstraints(nameLabel, 0, ++row);
mTextFieldPane.getChildren().add(nameLabel);
GridPane.setConstraints(getNameField(), 1, row);
GridPane.setHgrow(getNameField(), Priority.ALWAYS);
mTextFieldPane.getChildren().add(getNameField());
Label aliasListLabel = new Label("Alias List");
GridPane.setHalignment(aliasListLabel, HPos.RIGHT);
GridPane.setConstraints(aliasListLabel, 2, row);
mTextFieldPane.getChildren().add(aliasListLabel);
GridPane.setConstraints(getAliasListComboBox(), 3, row);
GridPane.setHgrow(getAliasListComboBox(), Priority.ALWAYS);
mTextFieldPane.getChildren().add(getAliasListComboBox());
GridPane.setConstraints(getNewAliasListButton(), 4, row);
mTextFieldPane.getChildren().add(getNewAliasListButton());
}
return mTextFieldPane;
}
protected TextField getSystemField()
{
if(mSystemField == null)
{
mSystemField = new TextField();
mSystemField.setDisable(true);
mSystemField.setMaxWidth(Double.MAX_VALUE);
mSystemField.textProperty().addListener(mEditorModificationListener);
}
return mSystemField;
}
protected TextField getSiteField()
{
if(mSiteField == null)
{
mSiteField = new TextField();
mSiteField.setDisable(true);
mSiteField.setMaxWidth(Double.MAX_VALUE);
mSiteField.textProperty().addListener(mEditorModificationListener);
}
return mSiteField;
}
protected TextField getNameField()
{
if(mNameField == null)
{
mNameField = new TextField();
mNameField.setDisable(true);
mNameField.setMaxWidth(Double.MAX_VALUE);
mNameField.textProperty().addListener(mEditorModificationListener);
}
return mNameField;
}
protected ComboBox<String> getAliasListComboBox()
{
if(mAliasListComboBox == null)
{
Predicate<String> filterPredicate = s -> !s.contentEquals(AliasModel.NO_ALIAS_LIST);
FilteredList<String> filteredChannelList =
new FilteredList<>(mPlaylistManager.getAliasModel().aliasListNames(), filterPredicate);
mAliasListComboBox = new ComboBox<>(filteredChannelList);
mAliasListComboBox.setPrefWidth(150);
mAliasListComboBox.setDisable(true);
mAliasListComboBox.setEditable(false);
mAliasListComboBox.setMaxWidth(Double.MAX_VALUE);
mAliasListComboBox.setOnAction(event -> modifiedProperty().set(true));
}
return mAliasListComboBox;
}
private Button getNewAliasListButton()
{
if(mNewAliasListButton == null)
{
mNewAliasListButton = new Button("New Alias List");
mNewAliasListButton.setDisable(true);
mNewAliasListButton.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
TextInputDialog dialog = new TextInputDialog();
dialog.setTitle("Create New Alias List");
dialog.setHeaderText("Please enter an alias list name (max 25 chars).");
dialog.setContentText("Name:");
dialog.getEditor().setTextFormatter(new TextFormatter<String>(new MaxLengthUnaryOperator(25)));
Optional<String> result = dialog.showAndWait();
result.ifPresent(s -> {
String name = result.get();
if(name != null && !name.isEmpty())
{
name = name.trim();
mPlaylistManager.getAliasModel().addAliasList(name);
getAliasListComboBox().getSelectionModel().select(name);
}
});
}
});
}
return mNewAliasListButton;
}
private VBox getButtonBox()
{
if(mButtonBox == null)
{
mButtonBox = new VBox();
mButtonBox.setSpacing(10);
mButtonBox.getChildren().addAll(getSaveButton(), getResetButton());
}
return mButtonBox;
}
private Button getSaveButton()
{
if(mSaveButton == null)
{
mSaveButton = new Button(" Save ");
mSaveButton.setMaxWidth(Double.MAX_VALUE);
mSaveButton.disableProperty().bind(modifiedProperty().not());
mSaveButton.setOnAction(event -> {
save();
if(getItem().isProcessing())
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Would you like to restart the channel?", ButtonType.YES, ButtonType.NO);
alert.setTitle("Restart Channel?");
alert.setHeaderText("Channel configuration has changed");
alert.initOwner((getPlayButton()).getScene().getWindow());
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.YES)
{
try
{
mPlaylistManager.getChannelProcessingManager().stop(getItem());
mPlaylistManager.getChannelProcessingManager().start(getItem());
}
catch(ChannelException se)
{
mLog.error("Error restarting channel", se);
}
}
}
});
}
return mSaveButton;
}
private Button getResetButton()
{
if(mResetButton == null)
{
mResetButton = new Button("Reset");
mResetButton.setMaxWidth(Double.MAX_VALUE);
mResetButton.disableProperty().bind(modifiedProperty().not());
mResetButton.setOnAction(event -> {
modifiedProperty().set(false);
setItem(getItem());
});
}
return mResetButton;
}
/**
* Simple string change listener that sets the editor modified flag to true any time text fields are edited.
*/
public class EditorModificationListener implements ChangeListener<String>
{
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
{
modifiedProperty().set(true);
}
}
public class ChannelProcessingMonitor implements ChangeListener<Boolean>
{
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
{
if(getItem() != null && newValue != null)
{
setPlayButtonState(newValue);
}
}
}
}

View File

@@ -0,0 +1,76 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.channel;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.playlist.PlaylistManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Provides access to channel configuration editors for various decoder types.
*/
public class ChannelConfigurationEditorFactory
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelConfigurationEditorFactory.class);
private static List<DecoderType> mLoggedUnrecognizedTypes = new ArrayList<>();
/**
* Constructs an editor for the specified decoder type
* @param decoderType to create
* @param playlistManager for the editor
* @return constructed editor
*/
public static ChannelConfigurationEditor getEditor(DecoderType decoderType, PlaylistManager playlistManager)
{
switch(decoderType)
{
case AM:
return new AMConfigurationEditor(playlistManager);
case NBFM:
return new NBFMConfigurationEditor(playlistManager);
case LTR_NET:
return new LTRNetConfigurationEditor(playlistManager);
case LTR:
return new LTRConfigurationEditor(playlistManager);
case MPT1327:
return new MPT1327ConfigurationEditor(playlistManager);
case PASSPORT:
return new PassportConfigurationEditor(playlistManager);
case P25_PHASE1:
return new P25P1ConfigurationEditor(playlistManager);
case P25_PHASE2:
return new P25P2ConfigurationEditor(playlistManager);
default:
if(decoderType != null && !mLoggedUnrecognizedTypes.contains(decoderType))
{
mLog.warn("Can't create channel configuration editor - unrecognized decoder type: " + decoderType);
mLoggedUnrecognizedTypes.add(decoderType);
}
return null;
}
}
}

View File

@@ -0,0 +1,715 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.channel;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.module.decode.DecoderFactory;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.playlist.PlaylistManager;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.util.Callback;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconNode;
import org.controlsfx.control.SegmentedButton;
import org.controlsfx.control.textfield.TextFields;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
/**
* JavaFX editor for managing channel configurations.
*/
public class ChannelEditor extends SplitPane
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelEditor.class);
private PlaylistManager mPlaylistManager;
private TableView<Channel> mChannelTableView;
private Label mPlaceholderLabel;
private MenuButton mNewButton;
private Button mDeleteButton;
private Button mCloneButton;
private VBox mButtonBox;
private HBox mSearchAndViewBox;
private TextField mSearchField;
private SegmentedButton mViewSegmentedButton;
private ToggleButton mAllToggleButton;
private ToggleButton mAutoStartToggleButton;
private ToggleButton mPlayingToggleButton;
private ChannelConfigurationEditor mChannelConfigurationEditor;
private UnknownConfigurationEditor mUnknownConfigurationEditor;
private Map<DecoderType,ChannelConfigurationEditor> mChannelConfigurationEditorMap = new HashMap();
private FilteredList<Channel> mChannelFilteredList;
private ChannelListFilter mChannelListFilter = new ChannelListFilter();
/**
* Constructs an instance
* @param playlistManager containing playlists and channel configurations
*/
public ChannelEditor(PlaylistManager playlistManager)
{
mPlaylistManager = playlistManager;
mUnknownConfigurationEditor = new UnknownConfigurationEditor(mPlaylistManager);
HBox channelsBox = new HBox();
channelsBox.setSpacing(10.0);
HBox.setHgrow(getChannelTableView(), Priority.ALWAYS);
channelsBox.getChildren().addAll(getChannelTableView(), getButtonBox());
VBox topBox = new VBox();
topBox.setPadding(new Insets(10,10,10,10));
topBox.setSpacing(10);
VBox.setVgrow(channelsBox, Priority.ALWAYS);
topBox.getChildren().addAll(getSearchAndViewBox(), channelsBox);
setOrientation(Orientation.VERTICAL);
getItems().addAll(topBox, getChannelConfigurationEditor());
}
/**
* Processes the channel view request.
*
* Note: this method must be invoked on the JavaFX platform thread
*
* @param channelTabRequest to process
*/
public void process(ChannelTabRequest channelTabRequest)
{
if(channelTabRequest instanceof ViewChannelRequest)
{
Channel channel = ((ViewChannelRequest)channelTabRequest).getChannel();
if(channel != null)
{
getSearchField().setText(null);
getChannelTableView().getSelectionModel().select(channel);
getChannelTableView().scrollTo(channel);
}
}
}
private void setChannel(Channel channel)
{
//Prompt the user to save if the contents of the current channel editor have been modified
if(getChannelConfigurationEditor().modifiedProperty().get())
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.getButtonTypes().clear();
alert.getButtonTypes().addAll(ButtonType.NO, ButtonType.YES);
alert.setTitle("Save Changes");
alert.setHeaderText("Channel configuration has been modified");
alert.setContentText("Do you want to save these changes?");
alert.initOwner(((Node)getButtonBox()).getScene().getWindow());
//Workaround for JavaFX KDE on Linux bug in FX 10/11: https://bugs.openjdk.java.net/browse/JDK-8179073
alert.setResizable(true);
alert.onShownProperty().addListener(e -> {
Platform.runLater(() -> alert.setResizable(false));
});
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.YES)
{
getChannelConfigurationEditor().save();
}
}
getCloneButton().setDisable(channel == null);
getDeleteButton().setDisable(channel == null);
if(channel == null)
{
setChannelConfigurationEditor(mUnknownConfigurationEditor);
}
else
{
DecoderType channelDecoderType = null;
if(channel.getDecodeConfiguration() != null)
{
channelDecoderType = channel.getDecodeConfiguration().getDecoderType();
}
if(channelDecoderType == null)
{
setChannelConfigurationEditor(mUnknownConfigurationEditor);
}
else
{
DecoderType editorDecoderType = getChannelConfigurationEditor().getDecoderType();
if(editorDecoderType == null || editorDecoderType != channelDecoderType)
{
ChannelConfigurationEditor editor = mChannelConfigurationEditorMap.get(channelDecoderType);
if(editor == null)
{
editor = ChannelConfigurationEditorFactory.getEditor(channelDecoderType, mPlaylistManager);
if(editor != null)
{
mChannelConfigurationEditorMap.put(channelDecoderType, editor);
}
}
if(editor == null)
{
editor = mUnknownConfigurationEditor;
}
setChannelConfigurationEditor(editor);
}
}
}
getChannelConfigurationEditor().setItem(channel);
}
private void createNewChannel(DecoderType decoderType)
{
Channel channel = new Channel();
channel.setDecodeConfiguration(DecoderFactory.getDecodeConfiguration(decoderType));
mPlaylistManager.getChannelModel().addChannel(channel);
getChannelTableView().getSelectionModel().select(channel);
getChannelTableView().scrollTo(channel);
}
private SegmentedButton getViewSegmentedButton()
{
if(mViewSegmentedButton == null)
{
mViewSegmentedButton = new SegmentedButton(getAllToggleButton(), getPlayingToggleButton(),
getAutoStartToggleButton());
getAllToggleButton().setSelected(true);
mViewSegmentedButton.getToggleGroup().selectedToggleProperty()
.addListener((observable, oldValue, newValue) -> {
//Don't allow toggles to be de-selected
if(newValue == null)
{
oldValue.setSelected(true);
}
else
{
updateChannelListFilter();
}
});
}
return mViewSegmentedButton;
}
private ToggleButton getAllToggleButton()
{
if(mAllToggleButton == null)
{
mAllToggleButton = new ToggleButton("All");
}
return mAllToggleButton;
}
private ToggleButton getAutoStartToggleButton()
{
if(mAutoStartToggleButton == null)
{
mAutoStartToggleButton = new ToggleButton("Auto-Start");
}
return mAutoStartToggleButton;
}
private ToggleButton getPlayingToggleButton()
{
if(mPlayingToggleButton == null)
{
mPlayingToggleButton = new ToggleButton("Playing");
}
return mPlayingToggleButton;
}
/**
* Sets the editor to be the current channel configuration editor
*/
private void setChannelConfigurationEditor(ChannelConfigurationEditor editor)
{
if(editor != getChannelConfigurationEditor())
{
getItems().remove(getChannelConfigurationEditor());
mChannelConfigurationEditor = editor;
getItems().add(getChannelConfigurationEditor());
}
}
private ChannelConfigurationEditor getChannelConfigurationEditor()
{
if(mChannelConfigurationEditor == null)
{
mChannelConfigurationEditor = mUnknownConfigurationEditor;
mChannelConfigurationEditor.setMaxWidth(Double.MAX_VALUE);
}
return mChannelConfigurationEditor;
}
private HBox getSearchAndViewBox()
{
if(mSearchAndViewBox == null)
{
mSearchAndViewBox = new HBox();
mSearchAndViewBox.setAlignment(Pos.CENTER_LEFT);
mSearchAndViewBox.setSpacing(10);
HBox searchBox = new HBox();
searchBox.setSpacing(5);
searchBox.setAlignment(Pos.CENTER);
Label searchLabel = new Label("Search:");
searchLabel.setAlignment(Pos.CENTER_RIGHT);
searchBox.getChildren().addAll(searchLabel, getSearchField());
HBox viewBox = new HBox();
viewBox.setSpacing(5);
viewBox.setAlignment(Pos.CENTER);
Label viewLabel = new Label("View Channels:");
viewBox.getChildren().addAll(viewLabel, getViewSegmentedButton());
mSearchAndViewBox.getChildren().addAll(searchBox, viewBox);
}
return mSearchAndViewBox;
}
private TextField getSearchField()
{
if(mSearchField == null)
{
mSearchField = TextFields.createClearableTextField();
mSearchField.textProperty().addListener((observable, oldValue, newValue) -> updateChannelListFilter());
}
return mSearchField;
}
/**
* Updates the predicate on the channel list to apply search string and view settings
*/
private void updateChannelListFilter()
{
mChannelListFilter.setFilterText(getSearchField().getText());
if(getAllToggleButton().isSelected())
{
mChannelListFilter.setView(ChannelListFilter.View.ALL);
}
else if(getAutoStartToggleButton().isSelected())
{
mChannelListFilter.setView(ChannelListFilter.View.AUTO_START);
}
else if(getPlayingToggleButton().isSelected())
{
mChannelListFilter.setView(ChannelListFilter.View.PLAYING);
}
mChannelFilteredList.setPredicate(null);
mChannelFilteredList.setPredicate(mChannelListFilter);
}
private TableView<Channel> getChannelTableView()
{
if(mChannelTableView == null)
{
mChannelTableView = new TableView<>();
TableColumn<Channel,Boolean> playingColumn = new TableColumn("Playing");
playingColumn.setPrefWidth(75);
playingColumn.setCellValueFactory(new PropertyValueFactory<>("processing"));
playingColumn.setCellFactory(param -> {
TableCell<Channel,Boolean> tableCell = new TableCell<>()
{
@Override
protected void updateItem(Boolean item, boolean empty)
{
setAlignment(Pos.CENTER);
setText(null);
if(empty || item == null || !item)
{
setGraphic(null);
}
else
{
IconNode iconNode = new IconNode(FontAwesome.CHECK);
iconNode.setFill(Color.GREEN);
setGraphic(iconNode);
}
}
};
return tableCell;
});
TableColumn<Channel,Boolean> autoStartColumn = new TableColumn<>("Auto-Start");
autoStartColumn.setCellValueFactory(new PropertyValueFactory<>("autoStart"));
autoStartColumn.setPrefWidth(95);
autoStartColumn.setCellFactory(param -> {
TableCell<Channel,Boolean> tableCell = new TableCell<>()
{
@Override
protected void updateItem(Boolean item, boolean empty)
{
setAlignment(Pos.CENTER);
setText(null);
if(empty || item == null || !item)
{
setGraphic(null);
}
else
{
IconNode iconNode = new IconNode(FontAwesome.CHECK);
iconNode.setFill(Color.GREEN);
setGraphic(iconNode);
}
}
};
return tableCell;
});
TableColumn systemColumn = new TableColumn("System");
systemColumn.setCellValueFactory(new PropertyValueFactory<>("system"));
systemColumn.setPrefWidth(175);
TableColumn siteColumn = new TableColumn("Site");
siteColumn.setCellValueFactory(new PropertyValueFactory<>("site"));
siteColumn.setPrefWidth(175);
TableColumn nameColumn = new TableColumn("Name");
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
nameColumn.setPrefWidth(200);
TableColumn protocolColumn = new TableColumn("Protocol");
protocolColumn.setCellValueFactory(new ProtocolCellValueFactory());
protocolColumn.setPrefWidth(100);
mChannelTableView.getColumns().addAll(systemColumn, siteColumn, nameColumn, protocolColumn, playingColumn,
autoStartColumn);
mChannelTableView.setPlaceholder(getPlaceholderLabel());
//Sorting and filtering for the table
mChannelFilteredList = new FilteredList<>(mPlaylistManager.getChannelModel().channelList(), mChannelListFilter);
SortedList<Channel> sortedList = new SortedList<>(mChannelFilteredList);
sortedList.comparatorProperty().bind(mChannelTableView.comparatorProperty());
mChannelTableView.setItems(sortedList);
mChannelTableView.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> setChannel(newValue));
}
return mChannelTableView;
}
private Label getPlaceholderLabel()
{
if(mPlaceholderLabel == null)
{
mPlaceholderLabel = new Label("No Channel Configurations Available");
}
return mPlaceholderLabel;
}
private VBox getButtonBox()
{
if(mButtonBox == null)
{
mButtonBox = new VBox();
mButtonBox.setSpacing(10);
mButtonBox.getChildren().addAll(getNewButton(), getCloneButton(), getDeleteButton());
}
return mButtonBox;
}
private MenuButton getNewButton()
{
if(mNewButton == null)
{
mNewButton = new MenuButton("New");
mNewButton.setAlignment(Pos.CENTER);
mNewButton.setMaxWidth(Double.MAX_VALUE);
MenuItem decodersItem = new MenuItem("Decoder");
decodersItem.setDisable(true);
mNewButton.getItems().addAll(decodersItem, new SeparatorMenuItem());
for(DecoderType decoderType: DecoderType.PRIMARY_DECODERS)
{
if(decoderType == DecoderType.P25_PHASE2)
{
mNewButton.getItems().add(new NewP25P2ChannelMenu());
}
else
{
mNewButton.getItems().add(new NewChannelMenuItem(decoderType));
}
}
}
return mNewButton;
}
private Button getDeleteButton()
{
if(mDeleteButton == null)
{
mDeleteButton = new Button("Delete");
mDeleteButton.setDisable(true);
mDeleteButton.setMaxWidth(Double.MAX_VALUE);
mDeleteButton.setOnAction(event -> {
Channel selected = getChannelTableView().getSelectionModel().getSelectedItem();
if(selected != null)
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION,
"Do you want to delete the selected channel?", ButtonType.NO, ButtonType.YES);
alert.setTitle("Delete Channel");
alert.setHeaderText("Are you sure?");
alert.initOwner(((Node)getDeleteButton()).getScene().getWindow());
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.YES)
{
if(selected.isProcessing())
{
try
{
mPlaylistManager.getChannelProcessingManager().stop(selected);
}
catch(Exception e)
{
mLog.error("Couldn't stop channel [" + selected.getName() + "] prior to delete by user");
}
}
mPlaylistManager.getChannelModel().removeChannel(selected);
}
}
});
}
return mDeleteButton;
}
private Button getCloneButton()
{
if(mCloneButton == null)
{
mCloneButton = new Button("Clone");
mCloneButton.setDisable(true);
mCloneButton.setMaxWidth(Double.MAX_VALUE);
mCloneButton.setOnAction(event -> {
Channel selected = getChannelTableView().getSelectionModel().getSelectedItem();
Channel copy = selected.copyOf();
mPlaylistManager.getChannelModel().addChannel(copy);
getChannelTableView().getSelectionModel().select(copy);
});
}
return mCloneButton;
}
/**
* Menu item for creating a new channel for a specific decoder type
*/
public class NewChannelMenuItem extends MenuItem
{
private DecoderType mDecoderType;
/**
* Constructs an instance
* @param decoderType to use for the decoder configuration for the channel.
*/
public NewChannelMenuItem(DecoderType decoderType)
{
setText(decoderType.getDisplayString());
mDecoderType = decoderType;
setOnAction(event -> createNewChannel(mDecoderType));
}
}
/**
* Menu item for creating new P25 Phase 2 channel configurations
*/
public class NewP25P2ChannelMenu extends Menu
{
public NewP25P2ChannelMenu()
{
setText(DecoderType.P25_PHASE2.getDisplayString());
MenuItem trunked = new MenuItem("Trunked System");
trunked.setOnAction(event -> createNewChannel(DecoderType.P25_PHASE1));
MenuItem channel = new MenuItem("Individual Channel");
channel.setOnAction(event -> createNewChannel(DecoderType.P25_PHASE2));
getItems().addAll(trunked, channel);
}
}
public class ProtocolCellValueFactory implements Callback<TableColumn.CellDataFeatures<Channel, String>,
ObservableValue<String>>
{
private SimpleStringProperty mProtocol = new SimpleStringProperty();
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Channel, String> param)
{
Channel channel = param.getValue();
if(channel != null)
{
mProtocol.set(channel.getDecodeConfiguration().getDecoderType().getDisplayString());
}
else
{
mProtocol.set(null);
}
return mProtocol;
}
}
/**
* Filter predicate for a filterec channel list
*/
public static class ChannelListFilter implements Predicate<Channel>
{
public enum View{ALL,AUTO_START,PLAYING};
private String mFilterText;
private View mView = View.ALL;
/**
* Sets the filter text to search for
*/
public void setFilterText(String filterText)
{
if(filterText == null || filterText.trim().isEmpty())
{
mFilterText = null;
}
else
{
mFilterText = filterText.toLowerCase();
}
}
/**
* Sets the channel view
*/
public void setView(View view)
{
mView = view;
}
/**
* Tests the channel to see if it matches the current view setting and the search filter text
*/
@Override
public boolean test(Channel channel)
{
switch(mView)
{
case ALL:
default:
return matchesFilter(channel);
case AUTO_START:
return channel.isAutoStart() && matchesFilter(channel);
case PLAYING:
return channel.isProcessing() && matchesFilter(channel);
}
}
/**
* Tests the channel to see if the system, site or name values match the filter text
*/
private boolean matchesFilter(Channel channel)
{
if(mFilterText == null)
{
return true;
}
if(channel.getName() != null && channel.getName().toLowerCase().contains(mFilterText))
{
return true;
}
if(channel.getSite() != null && channel.getSite().toLowerCase().contains(mFilterText))
{
return true;
}
if(channel.getSystem() != null && channel.getSystem().toLowerCase().contains(mFilterText))
{
return true;
}
if(channel.getDecodeConfiguration().getDecoderType().toString().toLowerCase().contains(mFilterText))
{
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,31 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.channel;
import io.github.dsheirer.gui.playlist.PlaylistEditorRequest;
public abstract class ChannelTabRequest extends PlaylistEditorRequest
{
@Override
public TabName getTabName()
{
return TabName.CHANNEL;
}
}

View File

@@ -0,0 +1,414 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.channel;
import io.github.dsheirer.gui.playlist.decoder.AuxDecoderConfigurationEditor;
import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor;
import io.github.dsheirer.gui.playlist.record.RecordConfigurationEditor;
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
import io.github.dsheirer.gui.playlist.source.SourceConfigurationEditor;
import io.github.dsheirer.message.MessageDirection;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.ltrstandard.DecodeConfigLTRStandard;
import io.github.dsheirer.module.log.EventLogType;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.source.config.SourceConfiguration;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import org.controlsfx.control.SegmentedButton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* LTR (standard) channel configuration editor
*/
public class LTRConfigurationEditor extends ChannelConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger(LTRConfigurationEditor.class);
private TitledPane mAuxDecoderPane;
private TitledPane mDecoderPane;
private TitledPane mEventLogPane;
private TitledPane mRecordPane;
private TitledPane mSourcePane;
private SegmentedButton mDirectionButton;
private SourceConfigurationEditor mSourceConfigurationEditor;
private AuxDecoderConfigurationEditor mAuxDecoderConfigurationEditor;
private EventLogConfigurationEditor mEventLogConfigurationEditor;
private RecordConfigurationEditor mRecordConfigurationEditor;
/**
* Constructs an instance
* @param playlistManager
*/
public LTRConfigurationEditor(PlaylistManager playlistManager)
{
super(playlistManager);
getTitledPanesBox().getChildren().add(getSourcePane());
getTitledPanesBox().getChildren().add(getDecoderPane());
getTitledPanesBox().getChildren().add(getAuxDecoderPane());
getTitledPanesBox().getChildren().add(getEventLogPane());
getTitledPanesBox().getChildren().add(getRecordPane());
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.LTR;
}
private TitledPane getSourcePane()
{
if(mSourcePane == null)
{
mSourcePane = new TitledPane("Source", getSourceConfigurationEditor());
mSourcePane.setExpanded(true);
}
return mSourcePane;
}
private TitledPane getDecoderPane()
{
if(mDecoderPane == null)
{
mDecoderPane = new TitledPane();
mDecoderPane.setText("Decoder: LTR");
mDecoderPane.setExpanded(false);
mDecoderPane.expandedProperty().addListener((observable, oldValue, newValue) -> {
//There's a bug either in the toggle group or the segmented button where setting the toggle to selected
//when the control is hidden in a titled pane prevents the selection, so we have to select the toggle
//again when the pane is expanded. This state is detected by a null selected toggle.
if(newValue)
{
if(getDirectionButton().getToggleGroup().getSelectedToggle() == null)
{
if(getItem().getDecodeConfiguration() instanceof DecodeConfigLTRStandard)
{
DecodeConfigLTRStandard decodeConfig = (DecodeConfigLTRStandard)getItem().getDecodeConfiguration();
MessageDirection direction = decodeConfig.getMessageDirection();
if(direction == null)
{
direction = MessageDirection.OSW;
}
//This will cause the modified property to trigger, so capture the modified state before
//and re-apply after
boolean modified = modifiedProperty().get();
for(Toggle toggle: getDirectionButton().getToggleGroup().getToggles())
{
toggle.setSelected(toggle.getUserData() == direction);
}
modifiedProperty().set(modified);
}
}
}
});
GridPane gridPane = new GridPane();
gridPane.setPadding(new Insets(10,10,10,10));
gridPane.setHgap(10);
Label directionLabel = new Label("Direction");
GridPane.setHalignment(directionLabel, HPos.LEFT);
GridPane.setConstraints(directionLabel, 0, 0);
gridPane.getChildren().add(directionLabel);
GridPane.setConstraints(getDirectionButton(), 1, 0);
gridPane.getChildren().add(getDirectionButton());
Label instructions = new Label("OSW: repeater output signaling (default). ISW: repeater input signaling");
GridPane.setConstraints(instructions, 2, 0);
gridPane.getChildren().addAll(instructions);
mDecoderPane.setContent(gridPane);
}
return mDecoderPane;
}
private TitledPane getEventLogPane()
{
if(mEventLogPane == null)
{
mEventLogPane = new TitledPane("Logging", getEventLogConfigurationEditor());
mEventLogPane.setExpanded(false);
}
return mEventLogPane;
}
private TitledPane getAuxDecoderPane()
{
if(mAuxDecoderPane == null)
{
mAuxDecoderPane = new TitledPane("Additional Decoders", getAuxDecoderConfigurationEditor());
mAuxDecoderPane.setExpanded(false);
}
return mAuxDecoderPane;
}
private TitledPane getRecordPane()
{
if(mRecordPane == null)
{
mRecordPane = new TitledPane();
mRecordPane.setText("Recording");
mRecordPane.setExpanded(false);
Label notice = new Label("Note: use aliases to control call audio recording");
notice.setPadding(new Insets(10, 10, 0, 10));
VBox vBox = new VBox();
vBox.getChildren().addAll(getRecordConfigurationEditor(), notice);
mRecordPane.setContent(vBox);
}
return mRecordPane;
}
private SourceConfigurationEditor getSourceConfigurationEditor()
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mSourceConfigurationEditor;
}
private EventLogConfigurationEditor getEventLogConfigurationEditor()
{
if(mEventLogConfigurationEditor == null)
{
List<EventLogType> types = new ArrayList<>();
types.add(EventLogType.CALL_EVENT);
types.add(EventLogType.DECODED_MESSAGE);
mEventLogConfigurationEditor = new EventLogConfigurationEditor(types);
mEventLogConfigurationEditor.setPadding(new Insets(5,5,5,5));
mEventLogConfigurationEditor.modifiedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mEventLogConfigurationEditor;
}
private AuxDecoderConfigurationEditor getAuxDecoderConfigurationEditor()
{
if(mAuxDecoderConfigurationEditor == null)
{
List<DecoderType> types = new ArrayList<>();
types.add(DecoderType.FLEETSYNC2);
types.add(DecoderType.MDC1200);
mAuxDecoderConfigurationEditor = new AuxDecoderConfigurationEditor(types);
mAuxDecoderConfigurationEditor.setPadding(new Insets(5,5,5,5));
mAuxDecoderConfigurationEditor.modifiedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mAuxDecoderConfigurationEditor;
}
private SegmentedButton getDirectionButton()
{
if(mDirectionButton == null)
{
mDirectionButton = new SegmentedButton();
mDirectionButton.setDisable(true);
for(MessageDirection messageDirection: MessageDirection.ORDERED_VALUES)
{
ToggleButton toggleButton = new ToggleButton(messageDirection.name());
toggleButton.setTooltip(new Tooltip(messageDirection.toString()));
toggleButton.setUserData(messageDirection);
mDirectionButton.getButtons().add(toggleButton);
}
mDirectionButton.getToggleGroup().selectedToggleProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mDirectionButton;
}
private RecordConfigurationEditor getRecordConfigurationEditor()
{
if(mRecordConfigurationEditor == null)
{
List<RecorderType> types = new ArrayList<>();
types.add(RecorderType.BASEBAND);
mRecordConfigurationEditor = new RecordConfigurationEditor(types);
mRecordConfigurationEditor.setDisable(true);
mRecordConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mRecordConfigurationEditor;
}
@Override
protected void setDecoderConfiguration(DecodeConfiguration config)
{
if(config instanceof DecodeConfigLTRStandard)
{
getDirectionButton().setDisable(false);
DecodeConfigLTRStandard decodeConfig = (DecodeConfigLTRStandard)config;
MessageDirection direction = decodeConfig.getMessageDirection();
if(direction == null)
{
direction = MessageDirection.OSW;
}
for(Toggle toggle: getDirectionButton().getToggleGroup().getToggles())
{
toggle.setSelected(toggle.getUserData() == direction);
}
}
else
{
getDirectionButton().setDisable(true);
for(Toggle toggle: getDirectionButton().getToggleGroup().getToggles())
{
toggle.setSelected(false);
}
}
}
@Override
protected void saveDecoderConfiguration()
{
DecodeConfigLTRStandard config;
if(getItem().getDecodeConfiguration() instanceof DecodeConfigLTRStandard)
{
config = (DecodeConfigLTRStandard)getItem().getDecodeConfiguration();
}
else
{
config = new DecodeConfigLTRStandard();
}
MessageDirection messageDirection = MessageDirection.OSW;
if(getDirectionButton().getToggleGroup().getSelectedToggle() != null)
{
messageDirection = (MessageDirection)getDirectionButton().getToggleGroup().getSelectedToggle().getUserData();
}
config.setMessageDirection(messageDirection);
getItem().setDecodeConfiguration(config);
}
@Override
protected void setEventLogConfiguration(EventLogConfiguration config)
{
getEventLogConfigurationEditor().setItem(config);
}
@Override
protected void saveEventLogConfiguration()
{
getEventLogConfigurationEditor().save();
if(getEventLogConfigurationEditor().getItem().getLoggers().isEmpty())
{
getItem().setEventLogConfiguration(null);
}
else
{
getItem().setEventLogConfiguration(getEventLogConfigurationEditor().getItem());
}
}
@Override
protected void setAuxDecoderConfiguration(AuxDecodeConfiguration config)
{
getAuxDecoderConfigurationEditor().setItem(config);
}
@Override
protected void saveAuxDecoderConfiguration()
{
getAuxDecoderConfigurationEditor().save();
if(getAuxDecoderConfigurationEditor().getItem().getAuxDecoders().isEmpty())
{
getItem().setAuxDecodeConfiguration(null);
}
else
{
getItem().setAuxDecodeConfiguration(getAuxDecoderConfigurationEditor().getItem());
}
}
@Override
protected void setRecordConfiguration(RecordConfiguration config)
{
getRecordConfigurationEditor().setDisable(config == null);
getRecordConfigurationEditor().setItem(config);
}
@Override
protected void saveRecordConfiguration()
{
getRecordConfigurationEditor().save();
RecordConfiguration config = getRecordConfigurationEditor().getItem();
getItem().setRecordConfiguration(config);
}
@Override
protected void setSourceConfiguration(SourceConfiguration config)
{
getSourceConfigurationEditor().setSourceConfiguration(config);
}
@Override
protected void saveSourceConfiguration()
{
getSourceConfigurationEditor().save();
SourceConfiguration sourceConfiguration = getSourceConfigurationEditor().getSourceConfiguration();
getItem().setSourceConfiguration(sourceConfiguration);
}
}

View File

@@ -0,0 +1,415 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.channel;
import io.github.dsheirer.gui.playlist.decoder.AuxDecoderConfigurationEditor;
import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor;
import io.github.dsheirer.gui.playlist.record.RecordConfigurationEditor;
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
import io.github.dsheirer.gui.playlist.source.SourceConfigurationEditor;
import io.github.dsheirer.message.MessageDirection;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.ltrnet.DecodeConfigLTRNet;
import io.github.dsheirer.module.log.EventLogType;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.source.config.SourceConfiguration;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import org.controlsfx.control.SegmentedButton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* LTR-Net channel configuration editor
*/
public class LTRNetConfigurationEditor extends ChannelConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger(LTRNetConfigurationEditor.class);
private TitledPane mAuxDecoderPane;
private TitledPane mDecoderPane;
private TitledPane mEventLogPane;
private TitledPane mRecordPane;
private TitledPane mSourcePane;
private SegmentedButton mDirectionButton;
private SourceConfigurationEditor mSourceConfigurationEditor;
private AuxDecoderConfigurationEditor mAuxDecoderConfigurationEditor;
private EventLogConfigurationEditor mEventLogConfigurationEditor;
private RecordConfigurationEditor mRecordConfigurationEditor;
/**
* Constructs an instance
* @param playlistManager
*/
public LTRNetConfigurationEditor(PlaylistManager playlistManager)
{
super(playlistManager);
getTitledPanesBox().getChildren().add(getSourcePane());
getTitledPanesBox().getChildren().add(getDecoderPane());
getTitledPanesBox().getChildren().add(getAuxDecoderPane());
getTitledPanesBox().getChildren().add(getEventLogPane());
getTitledPanesBox().getChildren().add(getRecordPane());
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.LTR_NET;
}
private TitledPane getSourcePane()
{
if(mSourcePane == null)
{
mSourcePane = new TitledPane("Source", getSourceConfigurationEditor());
mSourcePane.setExpanded(true);
}
return mSourcePane;
}
private TitledPane getDecoderPane()
{
if(mDecoderPane == null)
{
mDecoderPane = new TitledPane();
mDecoderPane.setText("Decoder: LTR-Net");
mDecoderPane.setExpanded(false);
mDecoderPane.expandedProperty().addListener((observable, oldValue, newValue) -> {
//There's a bug either in the toggle group or the segmented button where setting the toggle to selected
//when the control is hidden in a titled pane prevents the selection, so we have to select the toggle
//again when the pane is expanded. This state is detected by a null selected toggle.
if(newValue)
{
if(getDirectionButton().getToggleGroup().getSelectedToggle() == null)
{
if(getItem().getDecodeConfiguration() instanceof DecodeConfigLTRNet)
{
DecodeConfigLTRNet decodeConfig = (DecodeConfigLTRNet)getItem().getDecodeConfiguration();
MessageDirection direction = decodeConfig.getMessageDirection();
if(direction == null)
{
direction = MessageDirection.OSW;
}
//This will cause the modified property to trigger, so capture the modified state before
//and re-apply after
boolean modified = modifiedProperty().get();
for(Toggle toggle: getDirectionButton().getToggleGroup().getToggles())
{
toggle.setSelected(toggle.getUserData() == direction);
}
modifiedProperty().set(modified);
}
}
}
});
GridPane gridPane = new GridPane();
gridPane.setPadding(new Insets(10,10,10,10));
gridPane.setHgap(10);
Label directionLabel = new Label("Direction");
GridPane.setHalignment(directionLabel, HPos.LEFT);
GridPane.setConstraints(directionLabel, 0, 0);
gridPane.getChildren().add(directionLabel);
GridPane.setConstraints(getDirectionButton(), 1, 0);
gridPane.getChildren().add(getDirectionButton());
Label instructions = new Label("OSW: repeater output signaling (default). ISW: repeater input signaling");
GridPane.setConstraints(instructions, 2, 0);
gridPane.getChildren().addAll(instructions);
mDecoderPane.setContent(gridPane);
}
return mDecoderPane;
}
private TitledPane getEventLogPane()
{
if(mEventLogPane == null)
{
mEventLogPane = new TitledPane("Logging", getEventLogConfigurationEditor());
mEventLogPane.setExpanded(false);
}
return mEventLogPane;
}
private TitledPane getAuxDecoderPane()
{
if(mAuxDecoderPane == null)
{
mAuxDecoderPane = new TitledPane("Additional Decoders", getAuxDecoderConfigurationEditor());
mAuxDecoderPane.setExpanded(false);
}
return mAuxDecoderPane;
}
private TitledPane getRecordPane()
{
if(mRecordPane == null)
{
mRecordPane = new TitledPane();
mRecordPane.setText("Recording");
mRecordPane.setExpanded(false);
Label notice = new Label("Note: use aliases to control call audio recording");
notice.setPadding(new Insets(10, 10, 0, 10));
VBox vBox = new VBox();
vBox.getChildren().addAll(getRecordConfigurationEditor(), notice);
mRecordPane.setContent(vBox);
}
return mRecordPane;
}
private SourceConfigurationEditor getSourceConfigurationEditor()
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mSourceConfigurationEditor;
}
private EventLogConfigurationEditor getEventLogConfigurationEditor()
{
if(mEventLogConfigurationEditor == null)
{
List<EventLogType> types = new ArrayList<>();
types.add(EventLogType.CALL_EVENT);
types.add(EventLogType.DECODED_MESSAGE);
mEventLogConfigurationEditor = new EventLogConfigurationEditor(types);
mEventLogConfigurationEditor.setPadding(new Insets(5,5,5,5));
mEventLogConfigurationEditor.modifiedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mEventLogConfigurationEditor;
}
private AuxDecoderConfigurationEditor getAuxDecoderConfigurationEditor()
{
if(mAuxDecoderConfigurationEditor == null)
{
List<DecoderType> types = new ArrayList<>();
types.add(DecoderType.FLEETSYNC2);
types.add(DecoderType.MDC1200);
mAuxDecoderConfigurationEditor = new AuxDecoderConfigurationEditor(types);
mAuxDecoderConfigurationEditor.setPadding(new Insets(5,5,5,5));
mAuxDecoderConfigurationEditor.modifiedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mAuxDecoderConfigurationEditor;
}
private SegmentedButton getDirectionButton()
{
if(mDirectionButton == null)
{
mDirectionButton = new SegmentedButton();
mDirectionButton.setDisable(true);
for(MessageDirection messageDirection: MessageDirection.ORDERED_VALUES)
{
ToggleButton toggleButton = new ToggleButton(messageDirection.name());
toggleButton.setTooltip(new Tooltip(messageDirection.toString()));
toggleButton.setUserData(messageDirection);
mDirectionButton.getButtons().add(toggleButton);
}
mDirectionButton.getToggleGroup().selectedToggleProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mDirectionButton;
}
private RecordConfigurationEditor getRecordConfigurationEditor()
{
if(mRecordConfigurationEditor == null)
{
List<RecorderType> types = new ArrayList<>();
types.add(RecorderType.BASEBAND);
mRecordConfigurationEditor = new RecordConfigurationEditor(types);
mRecordConfigurationEditor.setDisable(true);
mRecordConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mRecordConfigurationEditor;
}
@Override
protected void setDecoderConfiguration(DecodeConfiguration config)
{
if(config instanceof DecodeConfigLTRNet)
{
getDirectionButton().setDisable(false);
DecodeConfigLTRNet decodeConfig = (DecodeConfigLTRNet)config;
MessageDirection direction = decodeConfig.getMessageDirection();
if(direction == null)
{
direction = MessageDirection.OSW;
decodeConfig.setMessageDirection(direction);
}
for(Toggle toggle: getDirectionButton().getToggleGroup().getToggles())
{
toggle.setSelected(toggle.getUserData() == direction);
}
}
else
{
getDirectionButton().setDisable(true);
for(Toggle toggle: getDirectionButton().getToggleGroup().getToggles())
{
toggle.setSelected(false);
}
}
}
@Override
protected void saveDecoderConfiguration()
{
DecodeConfigLTRNet config;
if(getItem().getDecodeConfiguration() instanceof DecodeConfigLTRNet)
{
config = (DecodeConfigLTRNet)getItem().getDecodeConfiguration();
}
else
{
config = new DecodeConfigLTRNet();
}
MessageDirection messageDirection = MessageDirection.OSW;
if(getDirectionButton().getToggleGroup().getSelectedToggle() != null)
{
messageDirection = (MessageDirection)getDirectionButton().getToggleGroup().getSelectedToggle().getUserData();
}
config.setMessageDirection(messageDirection);
getItem().setDecodeConfiguration(config);
}
@Override
protected void setEventLogConfiguration(EventLogConfiguration config)
{
getEventLogConfigurationEditor().setItem(config);
}
@Override
protected void saveEventLogConfiguration()
{
getEventLogConfigurationEditor().save();
if(getEventLogConfigurationEditor().getItem().getLoggers().isEmpty())
{
getItem().setEventLogConfiguration(null);
}
else
{
getItem().setEventLogConfiguration(getEventLogConfigurationEditor().getItem());
}
}
@Override
protected void setAuxDecoderConfiguration(AuxDecodeConfiguration config)
{
getAuxDecoderConfigurationEditor().setItem(config);
}
@Override
protected void saveAuxDecoderConfiguration()
{
getAuxDecoderConfigurationEditor().save();
if(getAuxDecoderConfigurationEditor().getItem().getAuxDecoders().isEmpty())
{
getItem().setAuxDecodeConfiguration(null);
}
else
{
getItem().setAuxDecodeConfiguration(getAuxDecoderConfigurationEditor().getItem());
}
}
@Override
protected void setRecordConfiguration(RecordConfiguration config)
{
getRecordConfigurationEditor().setDisable(config == null);
getRecordConfigurationEditor().setItem(config);
}
@Override
protected void saveRecordConfiguration()
{
getRecordConfigurationEditor().save();
RecordConfiguration config = getRecordConfigurationEditor().getItem();
getItem().setRecordConfiguration(config);
}
@Override
protected void setSourceConfiguration(SourceConfiguration config)
{
getSourceConfigurationEditor().setSourceConfiguration(config);
}
@Override
protected void saveSourceConfiguration()
{
getSourceConfigurationEditor().save();
SourceConfiguration sourceConfiguration = getSourceConfigurationEditor().getSourceConfiguration();
getItem().setSourceConfiguration(sourceConfiguration);
}
}

View File

@@ -0,0 +1,448 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.channel;
import io.github.dsheirer.controller.channel.map.ChannelMap;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.playlist.channelMap.ChannelMapEditorViewRequest;
import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor;
import io.github.dsheirer.gui.playlist.record.RecordConfigurationEditor;
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
import io.github.dsheirer.gui.playlist.source.SourceConfigurationEditor;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.mpt1327.DecodeConfigMPT1327;
import io.github.dsheirer.module.log.EventLogType;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.source.config.SourceConfiguration;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* MPT-1327 channel configuration editor
*/
public class MPT1327ConfigurationEditor extends ChannelConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger(MPT1327ConfigurationEditor.class);
private TitledPane mDecoderPane;
private TitledPane mEventLogPane;
private TitledPane mRecordPane;
private TitledPane mSourcePane;
private SourceConfigurationEditor mSourceConfigurationEditor;
private EventLogConfigurationEditor mEventLogConfigurationEditor;
private RecordConfigurationEditor mRecordConfigurationEditor;
private ComboBox<ChannelMap> mChannelMapComboBox;
private Button mChannelMapEditButton;
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
private Spinner<Integer> mCallTimeoutSpinner;
/**
* Constructs an instance
* @param playlistManager
*/
public MPT1327ConfigurationEditor(PlaylistManager playlistManager)
{
super(playlistManager);
getTitledPanesBox().getChildren().add(getSourcePane());
getTitledPanesBox().getChildren().add(getDecoderPane());
getTitledPanesBox().getChildren().add(getEventLogPane());
getTitledPanesBox().getChildren().add(getRecordPane());
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.MPT1327;
}
private TitledPane getSourcePane()
{
if(mSourcePane == null)
{
mSourcePane = new TitledPane("Source", getSourceConfigurationEditor());
mSourcePane.setExpanded(true);
}
return mSourcePane;
}
private TitledPane getDecoderPane()
{
if(mDecoderPane == null)
{
mDecoderPane = new TitledPane();
mDecoderPane.setText("Decoder: MPT-1327");
mDecoderPane.setExpanded(true);
GridPane gridPane = new GridPane();
gridPane.setPadding(new Insets(10,10,10,10));
gridPane.setHgap(10);
gridPane.setVgap(10);
Label channelMapLabel = new Label("Channel Map");
GridPane.setHalignment(channelMapLabel, HPos.RIGHT);
GridPane.setConstraints(channelMapLabel, 0, 0);
gridPane.getChildren().add(channelMapLabel);
GridPane.setConstraints(getChannelMapComboBox(), 1, 0, 2, 1,
HPos.LEFT, VPos.CENTER, Priority.ALWAYS, Priority.SOMETIMES);
gridPane.getChildren().add(getChannelMapComboBox());
GridPane.setConstraints(getChannelMapEditButton(), 3, 0);
gridPane.getChildren().add(getChannelMapEditButton());
Label poolSizeLabel = new Label("Max Traffic Channels");
GridPane.setHalignment(poolSizeLabel, HPos.RIGHT);
GridPane.setConstraints(poolSizeLabel, 0, 1);
gridPane.getChildren().add(poolSizeLabel);
GridPane.setConstraints(getTrafficChannelPoolSizeSpinner(), 1, 1);
gridPane.getChildren().add(getTrafficChannelPoolSizeSpinner());
Label callTimeoutLabel = new Label("Call Timeout Seconds");
GridPane.setHalignment(callTimeoutLabel, HPos.RIGHT);
GridPane.setConstraints(callTimeoutLabel, 2, 1);
gridPane.getChildren().add(callTimeoutLabel);
GridPane.setConstraints(getCallTimeoutSpinner(), 3, 1);
gridPane.getChildren().add(getCallTimeoutSpinner());
mDecoderPane.setContent(gridPane);
}
return mDecoderPane;
}
private TitledPane getEventLogPane()
{
if(mEventLogPane == null)
{
mEventLogPane = new TitledPane("Logging", getEventLogConfigurationEditor());
mEventLogPane.setExpanded(false);
}
return mEventLogPane;
}
private TitledPane getRecordPane()
{
if(mRecordPane == null)
{
mRecordPane = new TitledPane();
mRecordPane.setText("Recording");
mRecordPane.setExpanded(false);
Label notice = new Label("Note: use aliases to control call audio recording");
notice.setPadding(new Insets(10, 10, 0, 10));
VBox vBox = new VBox();
vBox.getChildren().addAll(getRecordConfigurationEditor(), notice);
mRecordPane.setContent(vBox);
}
return mRecordPane;
}
private SourceConfigurationEditor getSourceConfigurationEditor()
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), true);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mSourceConfigurationEditor;
}
private EventLogConfigurationEditor getEventLogConfigurationEditor()
{
if(mEventLogConfigurationEditor == null)
{
List<EventLogType> types = new ArrayList<>();
types.add(EventLogType.CALL_EVENT);
types.add(EventLogType.DECODED_MESSAGE);
types.add(EventLogType.TRAFFIC_CALL_EVENT);
types.add(EventLogType.TRAFFIC_DECODED_MESSAGE);
mEventLogConfigurationEditor = new EventLogConfigurationEditor(types);
mEventLogConfigurationEditor.setPadding(new Insets(5,5,5,5));
mEventLogConfigurationEditor.modifiedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mEventLogConfigurationEditor;
}
private RecordConfigurationEditor getRecordConfigurationEditor()
{
if(mRecordConfigurationEditor == null)
{
List<RecorderType> types = new ArrayList<>();
types.add(RecorderType.BASEBAND);
types.add(RecorderType.DEMODULATED_BIT_STREAM);
types.add(RecorderType.TRAFFIC_BASEBAND);
types.add(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM);
mRecordConfigurationEditor = new RecordConfigurationEditor(types);
mRecordConfigurationEditor.setDisable(true);
mRecordConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mRecordConfigurationEditor;
}
private ComboBox<ChannelMap> getChannelMapComboBox()
{
if(mChannelMapComboBox == null)
{
mChannelMapComboBox = new ComboBox<>();
mChannelMapComboBox.setMaxWidth(Double.MAX_VALUE);
mChannelMapComboBox.setDisable(true);
mChannelMapComboBox.setTooltip(new Tooltip("Select a channel map to use for this system"));
mChannelMapComboBox.getItems().addAll(getPlaylistManager().getChannelMapModel().getChannelMaps());
mChannelMapComboBox.setDisable(true);
mChannelMapComboBox.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mChannelMapComboBox;
}
private Button getChannelMapEditButton()
{
if(mChannelMapEditButton == null)
{
mChannelMapEditButton = new Button("Channel Map Editor");
mChannelMapEditButton.setTooltip(new Tooltip("Open the channel map editor to add/edit maps"));
mChannelMapEditButton.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
String channelMapName = null;
if(getChannelMapComboBox().getSelectionModel().getSelectedItem() != null)
{
channelMapName = getChannelMapComboBox().getSelectionModel().getSelectedItem().getName();
}
MyEventBus.getEventBus().post(new ChannelMapEditorViewRequest(channelMapName));
}
});
}
return mChannelMapEditButton;
}
private Spinner<Integer> getTrafficChannelPoolSizeSpinner()
{
if(mTrafficChannelPoolSizeSpinner == null)
{
mTrafficChannelPoolSizeSpinner = new Spinner();
mTrafficChannelPoolSizeSpinner.setDisable(true);
mTrafficChannelPoolSizeSpinner.setTooltip(
new Tooltip("Maximum number of traffic channels that can be created by the decoder"));
mTrafficChannelPoolSizeSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 50);
mTrafficChannelPoolSizeSpinner.setValueFactory(svf);
mTrafficChannelPoolSizeSpinner.getValueFactory().valueProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mTrafficChannelPoolSizeSpinner;
}
private Spinner<Integer> getCallTimeoutSpinner()
{
if(mCallTimeoutSpinner == null)
{
mCallTimeoutSpinner = new Spinner();
mCallTimeoutSpinner.setDisable(true);
mCallTimeoutSpinner.setTooltip(new Tooltip("Maximum call limit in seconds"));
mCallTimeoutSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(1, 120);
mCallTimeoutSpinner.setValueFactory(svf);
mCallTimeoutSpinner.getValueFactory().valueProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mCallTimeoutSpinner;
}
@Override
protected void setDecoderConfiguration(DecodeConfiguration config)
{
getChannelMapComboBox().setDisable(config == null);
getCallTimeoutSpinner().setDisable(config == null);
getTrafficChannelPoolSizeSpinner().setDisable(config == null);
getChannelMapComboBox().getSelectionModel().select(null);
if(config instanceof DecodeConfigMPT1327)
{
DecodeConfigMPT1327 decodeConfigMPT1327 = (DecodeConfigMPT1327)config;
String channelMapName = decodeConfigMPT1327.getChannelMapName();
if(channelMapName != null)
{
for(ChannelMap channelMap: getChannelMapComboBox().getItems())
{
if(channelMap.getName().contentEquals(channelMapName))
{
getChannelMapComboBox().getSelectionModel().select(channelMap);
}
}
}
int callTimeout = decodeConfigMPT1327.getCallTimeout();
getCallTimeoutSpinner().getValueFactory().setValue(callTimeout);
int channelPoolSize = decodeConfigMPT1327.getTrafficChannelPoolSize();
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(channelPoolSize);
}
else if(config != null)
{
getCallTimeoutSpinner().getValueFactory().setValue(DecodeConfigMPT1327.DEFAULT_CALL_TIMEOUT_DELAY_SECONDS);
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(DecodeConfigMPT1327.TRAFFIC_CHANNEL_LIMIT_DEFAULT);
}
}
@Override
protected void saveDecoderConfiguration()
{
DecodeConfigMPT1327 config;
if(getItem().getDecodeConfiguration() instanceof DecodeConfigMPT1327)
{
config = (DecodeConfigMPT1327)getItem().getDecodeConfiguration();
}
else
{
config = new DecodeConfigMPT1327();
}
config.setCallTimeout(getCallTimeoutSpinner().getValue());
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
ChannelMap selected = getChannelMapComboBox().getSelectionModel().getSelectedItem();
if(selected != null)
{
config.setChannelMapName(selected.getName());
}
else
{
config.setChannelMapName(null);
}
getItem().setDecodeConfiguration(config);
}
@Override
protected void setEventLogConfiguration(EventLogConfiguration config)
{
getEventLogConfigurationEditor().setItem(config);
}
@Override
protected void saveEventLogConfiguration()
{
getEventLogConfigurationEditor().save();
if(getEventLogConfigurationEditor().getItem().getLoggers().isEmpty())
{
getItem().setEventLogConfiguration(null);
}
else
{
getItem().setEventLogConfiguration(getEventLogConfigurationEditor().getItem());
}
}
@Override
protected void setAuxDecoderConfiguration(AuxDecodeConfiguration config)
{
//no-op
}
@Override
protected void saveAuxDecoderConfiguration()
{
//no-op
}
@Override
protected void setRecordConfiguration(RecordConfiguration config)
{
getRecordConfigurationEditor().setDisable(config == null);
getRecordConfigurationEditor().setItem(config);
}
@Override
protected void saveRecordConfiguration()
{
getRecordConfigurationEditor().save();
RecordConfiguration config = getRecordConfigurationEditor().getItem();
getItem().setRecordConfiguration(config);
}
@Override
protected void setSourceConfiguration(SourceConfiguration config)
{
getSourceConfigurationEditor().setSourceConfiguration(config);
}
@Override
protected void saveSourceConfiguration()
{
getSourceConfigurationEditor().save();
SourceConfiguration sourceConfiguration = getSourceConfigurationEditor().getSourceConfiguration();
getItem().setSourceConfiguration(sourceConfiguration);
}
}

View File

@@ -0,0 +1,434 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.channel;
import io.github.dsheirer.gui.playlist.decoder.AuxDecoderConfigurationEditor;
import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor;
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
import io.github.dsheirer.gui.playlist.source.SourceConfigurationEditor;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
import io.github.dsheirer.module.log.EventLogType;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.source.config.SourceConfiguration;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.GridPane;
import javafx.scene.text.TextAlignment;
import org.controlsfx.control.SegmentedButton;
import org.controlsfx.control.ToggleSwitch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Narrow-Band FM channel configuration editor
*/
public class NBFMConfigurationEditor extends ChannelConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger(NBFMConfigurationEditor.class);
private TitledPane mAuxDecoderPane;
private TitledPane mDecoderPane;
private TitledPane mEventLogPane;
private TitledPane mRecordPane;
private TitledPane mSourcePane;
private ToggleSwitch mAudioRecordSwitch;
private ToggleSwitch mBasebandRecordSwitch;
private SegmentedButton mBandwidthButton;
private SourceConfigurationEditor mSourceConfigurationEditor;
private AuxDecoderConfigurationEditor mAuxDecoderConfigurationEditor;
private EventLogConfigurationEditor mEventLogConfigurationEditor;
/**
* Constructs an instance
* @param playlistManager
*/
public NBFMConfigurationEditor(PlaylistManager playlistManager)
{
super(playlistManager);
getTitledPanesBox().getChildren().add(getSourcePane());
getTitledPanesBox().getChildren().add(getDecoderPane());
getTitledPanesBox().getChildren().add(getAuxDecoderPane());
getTitledPanesBox().getChildren().add(getEventLogPane());
getTitledPanesBox().getChildren().add(getRecordPane());
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.NBFM;
}
private TitledPane getSourcePane()
{
if(mSourcePane == null)
{
mSourcePane = new TitledPane("Source", getSourceConfigurationEditor());
mSourcePane.setExpanded(true);
}
return mSourcePane;
}
private TitledPane getDecoderPane()
{
if(mDecoderPane == null)
{
mDecoderPane = new TitledPane();
mDecoderPane.setText("Decoder: NBFM");
mDecoderPane.setExpanded(false);
GridPane gridPane = new GridPane();
gridPane.setPadding(new Insets(10,10,10,10));
gridPane.setHgap(10);
Label bandwidthLabel = new Label("Channel Bandwidth");
GridPane.setHalignment(bandwidthLabel, HPos.LEFT);
GridPane.setConstraints(bandwidthLabel, 0, 0);
gridPane.getChildren().add(bandwidthLabel);
GridPane.setConstraints(getBandwidthButton(), 1, 0);
gridPane.getChildren().add(getBandwidthButton());
mDecoderPane.setContent(gridPane);
//Special handling - the pill button doesn't like to set a selected state if the pane is not expanded,
//so detect when the pane is expanded and refresh the config view
mDecoderPane.expandedProperty().addListener((observable, oldValue, newValue) -> {
if(newValue)
{
//Reset the config so the editor gets updated
setDecoderConfiguration(getItem().getDecodeConfiguration());
}
});
}
return mDecoderPane;
}
private TitledPane getEventLogPane()
{
if(mEventLogPane == null)
{
mEventLogPane = new TitledPane("Logging", getEventLogConfigurationEditor());
mEventLogPane.setExpanded(false);
}
return mEventLogPane;
}
private TitledPane getAuxDecoderPane()
{
if(mAuxDecoderPane == null)
{
mAuxDecoderPane = new TitledPane("Additional Decoders", getAuxDecoderConfigurationEditor());
mAuxDecoderPane.setExpanded(false);
}
return mAuxDecoderPane;
}
private TitledPane getRecordPane()
{
if(mRecordPane == null)
{
mRecordPane = new TitledPane();
mRecordPane.setText("Recording");
mRecordPane.setExpanded(false);
GridPane gridPane = new GridPane();
gridPane.setPadding(new Insets(10,10,10,10));
gridPane.setHgap(10);
gridPane.setVgap(10);
GridPane.setConstraints(getAudioRecordSwitch(), 0, 0);
gridPane.getChildren().add(getAudioRecordSwitch());
Label recordAudioLabel = new Label("Audio");
GridPane.setHalignment(recordAudioLabel, HPos.LEFT);
GridPane.setConstraints(recordAudioLabel, 1, 0);
gridPane.getChildren().add(recordAudioLabel);
GridPane.setConstraints(getBasebandRecordSwitch(), 0, 1);
gridPane.getChildren().add(getBasebandRecordSwitch());
Label recordBasebandLabel = new Label("Channel (Baseband I&Q)");
GridPane.setHalignment(recordBasebandLabel, HPos.LEFT);
GridPane.setConstraints(recordBasebandLabel, 1, 1);
gridPane.getChildren().add(recordBasebandLabel);
mRecordPane.setContent(gridPane);
}
return mRecordPane;
}
private SourceConfigurationEditor getSourceConfigurationEditor()
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mSourceConfigurationEditor;
}
private EventLogConfigurationEditor getEventLogConfigurationEditor()
{
if(mEventLogConfigurationEditor == null)
{
List<EventLogType> types = new ArrayList<>();
types.add(EventLogType.CALL_EVENT);
types.add(EventLogType.DECODED_MESSAGE);
mEventLogConfigurationEditor = new EventLogConfigurationEditor(types);
mEventLogConfigurationEditor.setPadding(new Insets(5,5,5,5));
mEventLogConfigurationEditor.modifiedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mEventLogConfigurationEditor;
}
private AuxDecoderConfigurationEditor getAuxDecoderConfigurationEditor()
{
if(mAuxDecoderConfigurationEditor == null)
{
mAuxDecoderConfigurationEditor = new AuxDecoderConfigurationEditor(DecoderType.AUX_DECODERS);
mAuxDecoderConfigurationEditor.setPadding(new Insets(5,5,5,5));
mAuxDecoderConfigurationEditor.modifiedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mAuxDecoderConfigurationEditor;
}
private SegmentedButton getBandwidthButton()
{
if(mBandwidthButton == null)
{
mBandwidthButton = new SegmentedButton();
mBandwidthButton.setDisable(true);
for(DecodeConfigNBFM.Bandwidth bandwidth : DecodeConfigNBFM.Bandwidth.values())
{
ToggleButton toggleButton = new ToggleButton(bandwidth.toString());
toggleButton.setUserData(bandwidth);
mBandwidthButton.getButtons().add(toggleButton);
}
mBandwidthButton.getToggleGroup().selectedToggleProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mBandwidthButton;
}
private ToggleSwitch getAudioRecordSwitch()
{
if(mAudioRecordSwitch == null)
{
mAudioRecordSwitch = new ToggleSwitch();
mAudioRecordSwitch.setDisable(true);
mAudioRecordSwitch.setTextAlignment(TextAlignment.RIGHT);
mAudioRecordSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mAudioRecordSwitch;
}
private ToggleSwitch getBasebandRecordSwitch()
{
if(mBasebandRecordSwitch == null)
{
mBasebandRecordSwitch = new ToggleSwitch();
mBasebandRecordSwitch.setDisable(true);
mBasebandRecordSwitch.setTextAlignment(TextAlignment.RIGHT);
mBasebandRecordSwitch.selectedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mBasebandRecordSwitch;
}
@Override
protected void setDecoderConfiguration(DecodeConfiguration config)
{
if(config instanceof DecodeConfigNBFM)
{
getBandwidthButton().setDisable(false);
DecodeConfigNBFM decodeConfigNBFM = (DecodeConfigNBFM)config;
DecodeConfigNBFM.Bandwidth bandwidth = decodeConfigNBFM.getBandwidth();
if(bandwidth == null)
{
bandwidth = DecodeConfigNBFM.Bandwidth.BW_12_5;
}
for(Toggle toggle: getBandwidthButton().getToggleGroup().getToggles())
{
toggle.setSelected(toggle.getUserData() == bandwidth);
}
getAudioRecordSwitch().setDisable(false);
getAudioRecordSwitch().selectedProperty().set(decodeConfigNBFM.getRecordAudio());
}
else
{
getBandwidthButton().setDisable(true);
for(Toggle toggle: getBandwidthButton().getToggleGroup().getToggles())
{
toggle.setSelected(false);
}
getAudioRecordSwitch().setDisable(true);
getAudioRecordSwitch().selectedProperty().set(false);
}
}
@Override
protected void saveDecoderConfiguration()
{
DecodeConfigNBFM config;
if(getItem().getDecodeConfiguration() instanceof DecodeConfigNBFM)
{
config = (DecodeConfigNBFM)getItem().getDecodeConfiguration();
}
else
{
config = new DecodeConfigNBFM();
}
DecodeConfigNBFM.Bandwidth bandwidth = DecodeConfigNBFM.Bandwidth.BW_12_5;
if(getBandwidthButton().getToggleGroup().getSelectedToggle() != null)
{
bandwidth = (DecodeConfigNBFM.Bandwidth)getBandwidthButton().getToggleGroup().getSelectedToggle().getUserData();
}
config.setBandwidth(bandwidth);
config.setRecordAudio(getAudioRecordSwitch().isSelected());
getItem().setDecodeConfiguration(config);
}
@Override
protected void setEventLogConfiguration(EventLogConfiguration config)
{
getEventLogConfigurationEditor().setItem(config);
}
@Override
protected void saveEventLogConfiguration()
{
getEventLogConfigurationEditor().save();
if(getEventLogConfigurationEditor().getItem().getLoggers().isEmpty())
{
getItem().setEventLogConfiguration(null);
}
else
{
getItem().setEventLogConfiguration(getEventLogConfigurationEditor().getItem());
}
}
@Override
protected void setAuxDecoderConfiguration(AuxDecodeConfiguration config)
{
getAuxDecoderConfigurationEditor().setItem(config);
}
@Override
protected void saveAuxDecoderConfiguration()
{
getAuxDecoderConfigurationEditor().save();
if(getAuxDecoderConfigurationEditor().getItem().getAuxDecoders().isEmpty())
{
getItem().setAuxDecodeConfiguration(null);
}
else
{
getItem().setAuxDecodeConfiguration(getAuxDecoderConfigurationEditor().getItem());
}
}
@Override
protected void setRecordConfiguration(RecordConfiguration config)
{
if(config != null)
{
getBasebandRecordSwitch().setDisable(false);
getBasebandRecordSwitch().selectedProperty().set(config.contains(RecorderType.BASEBAND));
}
else
{
getBasebandRecordSwitch().selectedProperty().set(false);
getBasebandRecordSwitch().setDisable(true);
}
}
@Override
protected void saveRecordConfiguration()
{
RecordConfiguration config = new RecordConfiguration();
if(getBasebandRecordSwitch().selectedProperty().get())
{
config.addRecorder(RecorderType.BASEBAND);
}
getItem().setRecordConfiguration(config);
}
@Override
protected void setSourceConfiguration(SourceConfiguration config)
{
getSourceConfigurationEditor().setSourceConfiguration(config);
}
@Override
protected void saveSourceConfiguration()
{
getSourceConfigurationEditor().save();
SourceConfiguration sourceConfiguration = getSourceConfigurationEditor().getSourceConfiguration();
getItem().setSourceConfiguration(sourceConfiguration);
}
}

View File

@@ -0,0 +1,429 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.channel;
import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor;
import io.github.dsheirer.gui.playlist.record.RecordConfigurationEditor;
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
import io.github.dsheirer.gui.playlist.source.SourceConfigurationEditor;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
import io.github.dsheirer.module.log.EventLogType;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.source.config.SourceConfiguration;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import org.controlsfx.control.SegmentedButton;
import org.controlsfx.control.ToggleSwitch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* P25 Phase 1 channel configuration editor
*/
public class P25P1ConfigurationEditor extends ChannelConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger(P25P1ConfigurationEditor.class);
private TitledPane mDecoderPane;
private TitledPane mEventLogPane;
private TitledPane mRecordPane;
private TitledPane mSourcePane;
private SourceConfigurationEditor mSourceConfigurationEditor;
private EventLogConfigurationEditor mEventLogConfigurationEditor;
private RecordConfigurationEditor mRecordConfigurationEditor;
private ToggleSwitch mIgnoreDataCallsButton;
private Spinner<Integer> mTrafficChannelPoolSizeSpinner;
private SegmentedButton mModulationSegmentedButton;
private ToggleButton mC4FMToggleButton;
private ToggleButton mLSMToggleButton;
/**
* Constructs an instance
* @param playlistManager
*/
public P25P1ConfigurationEditor(PlaylistManager playlistManager)
{
super(playlistManager);
getTitledPanesBox().getChildren().add(getSourcePane());
getTitledPanesBox().getChildren().add(getDecoderPane());
getTitledPanesBox().getChildren().add(getEventLogPane());
getTitledPanesBox().getChildren().add(getRecordPane());
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.P25_PHASE1;
}
private TitledPane getSourcePane()
{
if(mSourcePane == null)
{
mSourcePane = new TitledPane("Source", getSourceConfigurationEditor());
mSourcePane.setExpanded(true);
}
return mSourcePane;
}
private TitledPane getDecoderPane()
{
if(mDecoderPane == null)
{
mDecoderPane = new TitledPane();
mDecoderPane.setText("Decoder: P25 Phase 1 (or P25 Phase 2 Control Channel)");
mDecoderPane.setExpanded(true);
GridPane gridPane = new GridPane();
gridPane.setPadding(new Insets(10,10,10,10));
gridPane.setHgap(10);
gridPane.setVgap(10);
Label modulationLabel = new Label("Modulation");
GridPane.setHalignment(modulationLabel, HPos.RIGHT);
GridPane.setConstraints(modulationLabel, 0, 0);
gridPane.getChildren().add(modulationLabel);
GridPane.setConstraints(getModulationSegmentedButton(), 1, 0);
gridPane.getChildren().addAll(getModulationSegmentedButton());
Label poolSizeLabel = new Label("Max Traffic Channels");
GridPane.setHalignment(poolSizeLabel, HPos.RIGHT);
GridPane.setConstraints(poolSizeLabel, 2, 0);
gridPane.getChildren().add(poolSizeLabel);
GridPane.setConstraints(getTrafficChannelPoolSizeSpinner(), 3, 0);
gridPane.getChildren().add(getTrafficChannelPoolSizeSpinner());
GridPane.setConstraints(getIgnoreDataCallsButton(), 4, 0);
gridPane.getChildren().add(getIgnoreDataCallsButton());
Label directionLabel = new Label("Ignore Data Calls");
GridPane.setHalignment(directionLabel, HPos.LEFT);
GridPane.setConstraints(directionLabel, 5, 0);
gridPane.getChildren().add(directionLabel);
Label modulationHelpLabel = new Label("C4FM: repeaters and non-simulcast trunked systems. LSM: simulcast trunked systems.");
GridPane.setConstraints(modulationHelpLabel, 0, 1, 6, 1);
gridPane.getChildren().add(modulationHelpLabel);
mDecoderPane.setContent(gridPane);
}
return mDecoderPane;
}
private TitledPane getEventLogPane()
{
if(mEventLogPane == null)
{
mEventLogPane = new TitledPane("Logging", getEventLogConfigurationEditor());
mEventLogPane.setExpanded(false);
}
return mEventLogPane;
}
private TitledPane getRecordPane()
{
if(mRecordPane == null)
{
mRecordPane = new TitledPane();
mRecordPane.setText("Recording");
mRecordPane.setExpanded(false);
Label notice = new Label("Note: use aliases to control call audio recording");
notice.setPadding(new Insets(10, 10, 0, 10));
VBox vBox = new VBox();
vBox.getChildren().addAll(getRecordConfigurationEditor(), notice);
mRecordPane.setContent(vBox);
}
return mRecordPane;
}
private SourceConfigurationEditor getSourceConfigurationEditor()
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), true);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mSourceConfigurationEditor;
}
private EventLogConfigurationEditor getEventLogConfigurationEditor()
{
if(mEventLogConfigurationEditor == null)
{
List<EventLogType> types = new ArrayList<>();
types.add(EventLogType.CALL_EVENT);
types.add(EventLogType.DECODED_MESSAGE);
types.add(EventLogType.TRAFFIC_CALL_EVENT);
types.add(EventLogType.TRAFFIC_DECODED_MESSAGE);
mEventLogConfigurationEditor = new EventLogConfigurationEditor(types);
mEventLogConfigurationEditor.setPadding(new Insets(5,5,5,5));
mEventLogConfigurationEditor.modifiedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mEventLogConfigurationEditor;
}
private SegmentedButton getModulationSegmentedButton()
{
if(mModulationSegmentedButton == null)
{
mModulationSegmentedButton = new SegmentedButton();
mModulationSegmentedButton.getButtons().addAll(getC4FMToggleButton(), getLSMToggleButton());
mModulationSegmentedButton.getToggleGroup().selectedToggleProperty().addListener(new ChangeListener<Toggle>()
{
@Override
public void changed(ObservableValue<? extends Toggle> observable, Toggle oldValue, Toggle newValue)
{
if(newValue == null)
{
//Ensure at least one toggle is always selected
oldValue.setSelected(true);
}
else if(oldValue != null && newValue != null)
{
//Only set modified if the toggle changed from one to the other
modifiedProperty().set(true);
}
}
});
}
return mModulationSegmentedButton;
}
private ToggleButton getC4FMToggleButton()
{
if(mC4FMToggleButton == null)
{
mC4FMToggleButton = new ToggleButton("C4FM");
}
return mC4FMToggleButton;
}
private ToggleButton getLSMToggleButton()
{
if(mLSMToggleButton == null)
{
mLSMToggleButton = new ToggleButton("LSM");
}
return mLSMToggleButton;
}
private ToggleSwitch getIgnoreDataCallsButton()
{
if(mIgnoreDataCallsButton == null)
{
mIgnoreDataCallsButton = new ToggleSwitch();
mIgnoreDataCallsButton.setDisable(true);
mIgnoreDataCallsButton.selectedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mIgnoreDataCallsButton;
}
private Spinner<Integer> getTrafficChannelPoolSizeSpinner()
{
if(mTrafficChannelPoolSizeSpinner == null)
{
mTrafficChannelPoolSizeSpinner = new Spinner();
mTrafficChannelPoolSizeSpinner.setDisable(true);
mTrafficChannelPoolSizeSpinner.setTooltip(
new Tooltip("Maximum number of traffic channels that can be created by the decoder"));
mTrafficChannelPoolSizeSpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(0, 50);
mTrafficChannelPoolSizeSpinner.setValueFactory(svf);
mTrafficChannelPoolSizeSpinner.getValueFactory().valueProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mTrafficChannelPoolSizeSpinner;
}
private RecordConfigurationEditor getRecordConfigurationEditor()
{
if(mRecordConfigurationEditor == null)
{
List<RecorderType> types = new ArrayList<>();
types.add(RecorderType.BASEBAND);
types.add(RecorderType.DEMODULATED_BIT_STREAM);
types.add(RecorderType.MBE_CALL_SEQUENCE);
types.add(RecorderType.TRAFFIC_BASEBAND);
types.add(RecorderType.TRAFFIC_DEMODULATED_BIT_STREAM);
types.add(RecorderType.TRAFFIC_MBE_CALL_SEQUENCE);
mRecordConfigurationEditor = new RecordConfigurationEditor(types);
mRecordConfigurationEditor.setDisable(true);
mRecordConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mRecordConfigurationEditor;
}
@Override
protected void setDecoderConfiguration(DecodeConfiguration config)
{
getIgnoreDataCallsButton().setDisable(config == null);
getTrafficChannelPoolSizeSpinner().setDisable(config == null);
if(config instanceof DecodeConfigP25Phase1)
{
DecodeConfigP25Phase1 decodeConfig = (DecodeConfigP25Phase1)config;
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
if(decodeConfig.getModulation() == P25P1Decoder.Modulation.C4FM)
{
getC4FMToggleButton().setSelected(true);
getLSMToggleButton().setSelected(false);
}
else
{
getC4FMToggleButton().setSelected(false);
getLSMToggleButton().setSelected(true);
}
}
else
{
getIgnoreDataCallsButton().setSelected(false);
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0);
}
}
@Override
protected void saveDecoderConfiguration()
{
DecodeConfigP25Phase1 config;
if(getItem().getDecodeConfiguration() instanceof DecodeConfigP25Phase1)
{
config = (DecodeConfigP25Phase1)getItem().getDecodeConfiguration();
}
else
{
config = new DecodeConfigP25Phase1();
}
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
config.setModulation(getC4FMToggleButton().isSelected() ? P25P1Decoder.Modulation.C4FM : P25P1Decoder.Modulation.CQPSK);
getItem().setDecodeConfiguration(config);
}
@Override
protected void setEventLogConfiguration(EventLogConfiguration config)
{
getEventLogConfigurationEditor().setItem(config);
}
@Override
protected void saveEventLogConfiguration()
{
getEventLogConfigurationEditor().save();
if(getEventLogConfigurationEditor().getItem().getLoggers().isEmpty())
{
getItem().setEventLogConfiguration(null);
}
else
{
getItem().setEventLogConfiguration(getEventLogConfigurationEditor().getItem());
}
}
@Override
protected void setAuxDecoderConfiguration(AuxDecodeConfiguration config)
{
//no-op
}
@Override
protected void saveAuxDecoderConfiguration()
{
//no-op
}
@Override
protected void setRecordConfiguration(RecordConfiguration config)
{
getRecordConfigurationEditor().setDisable(config == null);
getRecordConfigurationEditor().setItem(config);
}
@Override
protected void saveRecordConfiguration()
{
getRecordConfigurationEditor().save();
RecordConfiguration config = getRecordConfigurationEditor().getItem();
getItem().setRecordConfiguration(config);
}
@Override
protected void setSourceConfiguration(SourceConfiguration config)
{
getSourceConfigurationEditor().setSourceConfiguration(config);
}
@Override
protected void saveSourceConfiguration()
{
getSourceConfigurationEditor().save();
SourceConfiguration sourceConfiguration = getSourceConfigurationEditor().getSourceConfiguration();
getItem().setSourceConfiguration(sourceConfiguration);
}
}

View File

@@ -0,0 +1,411 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.channel;
import io.github.dsheirer.gui.control.IntegerTextField;
import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor;
import io.github.dsheirer.gui.playlist.record.RecordConfigurationEditor;
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
import io.github.dsheirer.gui.playlist.source.SourceConfigurationEditor;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
import io.github.dsheirer.module.decode.p25.phase2.enumeration.ScrambleParameters;
import io.github.dsheirer.module.log.EventLogType;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.source.config.SourceConfiguration;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import org.controlsfx.control.ToggleSwitch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* P25 Phase 2 channel configuration editor
*/
public class P25P2ConfigurationEditor extends ChannelConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger(P25P2ConfigurationEditor.class);
private TitledPane mDecoderPane;
private TitledPane mEventLogPane;
private TitledPane mRecordPane;
private TitledPane mSourcePane;
private SourceConfigurationEditor mSourceConfigurationEditor;
private EventLogConfigurationEditor mEventLogConfigurationEditor;
private RecordConfigurationEditor mRecordConfigurationEditor;
private ToggleSwitch mAutoDetectScrambleParameters;
private IntegerTextField mWacnTextField;
private IntegerTextField mSystemTextField;
private IntegerTextField mNacTextField;
/**
* Constructs an instance
* @param playlistManager
*/
public P25P2ConfigurationEditor(PlaylistManager playlistManager)
{
super(playlistManager);
getTitledPanesBox().getChildren().add(getSourcePane());
getTitledPanesBox().getChildren().add(getDecoderPane());
getTitledPanesBox().getChildren().add(getEventLogPane());
getTitledPanesBox().getChildren().add(getRecordPane());
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.P25_PHASE2;
}
private TitledPane getSourcePane()
{
if(mSourcePane == null)
{
mSourcePane = new TitledPane("Source", getSourceConfigurationEditor());
mSourcePane.setExpanded(true);
}
return mSourcePane;
}
private TitledPane getDecoderPane()
{
if(mDecoderPane == null)
{
mDecoderPane = new TitledPane();
mDecoderPane.setText("Decoder: P25 Phase 2");
mDecoderPane.setExpanded(true);
GridPane gridPane = new GridPane();
gridPane.setPadding(new Insets(10,10,10,10));
gridPane.setHgap(10);
gridPane.setVgap(10);
GridPane.setConstraints(getAutoDetectScrambleParameters(), 0, 0);
gridPane.getChildren().add(getAutoDetectScrambleParameters());
Label directionLabel = new Label("Auto-Detect (WACN/System/NAC)");
GridPane.setHalignment(directionLabel, HPos.LEFT);
GridPane.setConstraints(directionLabel, 1, 0);
gridPane.getChildren().add(directionLabel);
Label wacnLabel = new Label("WACN");
GridPane.setHalignment(wacnLabel, HPos.RIGHT);
GridPane.setConstraints(wacnLabel, 0, 1);
gridPane.getChildren().add(wacnLabel);
GridPane.setConstraints(getWacnTextField(), 1, 1);
gridPane.getChildren().add(getWacnTextField());
Label systemLabel = new Label("System");
GridPane.setHalignment(systemLabel, HPos.RIGHT);
GridPane.setConstraints(systemLabel, 0, 2);
gridPane.getChildren().add(systemLabel);
GridPane.setConstraints(getSystemTextField(), 1, 2);
gridPane.getChildren().add(getSystemTextField());
Label nacLabel = new Label("NAC");
GridPane.setHalignment(nacLabel, HPos.RIGHT);
GridPane.setConstraints(nacLabel, 0, 3);
gridPane.getChildren().add(nacLabel);
GridPane.setConstraints(getNacTextField(), 1, 3);
gridPane.getChildren().add(getNacTextField());
mDecoderPane.setContent(gridPane);
}
return mDecoderPane;
}
private TitledPane getEventLogPane()
{
if(mEventLogPane == null)
{
mEventLogPane = new TitledPane("Logging", getEventLogConfigurationEditor());
mEventLogPane.setExpanded(false);
}
return mEventLogPane;
}
private TitledPane getRecordPane()
{
if(mRecordPane == null)
{
mRecordPane = new TitledPane();
mRecordPane.setText("Recording");
mRecordPane.setExpanded(false);
Label notice = new Label("Note: use aliases to control call audio recording");
notice.setPadding(new Insets(10, 10, 0, 10));
VBox vBox = new VBox();
vBox.getChildren().addAll(getRecordConfigurationEditor(), notice);
mRecordPane.setContent(vBox);
}
return mRecordPane;
}
private SourceConfigurationEditor getSourceConfigurationEditor()
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mSourceConfigurationEditor;
}
private EventLogConfigurationEditor getEventLogConfigurationEditor()
{
if(mEventLogConfigurationEditor == null)
{
List<EventLogType> types = new ArrayList<>();
types.add(EventLogType.CALL_EVENT);
types.add(EventLogType.DECODED_MESSAGE);
mEventLogConfigurationEditor = new EventLogConfigurationEditor(types);
mEventLogConfigurationEditor.setPadding(new Insets(5,5,5,5));
mEventLogConfigurationEditor.modifiedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mEventLogConfigurationEditor;
}
private ToggleSwitch getAutoDetectScrambleParameters()
{
if(mAutoDetectScrambleParameters == null)
{
mAutoDetectScrambleParameters = new ToggleSwitch();
//This messes up too many users. Hiding it.
mAutoDetectScrambleParameters.setVisible(false);
mAutoDetectScrambleParameters.setDisable(true);
mAutoDetectScrambleParameters.selectedProperty()
.addListener((observable, oldValue, newValue) -> {
modifiedProperty().set(true);
getWacnTextField().setDisable(mAutoDetectScrambleParameters.isSelected());
getSystemTextField().setDisable(mAutoDetectScrambleParameters.isSelected());
getNacTextField().setDisable(mAutoDetectScrambleParameters.isSelected());
});
}
return mAutoDetectScrambleParameters;
}
private IntegerTextField getWacnTextField()
{
if(mWacnTextField == null)
{
mWacnTextField = new IntegerTextField();
mWacnTextField.setDisable(true);
mWacnTextField.textProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mWacnTextField;
}
private IntegerTextField getSystemTextField()
{
if(mSystemTextField == null)
{
mSystemTextField = new IntegerTextField();
mSystemTextField.setDisable(true);
mSystemTextField.textProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mSystemTextField;
}
private IntegerTextField getNacTextField()
{
if(mNacTextField == null)
{
mNacTextField = new IntegerTextField();
mNacTextField.setDisable(true);
mNacTextField.textProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mNacTextField;
}
private RecordConfigurationEditor getRecordConfigurationEditor()
{
if(mRecordConfigurationEditor == null)
{
List<RecorderType> types = new ArrayList<>();
types.add(RecorderType.BASEBAND);
types.add(RecorderType.DEMODULATED_BIT_STREAM);
types.add(RecorderType.MBE_CALL_SEQUENCE);
mRecordConfigurationEditor = new RecordConfigurationEditor(types);
mRecordConfigurationEditor.setDisable(true);
mRecordConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mRecordConfigurationEditor;
}
@Override
protected void setDecoderConfiguration(DecodeConfiguration config)
{
getAutoDetectScrambleParameters().setDisable(config == null);
if(config instanceof DecodeConfigP25Phase2)
{
DecodeConfigP25Phase2 decodeConfig = (DecodeConfigP25Phase2)config;
getAutoDetectScrambleParameters().setSelected(decodeConfig.isAutoDetectScrambleParameters());
getWacnTextField().setDisable(decodeConfig.isAutoDetectScrambleParameters());
getSystemTextField().setDisable(decodeConfig.isAutoDetectScrambleParameters());
getNacTextField().setDisable(decodeConfig.isAutoDetectScrambleParameters());
ScrambleParameters scrambleParameters = decodeConfig.getScrambleParameters();
if(scrambleParameters != null)
{
getWacnTextField().set(scrambleParameters.getWACN());
getSystemTextField().set(scrambleParameters.getSystem());
getNacTextField().set(scrambleParameters.getNAC());
}
else
{
getWacnTextField().set(0);
getSystemTextField().set(0);
getNacTextField().set(0);
}
}
else
{
getWacnTextField().set(0);
getSystemTextField().set(0);
getNacTextField().set(0);
getAutoDetectScrambleParameters().setSelected(false);
getWacnTextField().setDisable(true);
getSystemTextField().setDisable(true);
getNacTextField().setDisable(true);
}
}
@Override
protected void saveDecoderConfiguration()
{
DecodeConfigP25Phase2 config;
if(getItem().getDecodeConfiguration() instanceof DecodeConfigP25Phase2)
{
config = (DecodeConfigP25Phase2)getItem().getDecodeConfiguration();
config.setAutoDetectScrambleParameters(getAutoDetectScrambleParameters().isSelected());
int wacn = getWacnTextField().get();
int system = getSystemTextField().get();
int nac = getSystemTextField().get();
config.setScrambleParameters(new ScrambleParameters(wacn, system, nac));
}
else
{
config = new DecodeConfigP25Phase2();
}
getItem().setDecodeConfiguration(config);
}
@Override
protected void setEventLogConfiguration(EventLogConfiguration config)
{
getEventLogConfigurationEditor().setItem(config);
}
@Override
protected void saveEventLogConfiguration()
{
getEventLogConfigurationEditor().save();
if(getEventLogConfigurationEditor().getItem().getLoggers().isEmpty())
{
getItem().setEventLogConfiguration(null);
}
else
{
getItem().setEventLogConfiguration(getEventLogConfigurationEditor().getItem());
}
}
@Override
protected void setAuxDecoderConfiguration(AuxDecodeConfiguration config)
{
//no-op
}
@Override
protected void saveAuxDecoderConfiguration()
{
//no-op
}
@Override
protected void setRecordConfiguration(RecordConfiguration config)
{
getRecordConfigurationEditor().setDisable(config == null);
getRecordConfigurationEditor().setItem(config);
}
@Override
protected void saveRecordConfiguration()
{
getRecordConfigurationEditor().save();
RecordConfiguration config = getRecordConfigurationEditor().getItem();
getItem().setRecordConfiguration(config);
}
@Override
protected void setSourceConfiguration(SourceConfiguration config)
{
getSourceConfigurationEditor().setSourceConfiguration(config);
}
@Override
protected void saveSourceConfiguration()
{
getSourceConfigurationEditor().save();
SourceConfiguration sourceConfiguration = getSourceConfigurationEditor().getSourceConfiguration();
getItem().setSourceConfiguration(sourceConfiguration);
}
}

View File

@@ -0,0 +1,296 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.channel;
import io.github.dsheirer.gui.playlist.decoder.AuxDecoderConfigurationEditor;
import io.github.dsheirer.gui.playlist.eventlog.EventLogConfigurationEditor;
import io.github.dsheirer.gui.playlist.record.RecordConfigurationEditor;
import io.github.dsheirer.gui.playlist.source.FrequencyEditor;
import io.github.dsheirer.gui.playlist.source.SourceConfigurationEditor;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.passport.DecodeConfigPassport;
import io.github.dsheirer.module.log.EventLogType;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.source.config.SourceConfiguration;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.VBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* Passport channel configuration editor
*/
public class PassportConfigurationEditor extends ChannelConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger(PassportConfigurationEditor.class);
private TitledPane mAuxDecoderPane;
private TitledPane mDecoderPane;
private TitledPane mEventLogPane;
private TitledPane mRecordPane;
private TitledPane mSourcePane;
private SourceConfigurationEditor mSourceConfigurationEditor;
private AuxDecoderConfigurationEditor mAuxDecoderConfigurationEditor;
private EventLogConfigurationEditor mEventLogConfigurationEditor;
private RecordConfigurationEditor mRecordConfigurationEditor;
/**
* Constructs an instance
* @param playlistManager
*/
public PassportConfigurationEditor(PlaylistManager playlistManager)
{
super(playlistManager);
getTitledPanesBox().getChildren().add(getSourcePane());
getTitledPanesBox().getChildren().add(getDecoderPane());
getTitledPanesBox().getChildren().add(getAuxDecoderPane());
getTitledPanesBox().getChildren().add(getEventLogPane());
getTitledPanesBox().getChildren().add(getRecordPane());
}
@Override
public DecoderType getDecoderType()
{
return DecoderType.PASSPORT;
}
private TitledPane getSourcePane()
{
if(mSourcePane == null)
{
mSourcePane = new TitledPane("Source", getSourceConfigurationEditor());
mSourcePane.setExpanded(true);
}
return mSourcePane;
}
private TitledPane getDecoderPane()
{
if(mDecoderPane == null)
{
mDecoderPane = new TitledPane();
mDecoderPane.setText("Decoder: Passport");
mDecoderPane.setExpanded(false);
mDecoderPane.setDisable(true);
}
return mDecoderPane;
}
private TitledPane getEventLogPane()
{
if(mEventLogPane == null)
{
mEventLogPane = new TitledPane("Logging", getEventLogConfigurationEditor());
mEventLogPane.setExpanded(false);
}
return mEventLogPane;
}
private TitledPane getAuxDecoderPane()
{
if(mAuxDecoderPane == null)
{
mAuxDecoderPane = new TitledPane("Additional Decoders", getAuxDecoderConfigurationEditor());
mAuxDecoderPane.setExpanded(false);
}
return mAuxDecoderPane;
}
private TitledPane getRecordPane()
{
if(mRecordPane == null)
{
mRecordPane = new TitledPane();
mRecordPane.setText("Recording");
mRecordPane.setExpanded(false);
Label notice = new Label("Note: use aliases to control call audio recording");
notice.setPadding(new Insets(10, 10, 0, 10));
VBox vBox = new VBox();
vBox.getChildren().addAll(getRecordConfigurationEditor(), notice);
mRecordPane.setContent(vBox);
}
return mRecordPane;
}
private SourceConfigurationEditor getSourceConfigurationEditor()
{
if(mSourceConfigurationEditor == null)
{
mSourceConfigurationEditor = new FrequencyEditor(getTunerModel(), false);
//Add a listener so that we can push change notifications up to this editor
mSourceConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mSourceConfigurationEditor;
}
private EventLogConfigurationEditor getEventLogConfigurationEditor()
{
if(mEventLogConfigurationEditor == null)
{
List<EventLogType> types = new ArrayList<>();
types.add(EventLogType.CALL_EVENT);
types.add(EventLogType.DECODED_MESSAGE);
mEventLogConfigurationEditor = new EventLogConfigurationEditor(types);
mEventLogConfigurationEditor.setPadding(new Insets(5,5,5,5));
mEventLogConfigurationEditor.modifiedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mEventLogConfigurationEditor;
}
private AuxDecoderConfigurationEditor getAuxDecoderConfigurationEditor()
{
if(mAuxDecoderConfigurationEditor == null)
{
List<DecoderType> types = new ArrayList<>();
types.add(DecoderType.FLEETSYNC2);
types.add(DecoderType.MDC1200);
mAuxDecoderConfigurationEditor = new AuxDecoderConfigurationEditor(types);
mAuxDecoderConfigurationEditor.setPadding(new Insets(5,5,5,5));
mAuxDecoderConfigurationEditor.modifiedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mAuxDecoderConfigurationEditor;
}
private RecordConfigurationEditor getRecordConfigurationEditor()
{
if(mRecordConfigurationEditor == null)
{
List<RecorderType> types = new ArrayList<>();
types.add(RecorderType.BASEBAND);
mRecordConfigurationEditor = new RecordConfigurationEditor(types);
mRecordConfigurationEditor.setDisable(true);
mRecordConfigurationEditor.modifiedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mRecordConfigurationEditor;
}
@Override
protected void setDecoderConfiguration(DecodeConfiguration config)
{
}
@Override
protected void saveDecoderConfiguration()
{
if(!(getItem().getDecodeConfiguration() instanceof DecodeConfigPassport))
{
getItem().setDecodeConfiguration(new DecodeConfigPassport());
}
}
@Override
protected void setEventLogConfiguration(EventLogConfiguration config)
{
getEventLogConfigurationEditor().setItem(config);
}
@Override
protected void saveEventLogConfiguration()
{
getEventLogConfigurationEditor().save();
if(getEventLogConfigurationEditor().getItem().getLoggers().isEmpty())
{
getItem().setEventLogConfiguration(null);
}
else
{
getItem().setEventLogConfiguration(getEventLogConfigurationEditor().getItem());
}
}
@Override
protected void setAuxDecoderConfiguration(AuxDecodeConfiguration config)
{
getAuxDecoderConfigurationEditor().setItem(config);
}
@Override
protected void saveAuxDecoderConfiguration()
{
getAuxDecoderConfigurationEditor().save();
if(getAuxDecoderConfigurationEditor().getItem().getAuxDecoders().isEmpty())
{
getItem().setAuxDecodeConfiguration(null);
}
else
{
getItem().setAuxDecodeConfiguration(getAuxDecoderConfigurationEditor().getItem());
}
}
@Override
protected void setRecordConfiguration(RecordConfiguration config)
{
getRecordConfigurationEditor().setDisable(config == null);
getRecordConfigurationEditor().setItem(config);
}
@Override
protected void saveRecordConfiguration()
{
getRecordConfigurationEditor().save();
RecordConfiguration config = getRecordConfigurationEditor().getItem();
getItem().setRecordConfiguration(config);
}
@Override
protected void setSourceConfiguration(SourceConfiguration config)
{
getSourceConfigurationEditor().setSourceConfiguration(config);
}
@Override
protected void saveSourceConfiguration()
{
getSourceConfigurationEditor().save();
SourceConfiguration sourceConfiguration = getSourceConfigurationEditor().getSourceConfiguration();
getItem().setSourceConfiguration(sourceConfiguration);
}
}

View File

@@ -0,0 +1,123 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.channel;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.record.config.RecordConfiguration;
import io.github.dsheirer.source.config.SourceConfiguration;
import javafx.scene.control.TitledPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Unknown protocol channel configuration editor
*/
public class UnknownConfigurationEditor extends ChannelConfigurationEditor
{
private final static Logger mLog = LoggerFactory.getLogger(UnknownConfigurationEditor.class);
private TitledPane mDecoderPane;
/**
* Constructs an instance
* @param aliasModel
*/
public UnknownConfigurationEditor(PlaylistManager playlistManager)
{
super(playlistManager);
getTitledPanesBox().getChildren().add(getDecoderPane());
}
private TitledPane getDecoderPane()
{
if(mDecoderPane == null)
{
mDecoderPane = new TitledPane();
mDecoderPane.setText("Decoder: Unknown Protocol");
mDecoderPane.setExpanded(false);
mDecoderPane.setDisable(true);
}
return mDecoderPane;
}
@Override
public DecoderType getDecoderType()
{
return null;
}
@Override
protected void setDecoderConfiguration(DecodeConfiguration config)
{
}
@Override
protected void saveDecoderConfiguration()
{
}
@Override
protected void setEventLogConfiguration(EventLogConfiguration config)
{
}
@Override
protected void saveEventLogConfiguration()
{
}
@Override
protected void setAuxDecoderConfiguration(AuxDecodeConfiguration config)
{
}
@Override
protected void saveAuxDecoderConfiguration()
{
}
@Override
protected void setRecordConfiguration(RecordConfiguration config)
{
}
@Override
protected void saveRecordConfiguration()
{
//Audio recording is handled in the NBFM decoder config
}
@Override
protected void setSourceConfiguration(SourceConfiguration config)
{
}
@Override
protected void saveSourceConfiguration()
{
}
}

View File

@@ -0,0 +1,44 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.channel;
import io.github.dsheirer.controller.channel.Channel;
/**
* Request to view a channel in the channel editor
*/
public class ViewChannelRequest extends ChannelTabRequest
{
private Channel mChannel;
/**
* Constructs an instance
* @param channel to view
*/
public ViewChannelRequest(Channel channel)
{
mChannel = channel;
}
public Channel getChannel()
{
return mChannel;
}
}

View File

@@ -0,0 +1,821 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.channelMap;
import io.github.dsheirer.controller.channel.map.ChannelMap;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
import io.github.dsheirer.controller.channel.map.ChannelRange;
import io.github.dsheirer.gui.control.IntegerTextField;
import io.github.dsheirer.gui.playlist.Editor;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.Separator;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.util.Callback;
import java.util.Optional;
/**
* JavaFX editor for channel maps
*/
public class ChannelMapEditor extends SplitPane
{
private static final String COPY_NAME = " (copy)";
private ChannelMapModel mChannelMapModel;
private SplitPane mSplitPane;
private MapEditor mMapEditor;
private HBox mChannelMapManagerBox;
private VBox mMapButtonsBox;
private Button mNewMapButton;
private Button mCloneMapButton;
private Button mDeleteMapButton;
private Button mHelpButton;
private ListView<ChannelMap> mChannelMapListView;
private GridPane mChannelRangePane;
private IntegerTextField mFirstField;
private IntegerTextField mLastField;
private IntegerTextField mBaseFrequencyField;
private IntegerTextField mStepSizeField;
/**
* Constructs an instance
*
* @param channelMapModel for accessing channel maps
*/
public ChannelMapEditor(ChannelMapModel channelMapModel)
{
mChannelMapModel = channelMapModel;
setOrientation(Orientation.VERTICAL);
getItems().addAll(getChannelMapManagerBox(), getMapEditor(), getChannelRangePane());
}
/**
* Top pane for selecting and managing channel maps in the list view
*/
private HBox getChannelMapManagerBox()
{
if(mChannelMapManagerBox == null)
{
mChannelMapManagerBox = new HBox();
HBox.setHgrow(getChannelMapListView(), Priority.ALWAYS);
mChannelMapManagerBox.getChildren().addAll(getChannelMapListView(), getMapButtonsBox());
}
return mChannelMapManagerBox;
}
private VBox getMapButtonsBox()
{
if(mMapButtonsBox == null)
{
mMapButtonsBox = new VBox();
mMapButtonsBox.setPadding(new Insets(10, 10, 10, 10));
mMapButtonsBox.setSpacing(10);
mMapButtonsBox.getChildren().addAll(getNewMapButton(), getCloneMapButton(), getDeleteMapButton(), getHelpButton());
}
return mMapButtonsBox;
}
private Button getNewMapButton()
{
if(mNewMapButton == null)
{
mNewMapButton = new Button("New Map");
mNewMapButton.setTooltip(new Tooltip("Create a new channel map"));
mNewMapButton.setMaxWidth(Double.MAX_VALUE);
mNewMapButton.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
ChannelMap channelMap = new ChannelMap("New Channel Map");
mChannelMapModel.addChannelMap(channelMap);
getChannelMapListView().getSelectionModel().select(channelMap);
}
});
}
return mNewMapButton;
}
private Button getCloneMapButton()
{
if(mCloneMapButton == null)
{
mCloneMapButton = new Button("Clone Map");
mCloneMapButton.setTooltip(new Tooltip("Create a copy of the selected channel map"));
mCloneMapButton.setDisable(true);
mCloneMapButton.setMaxWidth(Double.MAX_VALUE);
mCloneMapButton.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
ChannelMap selected = getChannelMapListView().getSelectionModel().getSelectedItem();
if(selected != null)
{
ChannelMap copy = selected.copyOf();
String name = selected.getName();
if(name == null)
{
name = COPY_NAME;
}
else if(!name.toLowerCase().endsWith(COPY_NAME))
{
name = name + COPY_NAME;
}
copy.setName(name);
mChannelMapModel.addChannelMap(copy);
getChannelMapListView().getSelectionModel().select(copy);
}
}
});
}
return mCloneMapButton;
}
private void updateChannelMapButtons()
{
boolean selected = getChannelMapListView().getSelectionModel().getSelectedItem() != null;
getCloneMapButton().setDisable(!selected);
getDeleteMapButton().setDisable(!selected);
}
private Button getDeleteMapButton()
{
if(mDeleteMapButton == null)
{
mDeleteMapButton = new Button("Delete Map");
mDeleteMapButton.setTooltip(new Tooltip("Delete the selected channel map"));
mDeleteMapButton.setDisable(true);
mDeleteMapButton.setMaxWidth(Double.MAX_VALUE);
mDeleteMapButton.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION,
"Do you want to delete the selected channel map?", ButtonType.NO, ButtonType.YES);
alert.setTitle("Delete Channel Map");
alert.setHeaderText("Are you sure?");
alert.initOwner(((Node)getDeleteMapButton()).getScene().getWindow());
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.YES)
{
ChannelMap selected = getChannelMapListView().getSelectionModel().getSelectedItem();
if(selected != null)
{
mChannelMapModel.removeChannelMap(selected);
updateChannelMapButtons();
}
}
}
});
}
return mDeleteMapButton;
}
private Button getHelpButton()
{
if(mHelpButton == null)
{
mHelpButton = new Button("Help");
mHelpButton.setMaxWidth(Double.MAX_VALUE);
mHelpButton.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
Alert alert = new Alert(Alert.AlertType.INFORMATION);
Text text1 = new Text("Channel maps are used by trunking systems to define the transmit frequency " +
"used for each channel number. A channel map is required so that the frequency for each call " +
"channel can be calculated correctly");
text1.setWrappingWidth(300);
Text text2 = new Text("A channel map contains one or more channel ranges. Each range has a first " +
"and last channel number and the channel numbers for each range do NOT overlap. Each range " +
"also has a base frequency and a step or channel size");
text2.setWrappingWidth(300);
Text text3 = new Text("The frequency for each channel number is calculated as: base frequency + " +
"(last - first * stepSize)");
text3.setWrappingWidth(300);
VBox vbox = new VBox();
vbox.setPadding(new Insets(10, 10, 10, 10));
vbox.setSpacing(10);
vbox.getChildren().addAll(text1, text2, text3);
alert.getDialogPane().setContent(vbox);
alert.setTitle("Channel Map Help");
alert.setHeaderText("Channel Map Help");
alert.initOwner(((Node)getHelpButton()).getScene().getWindow());
//Workaround for JavaFX KDE on Linux bug in FX 10/11: https://bugs.openjdk.java.net/browse/JDK-8179073
alert.setResizable(true);
alert.onShownProperty().addListener(e -> {
Platform.runLater(() -> alert.setResizable(false));
});
alert.showAndWait();
}
});
}
return mHelpButton;
}
private ListView<ChannelMap> getChannelMapListView()
{
if(mChannelMapListView == null)
{
mChannelMapListView = new ListView<>();
mChannelMapListView.setItems(mChannelMapModel.getChannelMaps());
mChannelMapListView.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> {
getMapEditor().setItem(newValue);
updateChannelMapButtons();
});
}
return mChannelMapListView;
}
private MapEditor getMapEditor()
{
if(mMapEditor == null)
{
mMapEditor = new MapEditor();
}
return mMapEditor;
}
private ChannelRange getSelectedChannelRange()
{
return getMapEditor().getChannelRangeTableView().getSelectionModel().getSelectedItem();
}
private IntegerTextField getFirstField()
{
if(mFirstField == null)
{
mFirstField = new IntegerTextField();
mFirstField.setDisable(true);
mFirstField.textProperty()
.addListener((observable, oldValue, newValue) -> {
if(getSelectedChannelRange() != null)
{
Integer value = getFirstField().get();
getSelectedChannelRange().setFirstChannelNumber(value != null ? value : 0);
getMapEditor().modifiedProperty().set(true);
}
});
}
return mFirstField;
}
private IntegerTextField getLastField()
{
if(mLastField == null)
{
mLastField = new IntegerTextField();
mLastField.setDisable(true);
mLastField.textProperty()
.addListener((observable, oldValue, newValue) -> {
if(getSelectedChannelRange() != null)
{
Integer value = getLastField().get();
getSelectedChannelRange().setLastChannelNumber(value != null ? value : 0);
getMapEditor().modifiedProperty().set(true);
}
});
}
return mLastField;
}
private IntegerTextField getBaseFrequencyField()
{
if(mBaseFrequencyField == null)
{
mBaseFrequencyField = new IntegerTextField();
mBaseFrequencyField.setDisable(true);
mBaseFrequencyField.textProperty().addListener((observable, oldValue, newValue) -> {
if(getSelectedChannelRange() != null)
{
Integer value = getBaseFrequencyField().get();
getSelectedChannelRange().setBaseFrequency(value != null ? value : 0);
getMapEditor().modifiedProperty().set(true);
}
});
}
return mBaseFrequencyField;
}
private IntegerTextField getStepSizeField()
{
if(mStepSizeField == null)
{
mStepSizeField = new IntegerTextField();
mStepSizeField.setDisable(true);
mStepSizeField.textProperty().addListener((observable, oldValue, newValue) -> {
if(getSelectedChannelRange() != null)
{
Integer value = getStepSizeField().get();
getSelectedChannelRange().setStepSize(value != null ? value : 0);
getMapEditor().modifiedProperty().set(true);
}
});
}
return mStepSizeField;
}
private GridPane getChannelRangePane()
{
if(mChannelRangePane == null)
{
mChannelRangePane = new GridPane();
mChannelRangePane.setPadding(new Insets(10, 10, 10,10));
mChannelRangePane.setVgap(10);
mChannelRangePane.setHgap(5);
Label firstLabel = new Label("First Channel");
GridPane.setHalignment(firstLabel, HPos.RIGHT);
GridPane.setConstraints(firstLabel, 0, 0);
mChannelRangePane.getChildren().add(firstLabel);
GridPane.setConstraints(getFirstField(), 1, 0);
GridPane.setHgrow(getFirstField(), Priority.ALWAYS);
mChannelRangePane.getChildren().add(getFirstField());
Label lastLabel = new Label("Last Channel");
GridPane.setHalignment(lastLabel, HPos.RIGHT);
GridPane.setConstraints(lastLabel, 0, 1);
mChannelRangePane.getChildren().add(lastLabel);
GridPane.setConstraints(getLastField(), 1, 1);
GridPane.setHgrow(getLastField(), Priority.ALWAYS);
mChannelRangePane.getChildren().add(getLastField());
Label baseLabel = new Label("Base Frequency (Hz)");
GridPane.setHalignment(baseLabel, HPos.RIGHT);
GridPane.setConstraints(baseLabel, 0, 2);
mChannelRangePane.getChildren().add(baseLabel);
GridPane.setConstraints(getBaseFrequencyField(), 1, 2);
GridPane.setHgrow(getBaseFrequencyField(), Priority.ALWAYS);
mChannelRangePane.getChildren().add(getBaseFrequencyField());
Label stepSizeLabel = new Label("Step Size (Hz)");
GridPane.setHalignment(stepSizeLabel, HPos.RIGHT);
GridPane.setConstraints(stepSizeLabel, 0, 3);
mChannelRangePane.getChildren().add(stepSizeLabel);
GridPane.setConstraints(getStepSizeField(), 1, 3);
GridPane.setHgrow(getStepSizeField(), Priority.ALWAYS);
mChannelRangePane.getChildren().add(getStepSizeField());
}
return mChannelRangePane;
}
public class MapEditor extends Editor<ChannelMap>
{
private Button mSaveButton;
private Button mResetButton;
private Button mNewRangeButton;
private Button mDeleteRangeButton;
private TableView<ChannelRange> mChannelRangeTableView;
private TextField mNameField;
public MapEditor()
{
HBox nameBox = new HBox();
nameBox.setPadding(new Insets(10, 0, 10, 10));
nameBox.setSpacing(10);
nameBox.setAlignment(Pos.CENTER);
HBox.setHgrow(getNameField(), Priority.ALWAYS);
nameBox.getChildren().addAll(new Label("Name"), getNameField());
VBox contentsBox = new VBox();
VBox.setVgrow(nameBox, Priority.NEVER);
VBox.setVgrow(getChannelRangeTableView(), Priority.ALWAYS);
contentsBox.getChildren().addAll(nameBox, getChannelRangeTableView());
VBox buttonsBox = new VBox();
buttonsBox.setPadding(new Insets(10, 10, 10, 10));
buttonsBox.setSpacing(10);
buttonsBox.getChildren().addAll(getSaveButton(), getResetButton(), new Separator(),
getNewRangeButton(), getDeleteRangeButton());
HBox hbox = new HBox();
HBox.setHgrow(contentsBox, Priority.ALWAYS);
hbox.getChildren().addAll(contentsBox, buttonsBox);
getChildren().add(hbox);
modifiedProperty().addListener((observable, oldValue, modified) -> {
getSaveButton().setDisable(!modified);
getResetButton().setDisable(!modified);
});
}
private void updateChannelRangeButtons()
{
getNewRangeButton().setDisable(getItem() == null);
getDeleteRangeButton().setDisable(getChannelRangeTableView().getSelectionModel().getSelectedItem() == null);
}
@Override
public void save()
{
ChannelRange selectedChannelRange = getChannelRangeTableView().getSelectionModel().getSelectedItem();
if(getItem() != null)
{
getItem().nameProperty().set(getNameField().getText());
getItem().getItems().clear();
getItem().getItems().addAll(getChannelRangeTableView().getItems());
}
modifiedProperty().set(false);
//Hack - the colorized table cells weren't updating after the channel map was validated
setItem(getItem());
if(getItem() != null && !getItem().isValid())
{
Alert alert = new Alert(Alert.AlertType.WARNING);
Text text1 = new Text("Channel range(s) have errors that must be fixed:");
text1.setWrappingWidth(300);
Text text2 = new Text("- Verify that the first channel number is less than the last channel number");
text2.setWrappingWidth(300);
Text text3 = new Text("- Verify that channel numbers in each range do not overlap with other channel ranges.");
text3.setWrappingWidth(300);
VBox vbox = new VBox();
vbox.setPadding(new Insets(10, 10, 10, 10));
vbox.setSpacing(10);
vbox.getChildren().addAll(text1, text2, text3);
alert.getDialogPane().setContent(vbox);
alert.setTitle("Channel Map Error");
alert.setHeaderText("Channel Map has errors");
alert.initOwner(((Node)getSaveButton()).getScene().getWindow());
//Workaround for JavaFX KDE on Linux bug in FX 10/11: https://bugs.openjdk.java.net/browse/JDK-8179073
alert.setResizable(true);
alert.onShownProperty().addListener(e -> {
Platform.runLater(() -> alert.setResizable(false));
});
alert.showAndWait();
for(ChannelRange channelRange: getItem().getRanges())
{
if(channelRange.isOverlapping() || !channelRange.isValid())
{
getChannelRangeTableView().getSelectionModel().select(channelRange);
break;
}
}
}
//Re-select the channel range that was selected before the save
getChannelRangeTableView().getSelectionModel().select(selectedChannelRange);
}
@Override
public void dispose()
{
//no-op
}
@Override
public void setItem(ChannelMap item)
{
if(modifiedProperty().get())
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.getButtonTypes().clear();
alert.getButtonTypes().addAll(ButtonType.NO, ButtonType.YES);
alert.setTitle("Save Changes");
alert.setHeaderText("Channel Map has been modified");
alert.setContentText("Do you want to save these changes?");
alert.initOwner(((Node)getSaveButton()).getScene().getWindow());
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.YES)
{
save();
}
}
super.setItem(item);
getNameField().setDisable(item == null);
getChannelRangeTableView().getItems().clear();
getChannelRangeTableView().setDisable(item == null);
if(item != null)
{
getNameField().setText(item.getName());
getChannelRangeTableView().getItems().addAll(item.getRanges());
}
updateChannelRangeButtons();
modifiedProperty().set(false);
}
private TextField getNameField()
{
if(mNameField == null)
{
mNameField = new TextField();
mNameField.setMaxWidth(Double.MAX_VALUE);
mNameField.setDisable(true);
mNameField.textProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mNameField;
}
private TableView<ChannelRange> getChannelRangeTableView()
{
if(mChannelRangeTableView == null)
{
mChannelRangeTableView = new TableView<>();
TableColumn<ChannelRange,Integer> firstColumn = new TableColumn("First");
firstColumn.setCellValueFactory(new PropertyValueFactory<ChannelRange,Integer>("firstChannel"));
firstColumn.setCellFactory(param -> new ColorTableCell());
TableColumn<ChannelRange,Integer> lastColumn = new TableColumn("Last");
lastColumn.setCellValueFactory(new PropertyValueFactory<ChannelRange,Integer>("lastChannel"));
lastColumn.setCellFactory(param -> new ColorTableCell());
TableColumn<ChannelRange,Integer> baseColumn = new TableColumn("Base");
baseColumn.setCellValueFactory(new PropertyValueFactory<ChannelRange,Integer>("baseFrequency"));
baseColumn.setPrefWidth(95);
TableColumn<ChannelRange,Integer> stepColumn = new TableColumn("Step");
stepColumn.setCellValueFactory(new PropertyValueFactory<ChannelRange,Integer>("stepSize"));
mChannelRangeTableView.getColumns().addAll(firstColumn, lastColumn, baseColumn, stepColumn);
mChannelRangeTableView.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, selected) -> {
getFirstField().setDisable(selected == null);
getLastField().setDisable(selected == null);
getBaseFrequencyField().setDisable(selected == null);
getStepSizeField().setDisable(selected == null);
//Capture the current modified state and reapply it after updating the editor controls
boolean modified = modifiedProperty().get();
if(selected != null)
{
getFirstField().set(selected.getFirstChannelNumber());
getLastField().set(selected.getLastChannelNumber());
getBaseFrequencyField().set(selected.getBaseFrequency());
getStepSizeField().set(selected.getStepSize());
}
else
{
getFirstField().setText("");
getLastField().setText("");
getBaseFrequencyField().setText("");
getStepSizeField().setText("");
}
updateChannelRangeButtons();
modifiedProperty().set(modified);
});
}
return mChannelRangeTableView;
}
private Button getSaveButton()
{
if(mSaveButton == null)
{
mSaveButton = new Button("Save");
mSaveButton.setTooltip(new Tooltip("Save changes to the Channel Map"));
mSaveButton.setMaxWidth(Double.MAX_VALUE);
mSaveButton.setDisable(true);
mSaveButton.setOnAction(event -> save());
}
return mSaveButton;
}
private Button getResetButton()
{
if(mResetButton == null)
{
mResetButton = new Button("Reset");
mResetButton.setTooltip(new Tooltip("Undo any changes made to the channel map"));
mResetButton.setMaxWidth(Double.MAX_VALUE);
mResetButton.setDisable(true);
mResetButton.setOnAction(event -> {
modifiedProperty().set(false);
setItem(getItem());
});
}
return mResetButton;
}
private Button getNewRangeButton()
{
if(mNewRangeButton == null)
{
mNewRangeButton = new Button("New Range");
mNewMapButton.setTooltip(new Tooltip("Add a new channel range to the channel map"));
mNewRangeButton.setMaxWidth(Double.MAX_VALUE);
mNewRangeButton.setDisable(true);
mNewRangeButton.setOnAction(event -> {
if(getItem() != null)
{
ChannelRange channelRange = new ChannelRange();
getChannelRangeTableView().getItems().add(channelRange);
getChannelRangeTableView().getSelectionModel().select(channelRange);
modifiedProperty().set(true);
}
});
}
return mNewRangeButton;
}
private Button getDeleteRangeButton()
{
if(mDeleteRangeButton == null)
{
mDeleteRangeButton = new Button("Delete Range");
mDeleteRangeButton.setTooltip(new Tooltip("Delete the currently selected channel range"));
mDeleteRangeButton.setMaxWidth(Double.MAX_VALUE);
mDeleteRangeButton.setDisable(true);
mDeleteRangeButton.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
if(getItem() != null)
{
ChannelRange selected = getChannelRangeTableView().getSelectionModel().getSelectedItem();
if(selected != null)
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION,
"Do you want to delete the selected channel range?", ButtonType.NO, ButtonType.YES);
alert.setTitle("Delete Channel Range");
alert.setHeaderText("Are you sure?");
alert.initOwner(((Node)getDeleteRangeButton()).getScene().getWindow());
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.YES)
{
getChannelRangeTableView().getItems().remove(selected);
updateChannelRangeButtons();
modifiedProperty().set(true);
}
}
}
}
});
}
return mDeleteRangeButton;
}
}
/**
* Table column factory for channel ranges to indicate errors in channel ranges.
*/
public class ChannelNumberCell implements Callback<TableColumn<ChannelRange,Integer>,TableCell<ChannelRange,Integer>>
{
@Override
public TableCell<ChannelRange,Integer> call(TableColumn<ChannelRange,Integer> param)
{
return new ColorTableCell();
}
}
/**
* Custom table cell that shows a red background when the channel range is invalid or overlapping
*/
public class ColorTableCell extends TableCell<ChannelRange,Integer>
{
private static final String BACKGROUND_COLOR_RED = "-fx-background-color: red;";
private static final String BACKGROND_COLOR_NONE = "-fx-background-color: null;";
public ColorTableCell()
{
}
@Override
protected void updateItem(Integer item, boolean empty)
{
super.updateItem(item, empty);
if(item == null || empty)
{
setText(null);
}
else
{
setText(item.toString());
}
boolean invalidRange = getTableRow().getItem() != null &&
(!getTableRow().getItem().isValid() || getTableRow().getItem().isOverlapping() ||
item == null || empty);
this.setStyle(invalidRange ? BACKGROUND_COLOR_RED : BACKGROND_COLOR_NONE);
}
@Override
protected boolean isItemChanged(Integer oldItem, Integer newItem)
{
return super.isItemChanged(oldItem, newItem);
}
}
/**
* Processes a request to show/edit a channel map
*/
public void process(ChannelMapEditorViewRequest request)
{
String channelMapName = request.getChannelMapName();
if(channelMapName != null)
{
for(ChannelMap channelMap: getChannelMapListView().getItems())
{
if(channelMapName.contentEquals(channelMap.getName()))
{
getChannelMapListView().getSelectionModel().select(channelMap);
break;
}
}
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.channelMap;
/**
* Request to show the channel map editor
*/
public class ChannelMapEditorViewRequest
{
private String mChannelMapName;
/**
* Constructs an instance
* @param channelMapName to select once the editor is showing.
*/
public ChannelMapEditorViewRequest(String channelMapName)
{
mChannelMapName = channelMapName;
}
/**
* Constructs an instance.
*/
public ChannelMapEditorViewRequest()
{
}
public String getChannelMapName()
{
return mChannelMapName;
}
public boolean hasChannelMapName()
{
return mChannelMapName != null;
}
}

View File

@@ -0,0 +1,146 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.decoder;
import io.github.dsheirer.gui.playlist.Editor;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.config.AuxDecodeConfiguration;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import org.controlsfx.control.ToggleSwitch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Editor for auxiliary decoder configuration objects using a VBox to visually display a vertical list of boolean toggle
* switches to turn on/off aux decoders for a custom set of decoder types.
*/
public class AuxDecoderConfigurationEditor extends Editor<AuxDecodeConfiguration>
{
private final static Logger mLog = LoggerFactory.getLogger(AuxDecoderConfigurationEditor.class);
private List<AuxDecoderControl> mControls = new ArrayList<>();
public AuxDecoderConfigurationEditor(Collection<DecoderType> types)
{
for(DecoderType type: types)
{
AuxDecoderControl control = new AuxDecoderControl(type);
mControls.add(control);
getChildren().add(control);
}
}
@Override
public void setItem(AuxDecodeConfiguration item)
{
if(item == null)
{
item = new AuxDecodeConfiguration();
}
super.setItem(item);
for(AuxDecoderControl control: mControls)
{
control.getToggleSwitch().setDisable(false);
control.getToggleSwitch().setSelected(item.getAuxDecoders().contains(control.getDecoderType()));
}
modifiedProperty().set(false);
}
@Override
public void save()
{
AuxDecodeConfiguration config = getItem();
if(config == null)
{
config = new AuxDecodeConfiguration();
}
config.clearAuxDecoders();
for(AuxDecoderControl control: mControls)
{
if(control.getToggleSwitch().isSelected())
{
config.addAuxDecoder(control.getDecoderType());
}
}
setItem(config);
}
@Override
public void dispose()
{
}
public class AuxDecoderControl extends GridPane
{
private DecoderType mDecoderType;
private ToggleSwitch mToggleSwitch;
public AuxDecoderControl(DecoderType type)
{
mDecoderType = type;
setPadding(new Insets(5,5,5,5));
setHgap(10);
GridPane.setConstraints(getToggleSwitch(), 0, 0);
getChildren().add(getToggleSwitch());
Label label = new Label(mDecoderType.getDisplayString());
GridPane.setHalignment(label, HPos.LEFT);
GridPane.setConstraints(label, 1, 0);
getChildren().add(label);
}
private ToggleSwitch getToggleSwitch()
{
if(mToggleSwitch == null)
{
mToggleSwitch = new ToggleSwitch();
mToggleSwitch.setDisable(true);
mToggleSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mToggleSwitch;
}
public DecoderType getDecoderType()
{
return mDecoderType;
}
}
}

View File

@@ -0,0 +1,142 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.eventlog;
import io.github.dsheirer.gui.playlist.Editor;
import io.github.dsheirer.module.log.EventLogType;
import io.github.dsheirer.module.log.config.EventLogConfiguration;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import org.controlsfx.control.ToggleSwitch;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Editor for event logging configuration objects using a VBox to visually display a vertical list of boolean toggle
* switches to turn on/off event logging for a custom set of event log types.
*/
public class EventLogConfigurationEditor extends Editor<EventLogConfiguration>
{
private List<EventLogControl> mControls = new ArrayList<>();
public EventLogConfigurationEditor(Collection<EventLogType> types)
{
for(EventLogType type: types)
{
EventLogControl control = new EventLogControl(type);
mControls.add(control);
getChildren().add(control);
}
}
@Override
public void setItem(EventLogConfiguration item)
{
if(item == null)
{
item = new EventLogConfiguration();
}
super.setItem(item);
for(EventLogControl control: mControls)
{
control.getToggleSwitch().setDisable(false);
control.getToggleSwitch().setSelected(item.getLoggers().contains(control.getEventLogType()));
}
modifiedProperty().set(false);
}
@Override
public void save()
{
EventLogConfiguration config = getItem();
if(config == null)
{
config = new EventLogConfiguration();
}
config.clear();
for(EventLogControl control: mControls)
{
if(control.getToggleSwitch().isSelected())
{
config.addLogger(control.getEventLogType());
}
}
setItem(config);
}
@Override
public void dispose()
{
}
public class EventLogControl extends GridPane
{
private EventLogType mEventLogType;
private ToggleSwitch mToggleSwitch;
public EventLogControl(EventLogType type)
{
mEventLogType = type;
setPadding(new Insets(5,5,5,5));
setHgap(10);
GridPane.setConstraints(getToggleSwitch(), 0, 0);
getChildren().add(getToggleSwitch());
Label label = new Label(mEventLogType.getDisplayString());
GridPane.setHalignment(label, HPos.LEFT);
GridPane.setConstraints(label, 1, 0);
getChildren().add(label);
}
private ToggleSwitch getToggleSwitch()
{
if(mToggleSwitch == null)
{
mToggleSwitch = new ToggleSwitch();
mToggleSwitch.setDisable(true);
mToggleSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mToggleSwitch;
}
public EventLogType getEventLogType()
{
return mEventLogType;
}
}
}

View File

@@ -0,0 +1,539 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.manager;
import com.google.common.eventbus.Subscribe;
import com.google.common.io.Files;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.controller.channel.ChannelException;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.PreferenceType;
import io.github.dsheirer.preference.UserPreferences;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Separator;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.FileChooser;
import javafx.util.Callback;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
/**
* Editor for managing playlists via the playlist manager
*/
public class PlaylistManagerEditor extends HBox
{
private static final Logger mLog = LoggerFactory.getLogger(PlaylistManagerEditor.class);
private static final FileChooser.ExtensionFilter PLAYLIST_FILE_FILTER =
new FileChooser.ExtensionFilter("Playlist Files (*.xml)", "*.xml");
private static final FileChooser.ExtensionFilter ALL_FILES_FILE_FILTER =
new FileChooser.ExtensionFilter("All Files (*.*)", "*.*");
private PlaylistManager mPlaylistManager;
private UserPreferences mUserPreferences;
private TableView<Path> mPlaylistTableView;
private VBox mButtonBox;
private Button mSelectButton;
private Button mAddButton;
private Button mRemoveButton;
private Button mCloneButton;
private Button mNewButton;
private Button mDeleteButton;
/**
* Constructs an instance
* @param playlistManager for managing playlist
* @param userPreferences for accessing preferences
*/
public PlaylistManagerEditor(PlaylistManager playlistManager, UserPreferences userPreferences)
{
mPlaylistManager = playlistManager;
mUserPreferences = userPreferences;
//Register to receive preferences updates
MyEventBus.getEventBus().register(this);
setPadding(new Insets(5, 5, 5, 5));
HBox.setHgrow(getPlaylistTableView(), Priority.ALWAYS);
getChildren().addAll(getPlaylistTableView(), getButtonBox());
updateButtons();
}
/**
* Indicates if the path argument is the same as the current playlist path
*/
private boolean isCurrent(Path path)
{
Path current = mUserPreferences.getPlaylistPreference().getPlaylist();
return path != null && current != null && path.equals(current);
}
/**
* Saves the current list of playlists to the user preferences.
*/
private void savePlaylistsPreference()
{
mUserPreferences.getPlaylistPreference().setPlaylistList(getPlaylistTableView().getItems());
}
private TableView<Path> getPlaylistTableView()
{
if(mPlaylistTableView == null)
{
mPlaylistTableView = new TableView<>();
TableColumn<Path,String> iconColumn = new TableColumn("Selected");
iconColumn.setPrefWidth(100);
iconColumn.setCellFactory(new Callback<TableColumn<Path,String>,TableCell<Path,String>>()
{
@Override
public TableCell<Path,String> call(TableColumn<Path,String> param)
{
TableCell<Path,String> tableCell = new TableCell<>()
{
@Override
protected void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
if(empty || getTableRow() == null || getTableRow().getItem() == null)
{
setGraphic(null);
}
else
{
Path path = getTableRow().getItem();
if(isCurrent(path))
{
IconNode iconNode = new IconNode(FontAwesome.CHECK);
iconNode.setFill(Color.GREEN);
setGraphic(iconNode);
}
else if(!path.toFile().exists())
{
IconNode iconNode = new IconNode(FontAwesome.TIMES);
iconNode.setFill(Color.RED);
setGraphic(iconNode);
}
else
{
setGraphic(null);
}
}
}
};
tableCell.setAlignment(Pos.BASELINE_RIGHT);
return tableCell;
}
});
TableColumn<Path,String> pathColumn = new TableColumn<>("Playlist");
pathColumn.setCellFactory(param -> new TableCell<Path,String>()
{
@Override
protected void updateItem(String item, boolean empty)
{
if(empty || getTableRow() == null || getTableRow().getItem() == null)
{
setText(null);
}
else
{
setText(getTableRow().getItem().toString());
}
}
});
pathColumn.setPrefWidth(650);
mPlaylistTableView.getColumns().addAll(iconColumn, pathColumn);
mPlaylistTableView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> updateButtons());
List<Path> playlistPaths = mUserPreferences.getPlaylistPreference().getPlaylistList();
mPlaylistTableView.getItems().addAll(playlistPaths);
}
return mPlaylistTableView;
}
private void updateButtons()
{
Path selected = getPlaylistTableView().getSelectionModel().getSelectedItem();
boolean itemSelected = (selected != null);
boolean isCurrent = isCurrent(selected);
getSelectButton().setDisable(!itemSelected || isCurrent);
getRemoveButton().setDisable(!itemSelected || isCurrent);
getCloneButton().setDisable(!itemSelected || (selected != null && !selected.toFile().exists()));
getDeleteButton().setDisable(!itemSelected || isCurrent || (selected != null && !selected.toFile().exists()));
}
private VBox getButtonBox()
{
if(mButtonBox == null)
{
mButtonBox = new VBox();
mButtonBox.setSpacing(10.0);
mButtonBox.setPadding(new Insets(5, 5, 5, 10));
mButtonBox.setAlignment(Pos.TOP_CENTER);
mButtonBox.getChildren().add(getSelectButton());
mButtonBox.getChildren().add(new Separator());
mButtonBox.getChildren().addAll(getNewButton(), getAddButton(), getRemoveButton(), getCloneButton());
mButtonBox.getChildren().add(new Separator());
mButtonBox.getChildren().add(getDeleteButton());
}
return mButtonBox;
}
private Button getSelectButton()
{
if(mSelectButton == null)
{
mSelectButton = new Button("Select");
mSelectButton.setTooltip(new Tooltip("Sets the selected playlist as the current playlist"));
mSelectButton.setMaxWidth(Double.MAX_VALUE);
mSelectButton.setOnAction(event -> {
Path current = mUserPreferences.getPlaylistPreference().getPlaylist();
Path selected = getPlaylistTableView().getSelectionModel().getSelectedItem();
if(selected != null)
{
try
{
mPlaylistManager.setPlaylist(selected);
}
catch(IOException ioe)
{
mLog.error("Error loading playlist [" + (selected != null ? selected.toString() : "null") + "]");
new Alert(Alert.AlertType.ERROR, "Unable to load selected playlist. " +
"Reverting to previous playlist", ButtonType.OK).show();
try
{
mPlaylistManager.setPlaylist(current);
}
catch(IOException ioe2)
{
mLog.error("Error reverting to previous playlist [" +
(current != null ? current.toString() : "null") + "]");
}
}
final List<Channel> autoStartChannels = mPlaylistManager.getChannelModel().getAutoStartChannels();
if(autoStartChannels.size() > 0)
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION,
"Would you like to auto-start your channels?", ButtonType.YES, ButtonType.NO);
alert.setTitle("Auto-Start Channels");
alert.setHeaderText("Discovered [" + autoStartChannels.size() + "] auto-start channel" +
(autoStartChannels.size() > 1 ? "s" : ""));
alert.showAndWait().ifPresent(buttonType -> {
if(buttonType == ButtonType.YES)
{
boolean error = false;
for(Channel channel: autoStartChannels)
{
try
{
mPlaylistManager.getChannelProcessingManager().start(channel);
}
catch(ChannelException ce)
{
error = true;
}
}
if(error)
{
Alert errorAlert = new Alert(Alert.AlertType.ERROR,
"Unable to start some or all of the auto-start channels",
ButtonType.OK);
errorAlert.setTitle("Channel Auto-Start Error(s)");
errorAlert.setHeaderText("Auto-Start Error");
errorAlert.showAndWait();
}
}
});
}
}
});
}
return mSelectButton;
}
private Button getAddButton()
{
if(mAddButton == null)
{
mAddButton = new Button("Add");
mAddButton.setTooltip(new Tooltip("Add an existing playlist from the file system"));
mAddButton.setMaxWidth(Double.MAX_VALUE);
mAddButton.setOnAction(event -> {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Add Playlist");
fileChooser.setInitialDirectory(mUserPreferences.getDirectoryPreference().getDirectoryPlaylist().toFile());
fileChooser.getExtensionFilters().addAll(PLAYLIST_FILE_FILTER, ALL_FILES_FILE_FILTER);
File playlistToAdd = fileChooser.showOpenDialog(null);
if(playlistToAdd != null)
{
if(PlaylistManager.isPlaylist(playlistToAdd.toPath()))
{
if(!getPlaylistTableView().getItems().contains(playlistToAdd.toPath()))
{
getPlaylistTableView().getItems().add(playlistToAdd.toPath());
savePlaylistsPreference();
}
else
{
new Alert(Alert.AlertType.INFORMATION, "Playlist already added", ButtonType.OK).show();
}
}
else
{
new Alert(Alert.AlertType.ERROR, "This file is not a valid playlist",
ButtonType.OK).show();
}
}
});
}
return mAddButton;
}
private Button getCloneButton()
{
if(mCloneButton == null)
{
mCloneButton = new Button("Clone");
mCloneButton.setTooltip(new Tooltip("Create a clone (copy) of the currently selected playlist"));
mCloneButton.setMaxWidth(Double.MAX_VALUE);
mCloneButton.setOnAction(event -> {
Path selected = getPlaylistTableView().getSelectionModel().getSelectedItem();
if(selected != null)
{
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Copy Playlist");
fileChooser.setInitialDirectory(mUserPreferences.getDirectoryPreference().getDirectoryPlaylist().toFile());
fileChooser.setInitialFileName(selected.getName(selected.getNameCount() - 1).toString());
fileChooser.getExtensionFilters().addAll(PLAYLIST_FILE_FILTER, ALL_FILES_FILE_FILTER);
File copyFile = fileChooser.showSaveDialog(null);
if(copyFile != null)
{
if(!copyFile.toString().endsWith(".xml"))
{
copyFile = new File(copyFile.toString() + ".xml");
if(copyFile.exists())
{
new Alert(Alert.AlertType.ERROR, "File already exists. Please copy " +
"to a new file name", ButtonType.OK).show();
return;
}
}
try
{
Files.copy(selected.toFile(), copyFile);
getPlaylistTableView().getItems().add(copyFile.toPath());
savePlaylistsPreference();
}
catch(IOException ioe)
{
mLog.error("Error creating copy of playlist [" + selected.toString() + "] as [" + copyFile.toString() + "]", ioe);
Alert alert = new Alert(Alert.AlertType.ERROR, "Unable to create copy of playlist", ButtonType.OK);
alert.initOwner(((Node)getCloneButton()).getScene().getWindow());
alert.show();
}
}
}
});
}
return mCloneButton;
}
private Button getRemoveButton()
{
if(mRemoveButton == null)
{
mRemoveButton = new Button("Remove");
mRemoveButton.setTooltip(new Tooltip("Remove the currently selected playlist from the application"));
mRemoveButton.setMaxWidth(Double.MAX_VALUE);
mRemoveButton.setOnAction(event -> {
Path selected = getPlaylistTableView().getSelectionModel().getSelectedItem();
if(selected != null)
{
getPlaylistTableView().getItems().remove(selected);
savePlaylistsPreference();
}
});
}
return mRemoveButton;
}
private Button getNewButton()
{
if(mNewButton == null)
{
mNewButton = new Button("New");
mNewButton.setTooltip(new Tooltip("Create a new playlist"));
mNewButton.setMaxWidth(Double.MAX_VALUE);
mNewButton.setOnAction(event -> {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("New Playlist");
fileChooser.setInitialDirectory(mUserPreferences.getDirectoryPreference().getDirectoryPlaylist().toFile());
fileChooser.setInitialFileName("*.xml");
fileChooser.getExtensionFilters().addAll(PLAYLIST_FILE_FILTER);
File newFile = fileChooser.showSaveDialog(null);
if(newFile != null)
{
try
{
Path toCreate = newFile.toPath();
if(!toCreate.toString().endsWith(".xml"))
{
toCreate = Paths.get(newFile.toString() + ".xml");
//Since we modified the file, we have to check for existence to avoid overwriting
if(toCreate.toFile().exists())
{
new Alert(Alert.AlertType.ERROR, "File already exists. Please choose " +
"a new file name", ButtonType.OK).show();
return;
}
}
mPlaylistManager.createEmptyPlaylist(toCreate);
getPlaylistTableView().getItems().add(toCreate);
savePlaylistsPreference();
}
catch(IOException ioe)
{
mLog.error("Error creating new playlist file [" + newFile.toString() + "]");
new Alert(Alert.AlertType.ERROR, "Unable to create new playlist", ButtonType.OK).show();
}
}
});
}
return mNewButton;
}
private Button getDeleteButton()
{
if(mDeleteButton == null)
{
mDeleteButton = new Button("Delete");
mDeleteButton.setTooltip(new Tooltip("Remove the currently selected playlist and delete it from the file system"));
mDeleteButton.setMaxWidth(Double.MAX_VALUE);
mDeleteButton.setOnAction(event -> {
Path selected = getPlaylistTableView().getSelectionModel().getSelectedItem();
if(selected != null)
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Delete playlist from file system?",
ButtonType.YES, ButtonType.NO);
alert.setHeaderText("Are you sure?");
alert.initOwner(((Node)getDeleteButton()).getScene().getWindow());
Button noButton = (Button)alert.getDialogPane().lookupButton(ButtonType.NO);
noButton.setDefaultButton(true);
Button yesButton = (Button)alert.getDialogPane().lookupButton(ButtonType.YES);
yesButton.setDefaultButton(false);
Optional<ButtonType> optional = alert.showAndWait();
if(optional.get() == ButtonType.YES)
{
getPlaylistTableView().getItems().remove(selected);
savePlaylistsPreference();
selected.toFile().delete();
}
}
});
}
return mDeleteButton;
}
/**
* Receives preference update notifications via the event bus for playlist updates. Use this to queue an update
* to the playlist list view so that we can reflect changes to the current playlist.
* @param preferenceType that was updated
*/
@Subscribe
public void preferenceUpdated(PreferenceType preferenceType)
{
if(preferenceType == PreferenceType.PLAYLIST)
{
Platform.runLater(new Runnable()
{
@Override
public void run()
{
getPlaylistTableView().refresh();
}
});
}
}
}

View File

@@ -0,0 +1,213 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.radioreference;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.type.Agency;
import io.github.dsheirer.rrapi.type.AgencyInfo;
import io.github.dsheirer.service.radioreference.RadioReference;
import io.github.dsheirer.util.ThreadPool;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Editor for displaying a list of agencies and viewing frequency lists for each agency
*/
public class AgencyEditor extends VBox
{
private static final Logger mLog = LoggerFactory.getLogger(AgencyEditor.class);
private UserPreferences mUserPreferences;
private RadioReference mRadioReference;
private PlaylistManager mPlaylistManager;
private Level mLevel;
private ListView<Agency> mAgencyListView;
private AgencyFrequencyEditor mAgencyFrequencyEditor;
private IntegerProperty mAgencyCountProperty = new SimpleIntegerProperty();
/**
* Constructs an instance
* @param userPreferences
* @param radioReference
* @param level
*/
public AgencyEditor(UserPreferences userPreferences, RadioReference radioReference,
PlaylistManager playlistManager, Level level)
{
mUserPreferences = userPreferences;
mRadioReference = radioReference;
mPlaylistManager = playlistManager;
mLevel = level;
mAgencyCountProperty.bind(Bindings.size(getAgencyListView().getItems()));
setPadding(new Insets(10,10,10,10));
setSpacing(10);
VBox.setVgrow(getAgencyFrequencyEditor(), Priority.ALWAYS);
getChildren().addAll(getAgencyListView(), getAgencyFrequencyEditor());
}
/**
* Sets the list of displayed agencies and clears out any existing agencies. Auto-selects an agency if the user
* has selected that agency before.
*
* Note: this should only be invoked on the FX application thread
*
* @param agencies to display
*/
public void setAgencies(List<Agency> agencies)
{
clear();
if(agencies != null && !agencies.isEmpty())
{
Collections.sort(agencies, new AgencyComparator());
getAgencyListView().getItems().addAll(agencies);
int preferredAgencyId = mUserPreferences.getRadioReferencePreference().getPreferredAgencyId(mLevel);
for(Agency agency: getAgencyListView().getItems())
{
if(agency.getAgencyId() == preferredAgencyId)
{
getAgencyListView().getSelectionModel().select(agency);
getAgencyListView().scrollTo(agency);
return;
}
}
}
}
public void clear()
{
getAgencyListView().getItems().clear();
getAgencyFrequencyEditor().setCategories(null);
}
/**
* Observable count of agencies in the editor
*/
public IntegerProperty agencyCountProperty()
{
return mAgencyCountProperty;
}
private AgencyFrequencyEditor getAgencyFrequencyEditor()
{
if(mAgencyFrequencyEditor == null)
{
mAgencyFrequencyEditor = new AgencyFrequencyEditor(mUserPreferences, mRadioReference, mPlaylistManager, mLevel);
}
return mAgencyFrequencyEditor;
}
private ListView<Agency> getAgencyListView()
{
if(mAgencyListView == null)
{
mAgencyListView = new ListView<>();
mAgencyListView.setPrefHeight(200);
mAgencyListView.setCellFactory(param -> new AgencyListCell());
mAgencyListView.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> setAgency(newValue));
mAgencyListView.setPlaceholder(new Label("No agencies available"));
}
return mAgencyListView;
}
private void setAgency(Agency agency)
{
if(agency != null)
{
getAgencyFrequencyEditor().clearAndSetLoading();
mUserPreferences.getRadioReferencePreference().setPreferredAgencyId(agency.getAgencyId(), mLevel);
ThreadPool.SCHEDULED.submit(() -> {
try
{
final AgencyInfo agencyInfo = mRadioReference.getService().getAgencyInfo(agency);
Platform.runLater(() -> getAgencyFrequencyEditor().setCategories(agencyInfo.getCategories()));
}
catch(Throwable t)
{
mLog.error("Error retrieving agency info", t);
Platform.runLater(() -> {
getAgencyFrequencyEditor().setLoading(false);
new RadioReferenceUnavailableAlert(getAgencyListView()).showAndWait();
});
}
});
}
else
{
getAgencyFrequencyEditor().clear();
}
}
public class AgencyListCell extends ListCell<Agency>
{
@Override
protected void updateItem(Agency item, boolean empty)
{
super.updateItem(item, empty);
setText((empty || item == null) ? null : item.getName());
}
}
public class AgencyComparator implements Comparator<Agency>
{
@Override
public int compare(Agency o1, Agency o2)
{
if(o1.getName() == null && o2.getName() == null)
{
return 0;
}
else if(o1.getName() == null)
{
return 1;
}
else if(o2.getName() == null)
{
return -1;
}
else
{
return o1.getName().compareTo(o2.getName());
}
}
}
}

View File

@@ -0,0 +1,591 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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.playlist.radioreference;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.type.Category;
import io.github.dsheirer.rrapi.type.Frequency;
import io.github.dsheirer.rrapi.type.Mode;
import io.github.dsheirer.rrapi.type.SubCategory;
import io.github.dsheirer.service.radioreference.RadioReference;
import io.github.dsheirer.util.ThreadPool;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.util.Callback;
import javafx.util.StringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Grid pane with components for visualizing and selecting categories of frequencies for an agency
*/
public class AgencyFrequencyEditor extends GridPane
{
private static final Logger mLog = LoggerFactory.getLogger(AgencyFrequencyEditor.class);
private static final Category ALL_CATEGORIES = new AllCategories();
private static final SubCategory ALL_SUB_CATEGORIES = new AllSubCategories();
private UserPreferences mUserPreferences;
private RadioReference mRadioReference;
private PlaylistManager mPlaylistManager;
private Level mLevel;
private ComboBox<Category> mCategoryComboBox;
private ComboBox<SubCategory> mSubCategoryComboBox;
private TableView<Frequency> mFrequencyTableView;
private Label mPlaceholderLabel;
private ProgressIndicator mProgressIndicator;
private FrequencyEditor mFrequencyEditor;
public AgencyFrequencyEditor(UserPreferences userPreferences, RadioReference radioReference,
PlaylistManager playlistManager, Level level)
{
mUserPreferences = userPreferences;
mRadioReference = radioReference;
mPlaylistManager = playlistManager;
mLevel = level;
setPadding(new Insets(5, 5, 5,5));
setVgap(10);
setHgap(10);
setMaxHeight(Double.MAX_VALUE);
int row = 0;
ColumnConstraints column1 = new ColumnConstraints();
ColumnConstraints column2 = new ColumnConstraints();
ColumnConstraints column3 = new ColumnConstraints();
column3.setPercentWidth(40);
getColumnConstraints().addAll(column1, column2, column3);
Label categoryLabel = new Label("Category");
GridPane.setConstraints(categoryLabel, 0, row);
GridPane.setHalignment(categoryLabel, HPos.RIGHT);
getChildren().add(categoryLabel);
GridPane.setConstraints(getCategoryComboBox(), 1, row);
GridPane.setHgrow(getCategoryComboBox(), Priority.ALWAYS);
getChildren().add(getCategoryComboBox());
Label subCategoryLabel = new Label("Sub-Category");
GridPane.setConstraints(subCategoryLabel, 0, ++row);
GridPane.setHalignment(subCategoryLabel, HPos.RIGHT);
getChildren().add(subCategoryLabel);
GridPane.setConstraints(getSubCategoryComboBox(), 1, row);
GridPane.setHgrow(getSubCategoryComboBox(), Priority.ALWAYS);
getChildren().add(getSubCategoryComboBox());
GridPane.setConstraints(getFrequencyTableView(), 0, ++row, 2, 1);
GridPane.setHgrow(getFrequencyTableView(), Priority.ALWAYS);
GridPane.setVgrow(getFrequencyTableView(), Priority.ALWAYS);
getChildren().add(getFrequencyTableView());
GridPane.setConstraints(getFrequencyEditor(), 2, 0, 1, 3);
GridPane.setHgrow(getFrequencyEditor(), Priority.ALWAYS);
getChildren().add(getFrequencyEditor());
}
public void setLoading(boolean loading)
{
if(Platform.isFxApplicationThread())
{
getFrequencyTableView().setPlaceholder(loading ? getProgressIndicator() : getPlaceholderLabel());
}
else
{
Platform.runLater(() -> getFrequencyTableView()
.setPlaceholder(loading ? getProgressIndicator() : getPlaceholderLabel()));
}
}
public void clear()
{
getCategoryComboBox().getItems().clear();
getSubCategoryComboBox().getItems().clear();
getFrequencyTableView().getItems().clear();
}
public void clearAndSetLoading()
{
clear();
setLoading(true);
}
public void setCategories(List<Category> categories)
{
clear();
if(categories != null)
{
Collections.sort(categories, new CategoryComparator());
}
if(categories != null && !categories.isEmpty())
{
getCategoryComboBox().getItems().add(ALL_CATEGORIES);
getCategoryComboBox().getItems().addAll(categories);
getCategoryComboBox().getSelectionModel().select(ALL_CATEGORIES);
}
else
{
setLoading(false);
}
}
private FrequencyEditor getFrequencyEditor()
{
if(mFrequencyEditor == null)
{
mFrequencyEditor = new FrequencyEditor(mUserPreferences, mRadioReference, mPlaylistManager, mLevel);
}
return mFrequencyEditor;
}
private ProgressIndicator getProgressIndicator()
{
if(mProgressIndicator == null)
{
mProgressIndicator = new ProgressIndicator();
mProgressIndicator.setProgress(-1);
}
return mProgressIndicator;
}
private Label getPlaceholderLabel()
{
if(mPlaceholderLabel == null)
{
mPlaceholderLabel = new Label("Select an Agency to View Frequency Records");
}
return mPlaceholderLabel;
}
private TableView<Frequency> getFrequencyTableView()
{
if(mFrequencyTableView == null)
{
mFrequencyTableView = new TableView<>();
mFrequencyTableView.setMaxHeight(Double.MAX_VALUE);
mFrequencyTableView.setPlaceholder(getPlaceholderLabel());
TableColumn descriptionColumn = new TableColumn();
descriptionColumn.setText("Description");
descriptionColumn.setCellValueFactory(new PropertyValueFactory<>("description"));
descriptionColumn.setPrefWidth(300);
TableColumn frequencyColumn = new TableColumn();
frequencyColumn.setText("Frequency");
frequencyColumn.setCellValueFactory(new FrequencyCellValueFactory());
frequencyColumn.setPrefWidth(100);
TableColumn modeColumn = new TableColumn();
modeColumn.setText("Mode");
modeColumn.setCellValueFactory(new ModeCellValueFactory());
modeColumn.setPrefWidth(100);
mFrequencyTableView.getColumns().addAll(descriptionColumn, frequencyColumn, modeColumn);
mFrequencyTableView.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, selected) -> {
if(selected != null)
{
SubCategory subCategory = getSubCategory(selected.getSubCategoryId());
Category category = getCategory(subCategory);
getFrequencyEditor().setItem(selected, category, subCategory);
}
else
{
getFrequencyEditor().setItem(null, null, null);
}
});
}
return mFrequencyTableView;
}
/**
* Returns the category parent for the specified sub-category from the current list of categories.
* @return category or null
*/
private Category getCategory(SubCategory subCategory)
{
if(subCategory != null)
{
for(Category category: getCategoryComboBox().getItems())
{
for(SubCategory sub: category.getSubCategories())
{
if(sub == subCategory)
{
return category;
}
}
}
}
return null;
}
/**
* Returns the sub-category that matches the id from the current list of sub-categories
* @return sub-category or null
*/
private SubCategory getSubCategory(int id)
{
for(SubCategory subCategory: getSubCategoryComboBox().getItems())
{
if(subCategory.getSubCategoryId() == id)
{
return subCategory;
}
}
return null;
}
private ComboBox<Category> getCategoryComboBox()
{
if(mCategoryComboBox == null)
{
mCategoryComboBox = new ComboBox<>();
mCategoryComboBox.setMaxWidth(Double.MAX_VALUE);
mCategoryComboBox.setOnAction(event -> {
getFrequencyTableView().getItems().clear();
getSubCategoryComboBox().getItems().clear();
Category selected = mCategoryComboBox.getSelectionModel().getSelectedItem();
if(selected != null)
{
List<SubCategory> subCategories = new ArrayList<>();
if(selected.equals(ALL_CATEGORIES))
{
for(Category category: getCategoryComboBox().getItems())
{
if(category.getSubCategories() != null)
{
subCategories.addAll(category.getSubCategories());
}
}
}
else if(selected.getSubCategories() != null)
{
subCategories.addAll(selected.getSubCategories());
}
Collections.sort(subCategories, new SubCategoryComparator());
getSubCategoryComboBox().getItems().add(ALL_SUB_CATEGORIES);
getSubCategoryComboBox().getItems().addAll(subCategories);
getSubCategoryComboBox().getSelectionModel().select(ALL_SUB_CATEGORIES);
}
});
mCategoryComboBox.setConverter(new StringConverter<Category>()
{
@Override
public String toString(Category category)
{
if(category != null)
{
return category.getName();
}
return null;
}
@Override
public Category fromString(String string)
{
if(string != null)
{
for(Category category: mCategoryComboBox.getItems())
{
if(category.getName().contentEquals(string))
{
return category;
}
}
}
return null;
}
});
}
return mCategoryComboBox;
}
private ComboBox<SubCategory> getSubCategoryComboBox()
{
if(mSubCategoryComboBox == null)
{
mSubCategoryComboBox = new ComboBox<>();
mSubCategoryComboBox.setMaxWidth(Double.MAX_VALUE);
mSubCategoryComboBox.setConverter(new StringConverter<SubCategory>()
{
@Override
public String toString(SubCategory subCategory)
{
if(subCategory != null)
{
return subCategory.getName();
}
return null;
}
@Override
public SubCategory fromString(String string)
{
if(string != null)
{
for(SubCategory subCategory: mSubCategoryComboBox.getItems())
{
if(subCategory.getName().contentEquals(string))
{
return subCategory;
}
}
}
return null;
}
});
mSubCategoryComboBox.setOnAction(event -> {
setLoading(true);
getFrequencyTableView().getItems().clear();
SubCategory subCategory = mSubCategoryComboBox.getValue();
if(subCategory != null)
{
ThreadPool.SCHEDULED.submit(() -> {
try
{
List<Frequency> frequencies = new ArrayList<>();
if(subCategory.equals(ALL_SUB_CATEGORIES))
{
for(SubCategory subCategory1: getSubCategoryComboBox().getItems())
{
if(subCategory1.equals(ALL_SUB_CATEGORIES))
{
//skip
}
else
{
final List<Frequency> subCatFrequencies = mRadioReference.getService()
.getSubCategoryFrequencies(subCategory1.getSubCategoryId());
if(subCatFrequencies != null)
{
frequencies.addAll(subCatFrequencies);
}
}
}
}
else
{
final List<Frequency> subCatFrequencies = mRadioReference.getService()
.getSubCategoryFrequencies(subCategory.getSubCategoryId());
if(subCatFrequencies != null)
{
frequencies.addAll(subCatFrequencies);
}
}
Collections.sort(frequencies, new FrequencyComparator());
Platform.runLater(() -> {
getFrequencyTableView().getItems().addAll(frequencies);
setLoading(false);
});
}
catch(Throwable t)
{
mLog.error("Error retrieving frequencies for subcategory [" + subCategory.getName() + "]", t);
Platform.runLater(() -> new RadioReferenceUnavailableAlert(getSubCategoryComboBox()).showAndWait());
}
});
}
});
}
return mSubCategoryComboBox;
}
public static class AllCategories extends Category
{
public AllCategories()
{
this.setCategoryId(-1);
this.setName("(All)");
}
}
public static class AllSubCategories extends SubCategory
{
public AllSubCategories()
{
this.setSubCategoryId(-1);
this.setName("(All)");
}
}
public class FrequencyCellValueFactory implements Callback<TableColumn.CellDataFeatures<Frequency, String>,
ObservableValue<String>>
{
private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat("0.00000");
private SimpleStringProperty mFrequencyFormatted = new SimpleStringProperty();
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Frequency, String> param)
{
mFrequencyFormatted.set(FREQUENCY_FORMATTER.format(param.getValue().getDownlink()));
return mFrequencyFormatted;
}
}
public class ModeCellValueFactory implements Callback<TableColumn.CellDataFeatures<Frequency, String>,
ObservableValue<String>>
{
private SimpleStringProperty mMode = new SimpleStringProperty();
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<Frequency, String> param)
{
String mode = param.getValue().getMode();
try
{
Integer modeInt = Integer.valueOf(mode);
Mode modeObj = mRadioReference.getService().getModesMap().get(modeInt);
if(modeObj != null)
{
mMode.set(modeObj != null ? modeObj.getName() : null);
}
}
catch(Exception pe)
{
mMode.set("Error");
}
return mMode;
}
}
public class CategoryComparator implements Comparator<Category>
{
@Override
public int compare(Category o1, Category o2)
{
if(o1.getName() == null && o2.getName() == null)
{
return 0;
}
else if(o1.getName() == null)
{
return 1;
}
else if(o2.getName() == null)
{
return -1;
}
else
{
return o1.getName().compareTo(o2.getName());
}
}
}
public class FrequencyComparator implements Comparator<Frequency>
{
@Override
public int compare(Frequency o1, Frequency o2)
{
if(o1.getAlphaTag() == null && o2.getAlphaTag() == null)
{
return 0;
}
else if(o1.getAlphaTag() == null)
{
return 1;
}
else if(o2.getAlphaTag() == null)
{
return -1;
}
else
{
return o1.getAlphaTag().compareTo(o2.getAlphaTag());
}
}
}
public class SubCategoryComparator implements Comparator<SubCategory>
{
@Override
public int compare(SubCategory o1, SubCategory o2)
{
if(o1.getName() == null && o2.getName() == null)
{
return 0;
}
else if(o1.getName() == null)
{
return 1;
}
else if(o2.getName() == null)
{
return -1;
}
else
{
return o1.getName() .compareTo(o2.getName());
}
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.radioreference;
/**
* Simple request to flash the alias list box
*/
public class FlashAliasListComboBoxRequest
{
}

View File

@@ -0,0 +1,484 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.radioreference;
import io.github.dsheirer.controller.channel.Channel;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.playlist.channel.ViewChannelRequest;
import io.github.dsheirer.module.decode.DecoderFactory;
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
import io.github.dsheirer.module.decode.nbfm.DecodeConfigNBFM;
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.type.Category;
import io.github.dsheirer.rrapi.type.Frequency;
import io.github.dsheirer.rrapi.type.Mode;
import io.github.dsheirer.rrapi.type.SubCategory;
import io.github.dsheirer.service.radioreference.RadioReference;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.util.ThreadPool;
import javafx.animation.FadeTransition;
import javafx.application.Platform;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.util.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
public class FrequencyEditor extends VBox
{
private static final Logger mLog = LoggerFactory.getLogger(FrequencyEditor.class);
private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat("0.00000");
private UserPreferences mUserPreferences;
private RadioReference mRadioReference;
private PlaylistManager mPlaylistManager;
private Level mLevel;
private TextField mAlphaTagTextField;
private TextField mFrequencyTextField;
private TextField mModeTextField;
private TextField mToneTextField;
private TextField mSystemTextField;
private TextField mSiteTextField;
private TextField mNameTextField;
private TextField mDecoderTextField;
private Button mCreateButton;
private Label mChannelCreatedLabel;
private CheckBox mShowCreatedChannelCheckBox;
private ModeDecoderType mModeDecoderType;
private long mFrequency;
public FrequencyEditor(UserPreferences userPreferences, RadioReference radioReference,
PlaylistManager playlistManager, Level level)
{
mUserPreferences = userPreferences;
mRadioReference = radioReference;
mPlaylistManager = playlistManager;
mLevel = level;
GridPane gridPane = new GridPane();
gridPane.setVgap(5);
gridPane.setHgap(5);
int row = 0;
Label detailsLabel = new Label("Radio Reference Details");
GridPane.setConstraints(detailsLabel, 1, row);
gridPane.getChildren().add(detailsLabel);
Label alphaLabel = new Label("Alpha Tag");
GridPane.setHalignment(alphaLabel, HPos.RIGHT);
GridPane.setConstraints(alphaLabel, 0, ++row);
gridPane.getChildren().add(alphaLabel);
GridPane.setHgrow(getAlphaTagTextField(), Priority.ALWAYS);
GridPane.setConstraints(getAlphaTagTextField(), 1, row);
gridPane.getChildren().add(getAlphaTagTextField());
Label toneLabel = new Label("Tone");
GridPane.setHalignment(toneLabel, HPos.RIGHT);
GridPane.setConstraints(toneLabel, 0, ++row);
gridPane.getChildren().add(toneLabel);
GridPane.setHgrow(getToneTextField(), Priority.ALWAYS);
GridPane.setConstraints(getToneTextField(), 1, row);
gridPane.getChildren().add(getToneTextField());
Label modeLabel = new Label("Mode");
GridPane.setHalignment(modeLabel, HPos.RIGHT);
GridPane.setConstraints(modeLabel, 0, ++row);
gridPane.getChildren().add(modeLabel);
GridPane.setHgrow(getModeTextField(), Priority.ALWAYS);
GridPane.setConstraints(getModeTextField(), 1, row);
gridPane.getChildren().add(getModeTextField());
Label frequencyLabel = new Label("Frequency");
GridPane.setHalignment(frequencyLabel, HPos.RIGHT);
GridPane.setConstraints(frequencyLabel, 0, ++row);
gridPane.getChildren().add(frequencyLabel);
GridPane.setHgrow(getFrequencyTextField(), Priority.ALWAYS);
GridPane.setConstraints(getFrequencyTextField(), 1, row);
gridPane.getChildren().add(getFrequencyTextField());
Separator separator = new Separator(Orientation.HORIZONTAL);
GridPane.setConstraints(separator, 0, ++row, 2, 1);
gridPane.getChildren().add(separator);
Label createLabel = new Label("Create Channel Configuration");
GridPane.setConstraints(createLabel, 1, ++row);
gridPane.getChildren().add(createLabel);
Label systemLabel = new Label("System");
GridPane.setHalignment(systemLabel, HPos.RIGHT);
GridPane.setConstraints(systemLabel, 0, ++row);
gridPane.getChildren().add(systemLabel);
GridPane.setHgrow(getSystemTextField(), Priority.ALWAYS);
GridPane.setConstraints(getSystemTextField(), 1, row);
gridPane.getChildren().add(getSystemTextField());
Label siteLabel = new Label("Site");
GridPane.setHalignment(siteLabel, HPos.RIGHT);
GridPane.setConstraints(siteLabel, 0, ++row);
gridPane.getChildren().add(siteLabel);
GridPane.setHgrow(getSiteTextField(), Priority.ALWAYS);
GridPane.setConstraints(getSiteTextField(), 1, row);
gridPane.getChildren().add(getSiteTextField());
Label nameLabel = new Label("Name");
GridPane.setHalignment(nameLabel, HPos.RIGHT);
GridPane.setConstraints(nameLabel, 0, ++row);
gridPane.getChildren().add(nameLabel);
GridPane.setHgrow(getNameTextField(), Priority.ALWAYS);
GridPane.setConstraints(getNameTextField(), 1, row);
gridPane.getChildren().add(getNameTextField());
Label decoderLabel = new Label("Decoder");
GridPane.setHalignment(decoderLabel, HPos.RIGHT);
GridPane.setConstraints(decoderLabel, 0, ++row);
gridPane.getChildren().add(decoderLabel);
GridPane.setHgrow(getDecoderTextField(), Priority.ALWAYS);
GridPane.setConstraints(getDecoderTextField(), 1, row);
gridPane.getChildren().add(getDecoderTextField());
GridPane.setConstraints(getShowCreatedChannelCheckBox(), 1, ++row);
gridPane.getChildren().add(getShowCreatedChannelCheckBox());
GridPane.setHgrow(getCreateButton(), Priority.ALWAYS);
GridPane.setMargin(getCreateButton(), new Insets(10,0,0,0));
GridPane.setConstraints(getCreateButton(), 1, ++row);
gridPane.getChildren().add(getCreateButton());
GridPane.setConstraints(getChannelCreatedLabel(), 1, ++row);
gridPane.getChildren().add(getChannelCreatedLabel());
getChildren().add(gridPane);
}
public void setItem(Frequency item, Category category, SubCategory subCategory)
{
mModeDecoderType = ModeDecoderType.UNKNOWN;
if(item != null)
{
mFrequency = (long)(item.getDownlink() * 1E6);
getAlphaTagTextField().setText(item.getAlphaTag());
getFrequencyTextField().setText(FREQUENCY_FORMATTER.format(item.getDownlink()));
getToneTextField().setText(item.getTone());
updateMode(item.getMode());
getSystemTextField().setText(category != null ? category.getName() : null);
getSiteTextField().setText(subCategory != null ? subCategory.getName() : null);
getNameTextField().setText(item.getAlphaTag());
}
else
{
mFrequency = 0;
getAlphaTagTextField().setText(null);
getFrequencyTextField().setText(null);
getModeTextField().setText(null);
getToneTextField().setText(null);
getSystemTextField().setText(null);
getSiteTextField().setText(null);
getNameTextField().setText(null);
getSystemTextField().setDisable(true);
getSiteTextField().setDisable(true);
getNameTextField().setDisable(true);
getCreateButton().setDisable(true);
getShowCreatedChannelCheckBox().setDisable(true);
}
}
/**
* Updates the mode text field and related controls to indicate if a channel configuration can be created.
*/
private void updateMode(String modeId)
{
if(modeId != null)
{
ThreadPool.SCHEDULED.execute(() -> {
Integer parsed = null;
try
{
parsed = Integer.parseInt(modeId);
}
catch(Exception e)
{
//Do nothing, we couldn't parse the value
}
if(parsed != null)
{
try
{
Mode mode = mRadioReference.getService().getMode(parsed);
mModeDecoderType = ModeDecoderType.get(mode);
Platform.runLater(() -> {
getModeTextField().setText(mode.getName());
boolean disable = !mModeDecoderType.hasDecoderType();
getSystemTextField().setDisable(disable);
getSiteTextField().setDisable(disable);
getNameTextField().setDisable(disable);
getCreateButton().setDisable(disable);
getShowCreatedChannelCheckBox().setDisable(disable);
if(mModeDecoderType.hasDecoderType())
{
getDecoderTextField().setText(mModeDecoderType.getDecoderType().getDisplayString());
}
else
{
getDecoderTextField().setText(mode.getName() + " - Not Supported");
}
});
}
catch(Throwable t)
{
mLog.error("Error retrieving mode from radio reference", t);
}
}
});
}
else
{
getModeTextField().setText(null);
}
}
private TextField getAlphaTagTextField()
{
if(mAlphaTagTextField == null)
{
mAlphaTagTextField = new TextField();
mAlphaTagTextField.setMaxWidth(Double.MAX_VALUE);
mAlphaTagTextField.setDisable(true);
}
return mAlphaTagTextField;
}
private TextField getFrequencyTextField()
{
if(mFrequencyTextField == null)
{
mFrequencyTextField = new TextField();
mFrequencyTextField.setMaxWidth(Double.MAX_VALUE);
mFrequencyTextField.setDisable(true);
}
return mFrequencyTextField;
}
private TextField getModeTextField()
{
if(mModeTextField == null)
{
mModeTextField = new TextField();
mModeTextField.setMaxWidth(Double.MAX_VALUE);
mModeTextField.setDisable(true);
}
return mModeTextField;
}
private TextField getToneTextField()
{
if(mToneTextField == null)
{
mToneTextField = new TextField();
mToneTextField.setMaxWidth(Double.MAX_VALUE);
mToneTextField.setDisable(true);
}
return mToneTextField;
}
private CheckBox getShowCreatedChannelCheckBox()
{
if(mShowCreatedChannelCheckBox == null)
{
boolean show = mUserPreferences.getRadioReferencePreference().getShowChannelEditor(mLevel);
mShowCreatedChannelCheckBox = new CheckBox("View Channel Editor After Create");
mShowCreatedChannelCheckBox.setDisable(true);
mShowCreatedChannelCheckBox.selectedProperty().set(show);
mShowCreatedChannelCheckBox.selectedProperty()
.addListener((observable, oldValue, newValue) -> mUserPreferences.getRadioReferencePreference()
.setShowChannelEditor(newValue, mLevel));
}
return mShowCreatedChannelCheckBox;
}
private Label getChannelCreatedLabel()
{
if(mChannelCreatedLabel == null)
{
mChannelCreatedLabel = new Label("Channel Created Successfully");
mChannelCreatedLabel.setDisable(true);
mChannelCreatedLabel.setOpacity(0);
}
return mChannelCreatedLabel;
}
private Button getCreateButton()
{
if(mCreateButton == null)
{
mCreateButton = new Button("Create");
mCreateButton.setDisable(true);
mCreateButton.setOnAction(event -> {
Channel channel = createChannel(mModeDecoderType, mFrequency, getSystemTextField().getText(),
getSiteTextField().getText(), getNameTextField().getText());
if(channel != null)
{
getCreateButton().setDisable(true);
mPlaylistManager.getChannelModel().addChannel(channel);
if(getShowCreatedChannelCheckBox().selectedProperty().get())
{
MyEventBus.getEventBus().post(new ViewChannelRequest(channel));
}
else
{
getChannelCreatedLabel().setOpacity(1.0);
FadeTransition transition = new FadeTransition(Duration.seconds(2), getChannelCreatedLabel());
transition.setDelay(Duration.seconds(1));
transition.setToValue(0.0);
transition.play();
}
}
});
}
return mCreateButton;
}
/**
* Creates a channel configuration from the supplied values
* @param modeDecoderType indicating the decoder type
* @param frequency value
* @param system value
* @param site value
* @param name value
* @return configured channel or null
*/
private Channel createChannel(ModeDecoderType modeDecoderType, long frequency, String system, String site, String name)
{
if(modeDecoderType.hasDecoderType())
{
Channel channel = new Channel();
channel.setSystem(system);
channel.setSite(site);
channel.setName(name);
SourceConfigTuner sourceConfigTuner = new SourceConfigTuner();
sourceConfigTuner.setFrequency(frequency);
channel.setSourceConfiguration(sourceConfigTuner);
DecodeConfiguration decodeConfiguration = DecoderFactory.getDecodeConfiguration(modeDecoderType.getDecoderType());
if(decodeConfiguration instanceof DecodeConfigNBFM && modeDecoderType == ModeDecoderType.FM)
{
((DecodeConfigNBFM)decodeConfiguration).setBandwidth(DecodeConfigNBFM.Bandwidth.BW_25_0);
}
else if(decodeConfiguration instanceof DecodeConfigP25Phase1)
{
((DecodeConfigP25Phase1)decodeConfiguration).setModulation(P25P1Decoder.Modulation.C4FM);
}
channel.setDecodeConfiguration(decodeConfiguration);
return channel;
}
else
{
mLog.warn("Can't create channel configuration for [" + modeDecoderType.name() + "] no supported decoder type");
}
return null;
}
private TextField getSystemTextField()
{
if(mSystemTextField == null)
{
mSystemTextField = new TextField();
mSystemTextField.setMaxWidth(Double.MAX_VALUE);
mSystemTextField.setDisable(true);
}
return mSystemTextField;
}
private TextField getSiteTextField()
{
if(mSiteTextField == null)
{
mSiteTextField = new TextField();
mSiteTextField.setMaxWidth(Double.MAX_VALUE);
mSiteTextField.setDisable(true);
}
return mSiteTextField;
}
private TextField getNameTextField()
{
if(mNameTextField == null)
{
mNameTextField = new TextField();
mNameTextField.setMaxWidth(Double.MAX_VALUE);
mNameTextField.setDisable(true);
}
return mNameTextField;
}
private TextField getDecoderTextField()
{
if(mDecoderTextField == null)
{
mDecoderTextField = new TextField();
mDecoderTextField.setMaxWidth(Double.MAX_VALUE);
mDecoderTextField.setDisable(true);
}
return mDecoderTextField;
}
}

View File

@@ -0,0 +1,22 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.radioreference;
public enum Level {NATIONAL, STATE, COUNTY};

View File

@@ -0,0 +1,395 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.radioreference;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.RadioReferenceException;
import io.github.dsheirer.rrapi.response.Fault;
import io.github.dsheirer.rrapi.type.AuthorizationInformation;
import io.github.dsheirer.service.radioreference.RadioReference;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Dialog;
import javafx.scene.control.DialogPane;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.paint.Color;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.ConnectException;
/**
* Login dialog for radioreference.com with support for testing connection to the service.
*/
public class LoginDialog extends Dialog<AuthorizationInformation>
{
private final static Logger mLog = LoggerFactory.getLogger(LoginDialog.class);
private UserPreferences mUserPreferences;
private TextField mUserNameText;
private PasswordField mPasswordField;
private TextField mPasswordText;
private GridPane mGridPane;
private CheckBox mShowPasswordCheckBox;
private CheckBox mStoreLoginCheckBox;
private Button mTestConnectionButton;
private IconNode mTestPassIcon;
private IconNode mTestFailIcon;
/**
* Constructs an instance.
* @param userPreferences for accessing stored user credentials and preferences
*/
public LoginDialog(UserPreferences userPreferences)
{
mUserPreferences = userPreferences;
setTitle("Radio Reference Login");
final DialogPane dialogPane = getDialogPane();
dialogPane.setContent(getGridPane());
dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
setResultConverter(dialogButton -> {
ButtonBar.ButtonData data = dialogButton == null ? null : dialogButton.getButtonData();
if(data == null)
{
return null;
}
else
{
return getAuthorizationInformation();
}
});
}
private AuthorizationInformation getAuthorizationInformation()
{
boolean store = getStoreLoginCheckBox().isSelected();
String username = getUserNameText().getText();
String password = getPasswordField().isVisible() ? getPasswordField().getText() : getPasswordText().getText();
if(store)
{
mUserPreferences.getRadioReferencePreference().setStoreCredentials(getStoreLoginCheckBox().isSelected());
mUserPreferences.getRadioReferencePreference().setUserName(username);
mUserPreferences.getRadioReferencePreference().setPassword(password);
}
else
{
mUserPreferences.getRadioReferencePreference().removeStoredCredentials();
}
return RadioReference.getAuthorizatonInformation(username, password);
}
private GridPane getGridPane()
{
if(mGridPane == null)
{
mGridPane = new GridPane();
mGridPane.setPadding(new Insets(10,10,5,10));
mGridPane.setHgap(5.0);
mGridPane.setVgap(5.0);
Label userNameLabel = new Label("User Name:");
GridPane.setHalignment(userNameLabel, HPos.RIGHT);
GridPane.setConstraints(userNameLabel, 0, 0);
mGridPane.getChildren().add(userNameLabel);
GridPane.setHgrow(getUserNameText(), Priority.ALWAYS);
GridPane.setConstraints(getUserNameText(), 1, 0);
mGridPane.getChildren().add(getUserNameText());
Label passwordLabel = new Label("Password:");
GridPane.setHalignment(passwordLabel, HPos.RIGHT);
GridPane.setConstraints(passwordLabel, 0, 1);
mGridPane.getChildren().add(passwordLabel);
boolean show = mUserPreferences.getRadioReferencePreference().getShowPassword();
getPasswordField().setVisible(!show);
getPasswordText().setVisible(show);
GridPane.setHgrow(getPasswordText(), Priority.ALWAYS);
GridPane.setConstraints(getPasswordText(), 1, 1);
mGridPane.getChildren().add(getPasswordText());
GridPane.setHgrow(getPasswordField(), Priority.ALWAYS);
GridPane.setConstraints(getPasswordField(), 1, 1);
mGridPane.getChildren().add(getPasswordField());
GridPane.setConstraints(getShowPasswordCheckBox(), 2, 1);
mGridPane.getChildren().add(getShowPasswordCheckBox());
GridPane.setConstraints(getStoreLoginCheckBox(), 1, 2);
mGridPane.getChildren().add(getStoreLoginCheckBox());
GridPane.setHalignment(getTestConnectionButton(), HPos.CENTER);
GridPane.setConstraints(getTestConnectionButton(), 1, 4);
mGridPane.getChildren().add((getTestConnectionButton()));
getTestPassIcon().setVisible(false);
GridPane.setConstraints(getTestPassIcon(), 2, 4);
mGridPane.getChildren().add(getTestPassIcon());
getTestFailIcon().setVisible(false);
GridPane.setConstraints(getTestFailIcon(), 2, 4);
mGridPane.getChildren().add(getTestFailIcon());
}
return mGridPane;
}
private TextField getUserNameText()
{
if(mUserNameText == null)
{
mUserNameText = new TextField();
mUserNameText.setMaxWidth(Double.MAX_VALUE);
String userName = mUserPreferences.getRadioReferencePreference().getUserName();
mUserNameText.setText(userName);
}
return mUserNameText;
}
private PasswordField getPasswordField()
{
if(mPasswordField == null)
{
mPasswordField = new PasswordField();
mPasswordField.setMaxWidth(Double.MAX_VALUE);
String password = mUserPreferences.getRadioReferencePreference().getPassword();
mPasswordField.setText(password);
}
return mPasswordField;
}
private TextField getPasswordText()
{
if(mPasswordText == null)
{
mPasswordText = new TextField();
mPasswordText.setMaxWidth(Double.MAX_VALUE);
String password = mUserPreferences.getRadioReferencePreference().getPassword();
mPasswordText.setText(password);
}
return mPasswordText;
}
/**
* Show (or mask) the password entry field
*/
private CheckBox getShowPasswordCheckBox()
{
if(mShowPasswordCheckBox == null)
{
mShowPasswordCheckBox = new CheckBox("Show");
mShowPasswordCheckBox.setSelected(mUserPreferences.getRadioReferencePreference().getShowPassword());
mShowPasswordCheckBox.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
boolean show = mShowPasswordCheckBox.isSelected();
getPasswordText().setVisible(show);
getPasswordField().setVisible(!show);
//Transfer the password from the previously visible entry control
if(show)
{
getPasswordText().setText(getPasswordField().getText());
}
else
{
getPasswordField().setText(getPasswordText().getText());
}
mUserPreferences.getRadioReferencePreference().setShowPassword(show);
}
});
}
return mShowPasswordCheckBox;
}
private CheckBox getStoreLoginCheckBox()
{
if(mStoreLoginCheckBox == null)
{
mStoreLoginCheckBox = new CheckBox("Store Login Credentials");
mStoreLoginCheckBox.setSelected(mUserPreferences.getRadioReferencePreference().isStoreCredentials());
}
return mStoreLoginCheckBox;
}
private Button getTestConnectionButton()
{
if(mTestConnectionButton == null)
{
mTestConnectionButton = new Button("Test Connection");
mTestConnectionButton.setMaxWidth(Double.MAX_VALUE);
mTestConnectionButton.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
getTestConnectionButton().setDisable(true);
getTestPassIcon().setVisible(false);
getTestFailIcon().setVisible(false);
String userName = getUserNameText().getText();
String password = getPasswordField().isVisible() ? getPasswordField().getText() : getPasswordText().getText();
if(userName == null || userName.isEmpty())
{
Alert alert = new Alert(Alert.AlertType.ERROR, "Please provide a user name", ButtonType.OK);
alert.setHeaderText("Invalid User Name");
alert.setTitle("Login Credentials Required");
alert.initOwner(((Node)getTestConnectionButton()).getScene().getWindow());
alert.showAndWait();
getTestConnectionButton().setDisable(false);
return;
}
if(password == null || password.isEmpty())
{
Alert alert = new Alert(Alert.AlertType.ERROR, "Please provide a password", ButtonType.OK);
alert.setHeaderText("Invalid Password");
alert.setTitle("Login Credentials Required");
alert.initOwner(((Node)getTestConnectionButton()).getScene().getWindow());
alert.showAndWait();
getTestConnectionButton().setDisable(false);
return;
}
Platform.runLater(new Runnable()
{
@Override
public void run()
{
boolean success = false;
try
{
success = RadioReference.testConnection(userName, password);
}
catch(RadioReferenceException rre)
{
if(rre.getCause() instanceof ConnectException)
{
Alert alert = new Alert(Alert.AlertType.ERROR, "Please check network or radio reference availability", ButtonType.OK);
alert.setHeaderText("No Network Connection");
alert.setTitle("Test Failed");
alert.initOwner(((Node)getTestConnectionButton()).getScene().getWindow());
alert.showAndWait();
mLog.error("No network connection to radioreference.com");
}
else if(rre.hasFault())
{
Fault fault = rre.getFault();
if(fault.getFaultCode() != null && fault.getFaultCode().contentEquals("AUTH"))
{
Alert alert = new Alert(Alert.AlertType.ERROR, "Please verify username and password", ButtonType.OK);
alert.setHeaderText("Login Failed");
alert.setTitle("Test Failed");
alert.initOwner(((Node)getTestConnectionButton()).getScene().getWindow());
alert.showAndWait();
mLog.error("Login failed. Invalid username or password. Can't login to radioreference.com");
}
else
{
Alert alert = new Alert(Alert.AlertType.ERROR, fault.getFaultString(), ButtonType.OK);
alert.setHeaderText(fault.getFaultCode());
alert.setTitle("Test Failed");
alert.initOwner(((Node)getTestConnectionButton()).getScene().getWindow());
alert.showAndWait();
mLog.error("Test failed. Fault [" + fault.toString() + "] Can't login to radioreference.com");
}
}
else
{
Alert alert = new Alert(Alert.AlertType.ERROR, "Error: " + rre.getMessage(), ButtonType.OK);
alert.setHeaderText("Unknown Error");
alert.setTitle("Test Failed");
alert.initOwner(((Node)getTestConnectionButton()).getScene().getWindow());
alert.showAndWait();
mLog.error("Error testing connection to radioreference.com", rre);
}
}
getTestPassIcon().setVisible(success);
getTestFailIcon().setVisible(!success);
getTestConnectionButton().setDisable(false);
}
});
}
});
}
return mTestConnectionButton;
}
private IconNode getTestPassIcon()
{
if(mTestPassIcon == null)
{
mTestPassIcon = new IconNode(FontAwesome.CHECK);
mTestPassIcon.setIconSize(32);
mTestPassIcon.setFill(Color.GREEN);
}
return mTestPassIcon;
}
private IconNode getTestFailIcon()
{
if(mTestFailIcon == null)
{
mTestFailIcon = new IconNode(FontAwesome.TIMES);
mTestFailIcon.setIconSize(32);
mTestFailIcon.setFill(Color.RED);
}
return mTestFailIcon;
}
}

View File

@@ -0,0 +1,93 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.radioreference;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.rrapi.type.Mode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Enumeration of radio reference modes to corresponding sdrtrunk decoder type
*/
public enum ModeDecoderType
{
AM("am", DecoderType.AM),
APCO25("p25", DecoderType.P25_PHASE1),
DMR("dmr", null),
DSTAR("d-star", null),
FM("fm", DecoderType.NBFM),
FMN("fmn", DecoderType.NBFM),
NXDN("nxdn", null),
TELM("telm", null),
USB("usb", null),
LSB("lsb", null),
YAESU_SYSTEM_FUSION("ysf", null),
UNKNOWN("UNKNOWN", null);
private String mValue;
private DecoderType mDecoderType;
ModeDecoderType(String value, DecoderType decoderType)
{
mValue = value;
mDecoderType = decoderType;
}
private static final Logger mLog = LoggerFactory.getLogger(ModeDecoderType.class);
public String getValue()
{
return mValue;
}
public DecoderType getDecoderType()
{
return mDecoderType;
}
public boolean hasDecoderType()
{
return mDecoderType != null;
}
/**
* Lookup the entry that matches the mode
* @param mode to match
* @return matching entry or UNKNOWN
*/
public static ModeDecoderType get(Mode mode)
{
if(mode != null)
{
for(ModeDecoderType modeDecoderType : ModeDecoderType.values())
{
if(modeDecoderType.getValue().contentEquals(mode.getName().toLowerCase()))
{
return modeDecoderType;
}
}
mLog.warn("Unrecognized Radio Reference Mode [" + mode.getName() + " ID:" + mode.getModeId() + "]");
}
return UNKNOWN;
}
}

View File

@@ -0,0 +1,366 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.radioreference;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.identifier.talkgroup.LTRTalkgroup;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.identifier.talkgroup.UnknownTalkgroupIdentifier;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.module.decode.mpt1327.identifier.MPT1327Talkgroup;
import io.github.dsheirer.module.decode.p25.identifier.talkgroup.APCO25Talkgroup;
import io.github.dsheirer.module.decode.passport.identifier.PassportTalkgroup;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.rrapi.type.Flavor;
import io.github.dsheirer.rrapi.type.Site;
import io.github.dsheirer.rrapi.type.System;
import io.github.dsheirer.rrapi.type.Tag;
import io.github.dsheirer.rrapi.type.Talkgroup;
import io.github.dsheirer.rrapi.type.Type;
import io.github.dsheirer.rrapi.type.Voice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Utility class for decoding type, flavor and voice for systems and formatting talkgroups according to user preferences.
*/
public class RadioReferenceDecoder
{
private static final Logger mLog = LoggerFactory.getLogger(RadioReferenceDecoder.class);
private UserPreferences mUserPreferences;
private Map<Integer,Flavor> mFlavorMap;
private Map<Integer,Tag> mTagMap;
private Map<Integer,Type> mTypeMap;
private Map<Integer,Voice> mVoiceMap;
/**
* Constructs an instance
* @param userPreferences for settings lookup
* @param typeMap from radio reference
* @param flavorMap from radio reference
* @param voiceMap from radio reference
* @param tagMap from radio reference
*/
public RadioReferenceDecoder(UserPreferences userPreferences, Map<Integer,Type> typeMap,
Map<Integer,Flavor> flavorMap, Map<Integer,Voice> voiceMap, Map<Integer,Tag> tagMap)
{
mUserPreferences = userPreferences;
mTypeMap = typeMap;
mFlavorMap = flavorMap;
mVoiceMap = voiceMap;
mTagMap = tagMap;
}
/**
* Converts radio reference formatted talkgroup values to sdrtrunk decimal format
* @param talkgroup to convert
* @param system to identify the protocol
* @return sdrtrunk formatted decimal value
*/
public int getTalkgroupValue(Talkgroup talkgroup, System system)
{
Protocol protocol = getProtocol(system);
switch(protocol)
{
case LTR:
int value = talkgroup.getDecimalValue();
int area = (value >= 100000 ? 1 : 0);
int home = ((value % 100000) / 1000);
int group = (value % 1000);
return LTRTalkgroup.create(area, home, group);
case MPT1327:
int mptValue = talkgroup.getDecimalValue();
int prefix = (mptValue / 10000);
int ident = (mptValue % 10000);
return MPT1327Talkgroup.encode(prefix, ident);
default:
return talkgroup.getDecimalValue();
}
}
/**
* Creates a talkgroup identifier for the talkgroup using the protocol for the system
* @param talkgroup with value
* @param system with protocol
* @return identifier
*/
public TalkgroupIdentifier getIdentifier(Talkgroup talkgroup, System system)
{
Protocol protocol = getProtocol(system);
int value = getTalkgroupValue(talkgroup, system);
switch(protocol)
{
case APCO25:
return APCO25Talkgroup.create(value);
case LTR:
return LTRTalkgroup.create(value);
case MPT1327:
return MPT1327Talkgroup.createTo(value);
case PASSPORT:
return PassportTalkgroup.create(value);
default:
return UnknownTalkgroupIdentifier.create(value);
}
}
/**
* Creates a talkgroup alias identifier for the specified radio reference talkgroup and system protocol
* @param talkgroup to alias
* @param system to identify the protocol
* @return aliased talkgroup
*/
public io.github.dsheirer.alias.id.talkgroup.Talkgroup getTalkgroupAliasId(Talkgroup talkgroup, System system)
{
Protocol protocol = getProtocol(system);
int value = getTalkgroupValue(talkgroup, system);
return new io.github.dsheirer.alias.id.talkgroup.Talkgroup(protocol, value);
}
/**
* Creates an alias for the specified radio reference talkgroup and a talkgroup alias id for the value.
* @param talkgroup to alias
* @param system to identify the protocol
* @param aliasList for the alias
* @param group for the alias (optional null)
* @return alias
*/
public Alias createAlias(Talkgroup talkgroup, System system, String aliasList, String group)
{
Alias alias = new Alias(talkgroup.getAlphaTag());
alias.setAliasListName(aliasList);
alias.setGroup(group);
alias.addAliasID(getTalkgroupAliasId(talkgroup, system));
return alias;
}
/**
* Formats the talkgroup value according to the system's protocol and user preferences
*/
public String format(Talkgroup talkgroup, System system)
{
return mUserPreferences.getTalkgroupFormatPreference().format(getIdentifier(talkgroup, system));
}
/**
* Looks up the tags for the talkgroup.
*
* Note: even though the talkgroup has an array of tags, each tag only has a tag value and not a tag description,
* so we have to replace the tag with a lookup tag.
* @param talkgroup that optionally contains a tags array
* @return tags identified for the talkgroup
*/
public List<Tag> getTags(Talkgroup talkgroup)
{
List<Tag> tags = new ArrayList<>();
if(talkgroup.getTags() != null)
{
for(Tag tag: talkgroup.getTags())
{
tags.add(mTagMap.get(tag.getTagId()));
}
}
return tags;
}
/**
* Provides the radio reference Type for the system
*/
public Type getType(System system)
{
return mTypeMap.get(system.getTypeId());
}
/**
* Provides the radio reference Flavor for the system
*/
public Flavor getFlavor(System system)
{
return mFlavorMap.get(system.getFlavorId());
}
/**
* Provides the radio reference voice/traffic channel protocol for the system
*/
public Voice getVoice(System system)
{
return mVoiceMap.get(system.getVoiceId());
}
/**
* Indicates if the system has a protocol supported by sdrtrunk
*/
public boolean hasSupportedProtocol(System system)
{
return getProtocol(system) != Protocol.UNKNOWN;
}
/**
* Indicates if this is an LTR (standard, net or passport) system
*/
public boolean isLTR(System system)
{
return getType(system) != null && getType(system).getName().toLowerCase().contentEquals("ltr");
}
/**
* Indicates if the system is a hybrid Motorola system using a legacy Type II control channel with
* APCO25 CAI voice channels.
*/
public boolean isHybridMotorolaP25(System system)
{
return getType(system) != null && getType(system).getName().contentEquals("Motorola") &&
getProtocol(system) == Protocol.APCO25;
}
/**
* Indicates if the site is a simulcast site.
* @param site to inspect
* @return true if the site employs LSM modulation
*/
public boolean isLSM(Site site)
{
return site != null && site.getModulation() != null && site.getModulation().contentEquals("LSM");
}
/**
* Identifies the sdrtrunk protocol used by the system.
* @param system to identify
* @return protocol or UNKNOWN if the protocol is not supported by sdrtrunk.
*/
public Protocol getProtocol(System system)
{
if(system == null)
{
return Protocol.UNKNOWN;
}
Type type = getType(system);
Flavor flavor = getFlavor(system);
Voice voice = getVoice(system);
switch(type.getName())
{
case "LTR":
if(flavor.getName().contentEquals("Standard") || flavor.getName().contentEquals("Net"))
{
return Protocol.LTR;
}
else if(flavor.getName().contentEquals("Passport"))
{
return Protocol.PASSPORT;
}
break;
case "MPT-1327":
return Protocol.MPT1327;
case "Project 25":
return Protocol.APCO25;
case "Motorola":
if(voice.getName().contentEquals("Analog and APCO-25 Common Air Interface") ||
voice.getName().contentEquals("APCO-25 Common Air Interface Exclusive"))
{
return Protocol.APCO25;
}
break;
case "DMR":
case "NXDN":
case "EDACS":
case "TETRA":
case "Midland CMS":
case "OpenSky":
case "iDEN":
case "SmarTrunk":
case "Other":
default:
}
return Protocol.UNKNOWN;
}
/**
* Decoder type for the specified system, if supported.
* @param system requiring a decoder type
* @return decoder type or null.
*/
public DecoderType getDecoderType(System system)
{
Type type = getType(system);
Flavor flavor = getFlavor(system);
Voice voice = getVoice(system);
switch(type.getName())
{
case "LTR":
if(flavor.getName().contentEquals("Net"))
{
return DecoderType.LTR_NET;
}
else if(flavor.getName().contentEquals("Passport"))
{
return DecoderType.PASSPORT;
}
else
{
return DecoderType.LTR;
}
case "MPT-1327":
return DecoderType.MPT1327;
case "Project 25":
if(flavor.getName().contentEquals("Phase II"))
{
return DecoderType.P25_PHASE2;
}
else if(flavor.getName().contentEquals("Phase I"))
{
return DecoderType.P25_PHASE1;
}
break;
case "Motorola":
if(voice.getName().contentEquals("Analog and APCO-25 Common Air Interface") ||
voice.getName().contentEquals("APCO-25 Common Air Interface Exclusive"))
{
return DecoderType.P25_PHASE1;
}
break;
case "DMR":
case "NXDN":
case "EDACS":
case "TETRA":
case "Midland CMS":
case "OpenSky":
case "iDEN":
case "SmarTrunk":
case "Other":
default:
}
return null;
}
}

View File

@@ -0,0 +1,779 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.radioreference;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.RadioReferenceException;
import io.github.dsheirer.rrapi.type.AuthorizationInformation;
import io.github.dsheirer.rrapi.type.Country;
import io.github.dsheirer.rrapi.type.CountryInfo;
import io.github.dsheirer.rrapi.type.County;
import io.github.dsheirer.rrapi.type.CountyInfo;
import io.github.dsheirer.rrapi.type.State;
import io.github.dsheirer.rrapi.type.StateInfo;
import io.github.dsheirer.service.radioreference.RadioReference;
import io.github.dsheirer.util.ThreadPool;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.util.StringConverter;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.function.Consumer;
public class RadioReferenceEditor extends BorderPane implements Consumer<AuthorizationInformation>
{
private static final Logger mLog = LoggerFactory.getLogger(RadioReferenceEditor.class);
private UserPreferences mUserPreferences;
private RadioReference mRadioReference;
private PlaylistManager mPlaylistManager;
private VBox mTopBox;
private HBox mCredentialsBox;
private TextField mUserNameText;
private TextField mAccountExpiresText;
private IconNode mTestPassIcon;
private IconNode mTestFailIcon;
private Button mLoginButton;
private GridPane mComboBoxPane;
private ComboBox<Country> mCountryComboBox;
private ComboBox<State> mStateComboBox;
private ComboBox<County> mCountyComboBox;
private AgencyEditor mNationalAgencyEditor;
private AgencyEditor mStateAgencyEditor;
private AgencyEditor mCountyAgencyEditor;
private SystemEditor mStateSystemEditor;
private SystemEditor mCountySystemEditor;
private TabPane mTabPane;
private Tab mNationalAgencyTab;
private Tab mStateAgencyTab;
private Tab mCountyAgencyTab;
private Tab mStateSystemTab;
private Tab mCountySystemTab;
public RadioReferenceEditor(UserPreferences userPreferences, PlaylistManager playlistManager)
{
mUserPreferences = userPreferences;
mRadioReference = playlistManager.getRadioReference();
mPlaylistManager = playlistManager;
setTop(getTopBox());
setCenter(getTabPane());
login();
if(mRadioReference.availableProperty().get())
{
refreshCountries();
}
//Refresh the countries combo box once we're logged on, if not already
mRadioReference.availableProperty().addListener((observable, oldValue, newValue) -> {
if(newValue)
{
refreshCountries();
}
});
}
private void login()
{
if(mUserPreferences.getRadioReferencePreference().hasStoredCredentials())
{
AuthorizationInformation credentials = mUserPreferences.getRadioReferencePreference().getAuthorizationInformation();
if(credentials != null)
{
ThreadPool.SCHEDULED.execute(() -> accept(credentials));
}
}
}
/**
* Retrieves the list of countries supported by the service and populates the combobox, auto-selecting the
* country that the user last selected.
*/
private void refreshCountries()
{
getCountryComboBox().getItems().clear();
final int preferredCountryId = mUserPreferences.getRadioReferencePreference().getPreferredCountryId();
ThreadPool.SCHEDULED.execute(() -> {
try
{
List<Country> countries = mRadioReference.getService().getCountries();
Platform.runLater(() -> {
getCountryComboBox().getItems().addAll(countries);
if(preferredCountryId >= 0)
{
for(Country country: countries)
{
if(country.getCountryId() == preferredCountryId)
{
getCountryComboBox().getSelectionModel().select(country);
return;
}
}
}
});
}
catch(Throwable t)
{
mLog.error("Error retrieving country list from radio reference", t);
Platform.runLater(() -> new RadioReferenceUnavailableAlert(getCountryComboBox()).showAndWait());
}
});
}
private TabPane getTabPane()
{
if(mTabPane == null)
{
mTabPane = new TabPane();
mTabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
mTabPane.getTabs().addAll(getCountySystemTab(), getStateSystemTab(), getCountyAgencyTab(),
getStateAgencyTab(), getNationalAgencyTab());
}
return mTabPane;
}
private Tab getNationalAgencyTab()
{
if(mNationalAgencyTab == null)
{
mNationalAgencyTab = new Tab("National Agencies");
mNationalAgencyTab.setContent(getNationalAgencyEditor());
getNationalAgencyEditor().agencyCountProperty().addListener((observable, oldValue, newValue) -> {
int count = (newValue != null ? newValue.intValue() : 0);
if(count > 0)
{
getNationalAgencyTab().setText("National Agencies (" + count + ")");
}
else
{
getNationalAgencyTab().setText("National Agencies");
}
});
}
return mNationalAgencyTab;
}
private Tab getStateAgencyTab()
{
if(mStateAgencyTab == null)
{
mStateAgencyTab = new Tab("State Agencies");
mStateAgencyTab.setContent(getStateAgencyEditor());
getStateAgencyEditor().agencyCountProperty().addListener((observable, oldValue, newValue) -> {
int count = (newValue != null ? newValue.intValue() : 0);
if(count > 0)
{
getStateAgencyTab().setText("State Agencies (" + count + ")");
}
else
{
getStateAgencyTab().setText("State Agencies");
}
});
}
return mStateAgencyTab;
}
private Tab getCountyAgencyTab()
{
if(mCountyAgencyTab == null)
{
mCountyAgencyTab = new Tab("County Agencies");
mCountyAgencyTab.setContent(getCountyAgencyEditor());
getCountyAgencyEditor().agencyCountProperty().addListener((observable, oldValue, newValue) -> {
int count = (newValue != null ? newValue.intValue() : 0);
if(count > 0)
{
getCountyAgencyTab().setText("County Agencies (" + count + ")");
}
else
{
getCountyAgencyTab().setText("County Agencies");
}
});
}
return mCountyAgencyTab;
}
private Tab getStateSystemTab()
{
if(mStateSystemTab == null)
{
mStateSystemTab = new Tab("State Trunked Systems");
mStateSystemTab.setContent(getStateSystemEditor());
getStateSystemEditor().systemCountProperty().addListener((observable, oldValue, newValue) -> {
int count = (newValue != null ? newValue.intValue() : 0);
if(count > 0)
{
getStateSystemTab().setText("State Trunked Systems (" + count + ")");
}
else
{
getStateSystemTab().setText("State Trunked Systems");
}
});
}
return mStateSystemTab;
}
private Tab getCountySystemTab()
{
if(mCountySystemTab == null)
{
mCountySystemTab = new Tab("County Trunked Systems");
mCountySystemTab.setContent(getCountySystemEditor());
getCountySystemEditor().systemCountProperty().addListener((observable, oldValue, newValue) -> {
int count = (newValue != null ? newValue.intValue() : 0);
if(count > 0)
{
getCountySystemTab().setText("County Trunked Systems (" + count + ")");
}
else
{
getCountySystemTab().setText("County Trunked Systems");
}
});
}
return mCountySystemTab;
}
private AgencyEditor getNationalAgencyEditor()
{
if(mNationalAgencyEditor == null)
{
mNationalAgencyEditor = new AgencyEditor(mUserPreferences, mRadioReference, mPlaylistManager, Level.NATIONAL);
}
return mNationalAgencyEditor;
}
private AgencyEditor getStateAgencyEditor()
{
if(mStateAgencyEditor == null)
{
mStateAgencyEditor = new AgencyEditor(mUserPreferences, mRadioReference, mPlaylistManager, Level.STATE);
}
return mStateAgencyEditor;
}
private AgencyEditor getCountyAgencyEditor()
{
if(mCountyAgencyEditor == null)
{
mCountyAgencyEditor = new AgencyEditor(mUserPreferences, mRadioReference, mPlaylistManager, Level.COUNTY);
}
return mCountyAgencyEditor;
}
private SystemEditor getStateSystemEditor()
{
if(mStateSystemEditor == null)
{
mStateSystemEditor = new SystemEditor(mUserPreferences, mRadioReference, mPlaylistManager, Level.STATE);
}
return mStateSystemEditor;
}
private SystemEditor getCountySystemEditor()
{
if(mCountySystemEditor == null)
{
mCountySystemEditor = new SystemEditor(mUserPreferences, mRadioReference, mPlaylistManager, Level.COUNTY);
}
return mCountySystemEditor;
}
private VBox getTopBox()
{
if(mTopBox == null)
{
mTopBox = new VBox();
mTopBox.setSpacing(5);
mTopBox.setPadding(new Insets(10,10,10,10));
mTopBox.getChildren().addAll(getCredentialsBox(), getComboBoxPane());
}
return mTopBox;
}
private GridPane getComboBoxPane()
{
if(mComboBoxPane == null)
{
mComboBoxPane = new GridPane();
mComboBoxPane.setHgap(5);
mComboBoxPane.setVgap(2);
ColumnConstraints column1 = new ColumnConstraints();
column1.setPercentWidth(33.33);
ColumnConstraints column2 = new ColumnConstraints();
column2.setPercentWidth(33.33);
ColumnConstraints column3 = new ColumnConstraints();
column3.setPercentWidth(33.33);
mComboBoxPane.getColumnConstraints().addAll(column1, column2, column3);
Label countryLabel = new Label("Country");
GridPane.setConstraints(countryLabel, 0, 0);
mComboBoxPane.getChildren().add(countryLabel);
GridPane.setConstraints(getCountryComboBox(), 0, 1);
GridPane.setHgrow(getCountryComboBox(), Priority.ALWAYS);
mComboBoxPane.getChildren().add(getCountryComboBox());
Label stateLabel = new Label("State");
GridPane.setConstraints(stateLabel, 1, 0);
mComboBoxPane.getChildren().add(stateLabel);
GridPane.setConstraints(getStateComboBox(), 1, 1);
GridPane.setHgrow(getStateComboBox(), Priority.ALWAYS);
mComboBoxPane.getChildren().add(getStateComboBox());
Label countyLabel = new Label("County");
GridPane.setConstraints(countyLabel, 2, 0);
mComboBoxPane.getChildren().add(countyLabel);
GridPane.setConstraints(getCountyComboBox(), 2, 1);
GridPane.setHgrow(getCountyComboBox(), Priority.ALWAYS);
mComboBoxPane.getChildren().add(getCountyComboBox());
}
return mComboBoxPane;
}
private HBox getCredentialsBox()
{
if(mCredentialsBox == null)
{
mCredentialsBox = new HBox();
mCredentialsBox.setAlignment(Pos.CENTER_RIGHT);
mCredentialsBox.setSpacing(5.0);
mCredentialsBox.getChildren().add(new Label("User Name:"));
mCredentialsBox.getChildren().add(getUserNameText());
mCredentialsBox.getChildren().add(new Label("Expires:"));
mCredentialsBox.getChildren().add(getAccountExpiresText());
mCredentialsBox.getChildren().add(getTestFailIcon());
mCredentialsBox.getChildren().add(getTestPassIcon());
mCredentialsBox.getChildren().add(getLoginButton());
}
return mCredentialsBox;
}
private TextField getUserNameText()
{
if(mUserNameText == null)
{
mUserNameText = new TextField();
mUserNameText.textProperty().bind(mRadioReference.userNameProperty());
mUserNameText.setDisable(true);
}
return mUserNameText;
}
private TextField getAccountExpiresText()
{
if(mAccountExpiresText == null)
{
mAccountExpiresText = new TextField();
mAccountExpiresText.textProperty().bind(mRadioReference.accountExpiresProperty());
mAccountExpiresText.setDisable(true);
}
return mAccountExpiresText;
}
private IconNode getTestPassIcon()
{
if(mTestPassIcon == null)
{
mTestPassIcon = new IconNode(FontAwesome.CHECK);
mTestPassIcon.setFill(Color.GREEN);
mTestPassIcon.visibleProperty().bind(mRadioReference.availableProperty());
}
return mTestPassIcon;
}
private IconNode getTestFailIcon()
{
if(mTestFailIcon == null)
{
mTestFailIcon = new IconNode(FontAwesome.TIMES);
mTestFailIcon.setFill(Color.RED);
mTestFailIcon.visibleProperty().bind(mRadioReference.availableProperty().not());
}
return mTestFailIcon;
}
private Button getLoginButton()
{
if(mLoginButton == null)
{
mLoginButton = new Button("Login");
IconNode configureIcon = new IconNode(FontAwesome.COG);
configureIcon.setFill(Color.GRAY);
mLoginButton.setGraphic(configureIcon);
mLoginButton.setOnAction(event -> {
new LoginDialog(mUserPreferences).showAndWait().ifPresent(this);
});
}
return mLoginButton;
}
private void clearEditors(Level level)
{
switch(level)
{
case NATIONAL:
getNationalAgencyEditor().clear();
getStateAgencyEditor().clear();
getCountyAgencyEditor().clear();
getStateSystemEditor().clear();
getCountySystemEditor().clear();
break;
case STATE:
getStateAgencyEditor().clear();
getCountyAgencyEditor().clear();
getStateSystemEditor().clear();
getCountySystemEditor().clear();
break;
case COUNTY:
getCountyAgencyEditor().clear();
getCountySystemEditor().clear();
break;
}
}
private void setCountry(Country country)
{
clearEditors(Level.NATIONAL);
getStateComboBox().getItems().clear();
getCountyComboBox().getItems().clear();
if(country != null)
{
int preferredStateId = mUserPreferences.getRadioReferencePreference().getPreferredStateId();
ThreadPool.SCHEDULED.execute(() -> {
try
{
final CountryInfo countryInfo = mRadioReference.getService().getCountryInfo(country.getCountryId());
Platform.runLater(() -> {
getNationalAgencyEditor().setAgencies(countryInfo.getAgencies());
getStateComboBox().getItems().addAll(countryInfo.getStates());
for(State state: mStateComboBox.getItems())
{
if(state.getStateId() == preferredStateId)
{
getStateComboBox().getSelectionModel().select(state);
return;
}
}
});
}
catch(RadioReferenceException rre)
{
mLog.error("Error retrieving country information from radio reference - " + rre.getMessage());
Platform.runLater(() -> new RadioReferenceUnavailableAlert(getCountryComboBox()).showAndWait());
}
});
}
}
private ComboBox<Country> getCountryComboBox()
{
if(mCountryComboBox == null)
{
mCountryComboBox = new ComboBox<>();
mCountryComboBox.setConverter(new CountryStringConverter());
mCountryComboBox.setMaxWidth(Double.MAX_VALUE);
mCountryComboBox.setOnAction(event -> {
Country selected = mCountryComboBox.getValue();
setCountry(selected);
if(selected != null)
{
mUserPreferences.getRadioReferencePreference().setPreferredCountryId(selected.getCountryId());
}
});
}
return mCountryComboBox;
}
private void setState(State state)
{
clearEditors(Level.STATE);
getCountyComboBox().getItems().clear();
int preferredCountyId = mUserPreferences.getRadioReferencePreference().getPreferredCountyId();
if(state != null)
{
ThreadPool.SCHEDULED.execute(() -> {
try
{
final StateInfo stateInfo = mRadioReference.getService().getStateInfo(state.getStateId());
Platform.runLater(() -> {
getStateAgencyEditor().setAgencies(stateInfo.getAgencies());
getStateSystemEditor().setSystems(stateInfo.getSystems());
getCountyComboBox().getItems().addAll(stateInfo.getCounties());
for(County county: mCountyComboBox.getItems())
{
if(county.getCountyId() == preferredCountyId)
{
getCountyComboBox().getSelectionModel().select(county);
return;
}
}
});
}
catch(RadioReferenceException rre)
{
mLog.error("Error retrieving state information from radio reference - " + rre.getMessage());
Platform.runLater(() -> new RadioReferenceUnavailableAlert(getStateComboBox()).showAndWait());
}
});
}
}
private ComboBox<State> getStateComboBox()
{
if(mStateComboBox == null)
{
mStateComboBox = new ComboBox<>();
mStateComboBox.setConverter(new StateStringConverter());
mStateComboBox.setMaxWidth(Double.MAX_VALUE);
mStateComboBox.setOnAction(event -> {
State selected = mStateComboBox.getValue();
setState(selected);
if(selected != null)
{
mUserPreferences.getRadioReferencePreference().setPreferredStateId(selected.getStateId());
}
});
}
return mStateComboBox;
}
private void setCounty(County county)
{
clearEditors(Level.COUNTY);
if(county != null)
{
ThreadPool.SCHEDULED.execute(() -> {
try
{
final CountyInfo countyInfo = mRadioReference.getService().getCountyInfo(county.getCountyId());
Platform.runLater(() -> {
getCountySystemEditor().setSystems(countyInfo.getSystems());
getCountyAgencyEditor().setAgencies(countyInfo.getAgencies());
});
}
catch(RadioReferenceException rre)
{
mLog.error("Error retrieving county information from radio reference - " + rre.getMessage());
Platform.runLater(() -> new RadioReferenceUnavailableAlert(getCountyComboBox()).showAndWait());
}
});
}
}
private ComboBox<County> getCountyComboBox()
{
if(mCountyComboBox == null)
{
mCountyComboBox = new ComboBox<>();
mCountyComboBox.setConverter(new CountyStringConverter());
mCountyComboBox.setMaxWidth(Double.MAX_VALUE);
mCountyComboBox.setOnAction(event -> {
County selected = mCountyComboBox.getValue();
setCounty(selected);
if(selected != null)
{
mUserPreferences.getRadioReferencePreference().setPreferredCountyId(selected.getCountyId());
}
});
}
return mCountyComboBox;
}
/**
* Consumer interface method for receiving login credentials
*/
@Override
public void accept(AuthorizationInformation authorizationInformation)
{
if(authorizationInformation != null)
{
mRadioReference.setAuthorizationInformation(authorizationInformation);
}
}
public class CountryStringConverter extends StringConverter<Country>
{
@Override
public String toString(Country country)
{
if(country != null)
{
return country.getName();
}
return null;
}
@Override
public Country fromString(String string)
{
if(string != null && !string.isEmpty())
{
for(Country country: mCountryComboBox.getItems())
{
if(country.getName().toLowerCase().contentEquals(string.toLowerCase()))
{
return country;
}
}
}
return null;
}
}
public class StateStringConverter extends StringConverter<State>
{
@Override
public String toString(State state)
{
if(state != null)
{
return state.getName();
}
return null;
}
@Override
public State fromString(String string)
{
if(string != null && !string.isEmpty())
{
for(State state: mStateComboBox.getItems())
{
if(state.getName().toLowerCase().contentEquals(string.toLowerCase()))
{
return state;
}
}
}
return null;
}
}
public class CountyStringConverter extends StringConverter<County>
{
@Override
public String toString(County county)
{
if(county != null)
{
return county.getName();
}
return null;
}
@Override
public County fromString(String string)
{
if(string != null && !string.isEmpty())
{
for(County county: mCountyComboBox.getItems())
{
if(county.getName().toLowerCase().contentEquals(string.toLowerCase()))
{
return county;
}
}
}
return null;
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.radioreference;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
/**
* Popup alert window that informs the user that the radio reference site is currently unavailable
*/
public class RadioReferenceUnavailableAlert extends Alert
{
public RadioReferenceUnavailableAlert(Node ownerNode)
{
super(AlertType.INFORMATION, "Check network connection or see details in sdrtrunk log.", ButtonType.OK);
setTitle("Service Unavailable");
setHeaderText("Radio Reference Is Currently Unavailable");
initOwner(ownerNode.getScene().getWindow());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,410 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.radioreference;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.type.CountyInfo;
import io.github.dsheirer.rrapi.type.Flavor;
import io.github.dsheirer.rrapi.type.Site;
import io.github.dsheirer.rrapi.type.System;
import io.github.dsheirer.rrapi.type.SystemInformation;
import io.github.dsheirer.rrapi.type.Tag;
import io.github.dsheirer.rrapi.type.Talkgroup;
import io.github.dsheirer.rrapi.type.TalkgroupCategory;
import io.github.dsheirer.rrapi.type.Type;
import io.github.dsheirer.rrapi.type.Voice;
import io.github.dsheirer.service.radioreference.RadioReference;
import io.github.dsheirer.util.ThreadPool;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* Radio Reference editor for trunked radio systems
*/
public class SystemEditor extends VBox
{
private static final Logger mLog = LoggerFactory.getLogger(SystemEditor.class);
private UserPreferences mUserPreferences;
private RadioReference mRadioReference;
private PlaylistManager mPlaylistManager;
private Level mLevel;
private ComboBox<System> mSystemComboBox;
private IntegerProperty mSystemCountProperty = new SimpleIntegerProperty();
private TabPane mTabPane;
private Tab mSystemTab;
private Tab mTalkgroupTab;
private SystemSiteSelectionEditor mSystemSiteSelectionEditor;
private SystemTalkgroupSelectionEditor mSystemTalkgroupSelectionEditor;
private RadioReferenceDecoder mRadioReferenceDecoder;
/**
* Constructs an instance
* @param userPreferences for preferences
* @param radioReference to access radio reference
* @param playlistManager
* @param level STATE or COUNTY
*/
public SystemEditor(UserPreferences userPreferences, RadioReference radioReference,
PlaylistManager playlistManager, Level level)
{
mUserPreferences = userPreferences;
mRadioReference = radioReference;
mPlaylistManager = playlistManager;
mLevel = level;
mSystemCountProperty.bind(Bindings.size(getSystemComboBox().getItems()));
setPadding(new Insets(20,10,10,10));
setSpacing(10);
HBox systemBox = new HBox();
HBox.setHgrow(getSystemComboBox(), Priority.ALWAYS);
systemBox.setAlignment(Pos.CENTER_LEFT);
systemBox.setSpacing(5);
systemBox.setMaxWidth(Double.MAX_VALUE);
systemBox.getChildren().addAll(new Label("System"), getSystemComboBox());
VBox.setVgrow(getTabPane(), Priority.ALWAYS);
getChildren().addAll(systemBox, getTabPane());
}
/**
* Observable count of systems in the editor
*/
public IntegerProperty systemCountProperty()
{
return mSystemCountProperty;
}
public void clear()
{
getSystemComboBox().getItems().clear();
getSystemSiteSelectionEditor().clear();
getSystemTalkgroupSelectionEditor().clear();
}
/**
* Sets the list of displayed systems and clears out any existing systems. Auto-selects a system if the user
* has selected that system before.
*
* Note: this should only be invoked on the FX application thread
*
* @param systems to display
*/
public void setSystems(List<System> systems)
{
clear();
if(systems != null && !systems.isEmpty())
{
Collections.sort(systems, new SystemComparator());
getSystemComboBox().getItems().addAll(systems);
int preferredSystemId = mUserPreferences.getRadioReferencePreference().getPreferredSystemId(mLevel);
for(System system: getSystemComboBox().getItems())
{
if(system.getSystemId() == preferredSystemId)
{
getSystemComboBox().getSelectionModel().select(system);
return;
}
}
}
}
private TabPane getTabPane()
{
if(mTabPane == null)
{
mTabPane = new TabPane();
mTabPane.setMaxHeight(Double.MAX_VALUE);
mTabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
mTabPane.getTabs().addAll(getSystemTab(), getTalkgroupTab());
}
return mTabPane;
}
private SystemSiteSelectionEditor getSystemSiteSelectionEditor()
{
if(mSystemSiteSelectionEditor == null)
{
mSystemSiteSelectionEditor = new SystemSiteSelectionEditor(mUserPreferences, mPlaylistManager);
}
return mSystemSiteSelectionEditor;
}
private Tab getSystemTab()
{
if(mSystemTab == null)
{
mSystemTab = new Tab("System View");
mSystemTab.setContent(getSystemSiteSelectionEditor());
}
return mSystemTab;
}
private SystemTalkgroupSelectionEditor getSystemTalkgroupSelectionEditor()
{
if(mSystemTalkgroupSelectionEditor == null)
{
mSystemTalkgroupSelectionEditor = new SystemTalkgroupSelectionEditor(mUserPreferences, mPlaylistManager);
}
return mSystemTalkgroupSelectionEditor;
}
private Tab getTalkgroupTab()
{
if(mTalkgroupTab == null)
{
mTalkgroupTab = new Tab("Talkgroup View");
mTalkgroupTab.setContent(getSystemTalkgroupSelectionEditor());
}
return mTalkgroupTab;
}
private ComboBox<System> getSystemComboBox()
{
if(mSystemComboBox == null)
{
mSystemComboBox = new ComboBox<>();
mSystemComboBox.setMaxWidth(Double.MAX_VALUE);
mSystemComboBox.setCellFactory(param -> new SystemListCell());
mSystemComboBox.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> setSystem(newValue));
}
return mSystemComboBox;
}
private boolean isSupported(System system)
{
return mRadioReferenceDecoder != null && mRadioReferenceDecoder.hasSupportedProtocol(system);
}
private String getType(System system)
{
if(mRadioReferenceDecoder != null)
{
Type type = mRadioReferenceDecoder.getType(system);
Flavor flavor = mRadioReferenceDecoder.getFlavor(system);
if(type != null)
{
if(flavor != null)
{
return type.getName() + " " + flavor.getName();
}
else
{
return type.getName();
}
}
}
return "Unknown";
}
/**
* Sets the system to be displayed in the editor and updates the system and talkgroup view editors
*/
private void setSystem(System system)
{
if(system != null)
{
getSystemSiteSelectionEditor().clearAndSetLoading();
getSystemTalkgroupSelectionEditor().clearAndSetLoading();
mUserPreferences.getRadioReferencePreference().setPreferredSystemId(system.getSystemId(), mLevel);
//Retrieve the radio reference data on a separate thread and then load the editors on the FX thread
ThreadPool.SCHEDULED.execute(() -> {
try
{
if(mRadioReferenceDecoder == null)
{
Map<Integer,Type> typeMap = mRadioReference.getService().getTypesMap();
Map<Integer,Flavor> flavorMap = mRadioReference.getService().getFlavorsMap();
Map<Integer,Voice> voiceMap = mRadioReference.getService().getVoicesMap();
Map<Integer,Tag> tagMap = mRadioReference.getService().getTagsMap();
mRadioReferenceDecoder = new RadioReferenceDecoder(mUserPreferences, typeMap, flavorMap,
voiceMap, tagMap);
}
//Query and load the system view editor first
SystemInformation systemInformation = mRadioReference.getService().getSystemInformation(system.getSystemId());
List<Site> sites = mRadioReference.getService().getSites(system.getSystemId());
//The service api doesn't provide the county name, so we run a separate query to update each value
for(Site site: sites)
{
CountyInfo countyInfo = mRadioReference.getService().getCountyInfo(site.getCountyId());
site.setCountyName(countyInfo.getName());
}
Platform.runLater(() -> getSystemSiteSelectionEditor().setSystem(system, sites, mRadioReferenceDecoder,
systemInformation));
//Query and load the talkgroup view second
List<Talkgroup> talkgroups = mRadioReference.getService().getTalkgroups(system.getSystemId());
List<TalkgroupCategory> categories = mRadioReference.getService().getTalkgroupCategories(system.getSystemId());
Platform.runLater(() -> getSystemTalkgroupSelectionEditor()
.setSystem(system, talkgroups, categories, mRadioReferenceDecoder));
}
catch(Throwable t)
{
mLog.error("Error retrieving system information", t);
//We have to call setSystem() on both editors to clear the loading status spinny icon
Platform.runLater(() -> {
getSystemTalkgroupSelectionEditor().setSystem(null, Collections.emptyList(),
Collections.emptyList(), mRadioReferenceDecoder);
getSystemSiteSelectionEditor().setSystem(null, Collections.emptyList(),
mRadioReferenceDecoder, null);
new RadioReferenceUnavailableAlert(getSystemComboBox()).showAndWait();
});
}
});
}
else
{
getSystemSiteSelectionEditor().clear();
getSystemTalkgroupSelectionEditor().clear();
}
}
public class SystemListCell extends ListCell<System>
{
private HBox mHBox;
private Label mName;
private Label mProtocol;
private Label mIdLabel;
private IconNode mIconNode;
public SystemListCell()
{
mHBox = new HBox();
mHBox.setSpacing(15);
mHBox.setPadding(new Insets(0,30,0,0));
mHBox.setMaxWidth(Double.MAX_VALUE);
mName = new Label();
mName.setMaxWidth(Double.MAX_VALUE);
mName.setAlignment(Pos.CENTER_LEFT);
mProtocol = new Label();
mProtocol.setMaxWidth(Double.MAX_VALUE);
mProtocol.setAlignment(Pos.CENTER_RIGHT);
mProtocol.setContentDisplay(ContentDisplay.RIGHT);
mIdLabel = new Label();
mIdLabel.setAlignment(Pos.CENTER_RIGHT);
mIdLabel.setContentDisplay(ContentDisplay.RIGHT);
HBox.setHgrow(mName, Priority.ALWAYS);
HBox.setHgrow(mProtocol, Priority.ALWAYS);
mHBox.getChildren().addAll(mName, mProtocol, mIdLabel);
}
@Override
protected void updateItem(System item, boolean empty)
{
super.updateItem(item, empty);
setText(null);
if(empty || item == null)
{
setGraphic(null);
mName.setText(null);
mProtocol.setText(null);
mIdLabel.setText(null);
}
else
{
mName.setText(item.getName());
mProtocol.setText(getType(item));
mIdLabel.setText("ID:" + item.getSystemId());
if(isSupported(item))
{
mIconNode = new IconNode(FontAwesome.CHECK);
mIconNode.setFill(Color.GREEN);
}
else
{
mIconNode = new IconNode(FontAwesome.BAN);
mIconNode.setFill(Color.RED);
}
mProtocol.setGraphic(mIconNode);
setGraphic(mHBox);
}
}
}
public class SystemComparator implements Comparator<System>
{
@Override
public int compare(System o1, System o2)
{
if(o1.getName() == null && o2.getName() == null)
{
return 0;
}
else if(o1.getName() == null)
{
return 1;
}
else if(o2.getName() == null)
{
return -1;
}
else
{
return o1.getName().compareTo(o2.getName());
}
}
}
}

View File

@@ -0,0 +1,250 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2019 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.playlist.radioreference;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.type.Flavor;
import io.github.dsheirer.rrapi.type.Site;
import io.github.dsheirer.rrapi.type.System;
import io.github.dsheirer.rrapi.type.SystemInformation;
import io.github.dsheirer.rrapi.type.Type;
import io.github.dsheirer.rrapi.type.Voice;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
/**
* View for a radio reference trunked mobile radio system
*/
public class SystemSiteSelectionEditor extends GridPane
{
private static final Logger mLog = LoggerFactory.getLogger(SystemSiteSelectionEditor.class);
private UserPreferences mUserPreferences;
private PlaylistManager mPlaylistManager;
private ProgressIndicator mProgressIndicator;
private Label mPlaceholderLabel;
private Label mProtocolLabel;
private Label mFlavorLabel;
private Label mVoiceLabel;
private TableView<Site> mSiteTableView;
private SiteEditor mSiteEditor;
private RadioReferenceDecoder mRadioReferenceDecoder;
private System mCurrentSystem;
private SystemInformation mCurrentSystemInformation;
/**
* Constructs an instance
* @param userPreferences for settings
* @param playlistManager to access radio reference
*/
public SystemSiteSelectionEditor(UserPreferences userPreferences, PlaylistManager playlistManager)
{
mUserPreferences = userPreferences;
mPlaylistManager = playlistManager;
setPadding(new Insets(10,0,0,0));
setHgap(10);
setVgap(10);
int row = 0;
Label typeLabel = new Label("Protocol:");
GridPane.setConstraints(typeLabel, 0, row);
GridPane.setHalignment(typeLabel, HPos.RIGHT);
getChildren().add(typeLabel);
GridPane.setConstraints(getProtocolLabel(), 1, row);
GridPane.setHgrow(getProtocolLabel(), Priority.ALWAYS);
getChildren().add(getProtocolLabel());
Label voiceLabel = new Label("Voice:");
GridPane.setConstraints(voiceLabel, 2, row);
GridPane.setHalignment(voiceLabel, HPos.RIGHT);
getChildren().add(voiceLabel);
GridPane.setConstraints(getVoiceLabel(), 3, row);
GridPane.setHgrow(getVoiceLabel(), Priority.ALWAYS);
getChildren().add(getVoiceLabel());
GridPane.setConstraints(getSiteTableView(), 0, ++row, 4, 1);
getChildren().add(getSiteTableView());
GridPane.setConstraints(getSiteEditor(), 0, ++row, 4, 1);
GridPane.setHgrow(getSiteEditor(), Priority.ALWAYS);
GridPane.setVgrow(getSiteEditor(), Priority.ALWAYS);
getChildren().add(getSiteEditor());
}
/**
* Updates this view with the specified system
* @param system to view
*/
public void setSystem(System system, List<Site> sites, RadioReferenceDecoder decoder, SystemInformation systemInformation)
{
mCurrentSystem = system;
mCurrentSystemInformation = systemInformation;
mRadioReferenceDecoder = decoder;
Flavor flavor = decoder.getFlavor(system);
Type type = decoder.getType(system);
String protocol = (type != null ? type.getName() : "Unknown") + (flavor != null ? " " + flavor.getName() : "");
getProtocolLabel().setText(protocol);
getFlavorLabel().setText(flavor != null ? flavor.getName() : null);
Voice voice = decoder.getVoice(system);
getVoiceLabel().setText(voice != null ? voice.getName() : null);
getSiteTableView().getItems().addAll(sites);
setLoading(false);
}
public void clear()
{
getProtocolLabel().setText(null);
getFlavorLabel().setText(null);
getVoiceLabel().setText(null);
getSiteTableView().getItems().clear();
}
public void clearAndSetLoading()
{
clear();
setLoading(true);
}
private SiteEditor getSiteEditor()
{
if(mSiteEditor == null)
{
mSiteEditor = new SiteEditor(mUserPreferences, mPlaylistManager);
}
return mSiteEditor;
}
private Label getProtocolLabel()
{
if(mProtocolLabel == null)
{
mProtocolLabel = new Label();
}
return mProtocolLabel;
}
private Label getFlavorLabel()
{
if(mFlavorLabel == null)
{
mFlavorLabel = new Label();
}
return mFlavorLabel;
}
private Label getVoiceLabel()
{
if(mVoiceLabel == null)
{
mVoiceLabel = new Label();
}
return mVoiceLabel;
}
private ProgressIndicator getProgressIndicator()
{
if(mProgressIndicator == null)
{
mProgressIndicator = new ProgressIndicator();
mProgressIndicator.setProgress(-1);
}
return mProgressIndicator;
}
private Label getPlaceholderLabel()
{
if(mPlaceholderLabel == null)
{
mPlaceholderLabel = new Label("Select a system, or there are no sites available for currently selected system");
}
return mPlaceholderLabel;
}
private TableView<Site> getSiteTableView()
{
if(mSiteTableView == null)
{
mSiteTableView = new TableView<>();
mSiteTableView.setPlaceholder(getPlaceholderLabel());
mSiteTableView.setPrefHeight(125);
TableColumn numberColumn = new TableColumn();
numberColumn.setText("Site");
numberColumn.setCellValueFactory(new PropertyValueFactory<>("siteNumber"));
numberColumn.setPrefWidth(75);
TableColumn rfssColumn = new TableColumn();
rfssColumn.setText("RFSS");
rfssColumn.setCellValueFactory(new PropertyValueFactory<>("rfss"));
rfssColumn.setPrefWidth(60);
TableColumn countyColumn = new TableColumn();
countyColumn.setText("County");
countyColumn.setCellValueFactory(new PropertyValueFactory<>("countyName"));
countyColumn.setPrefWidth(125);
TableColumn descriptionColumn = new TableColumn();
descriptionColumn.setText("Name");
descriptionColumn.setCellValueFactory(new PropertyValueFactory<>("description"));
descriptionColumn.setPrefWidth(400);
mSiteTableView.getColumns().addAll(numberColumn, rfssColumn, countyColumn, descriptionColumn);
mSiteTableView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
mSiteTableView.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, selected) -> getSiteEditor()
.setSite(selected, mCurrentSystem, mCurrentSystemInformation, mRadioReferenceDecoder));
}
return mSiteTableView;
}
private void setLoading(boolean loading)
{
getSiteTableView().setPlaceholder(loading ? getProgressIndicator() : getPlaceholderLabel());
}
}

View File

@@ -0,0 +1,853 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.radioreference;
import com.google.common.eventbus.Subscribe;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasList;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.control.MaxLengthUnaryOperator;
import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.protocol.Protocol;
import io.github.dsheirer.rrapi.type.System;
import io.github.dsheirer.rrapi.type.Talkgroup;
import io.github.dsheirer.rrapi.type.TalkgroupCategory;
import javafx.animation.RotateTransition;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.Separator;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.scene.control.TextInputDialog;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.controlsfx.control.textfield.TextFields;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
public class SystemTalkgroupSelectionEditor extends GridPane
{
private static final Logger mLog = LoggerFactory.getLogger(SystemTalkgroupSelectionEditor.class);
private final TalkgroupCategory ALL_TALKGROUPS = new TalkgroupCategory();
private UserPreferences mUserPreferences;
private PlaylistManager mPlaylistManager;
private TableView<AliasedTalkgroup> mTalkgroupTableView;
private ComboBox<TalkgroupCategory> mTalkgroupCategoryComboBox;
private TextField mSearchField;
private TalkgroupEditor mTalkgroupEditor;
private ComboBox<String> mAliasListNameComboBox;
private Button mNewAliasListButton;
private TalkgroupFilter mTalkgroupFilter = new TalkgroupFilter();
private FilteredList<AliasedTalkgroup> mTalkgroupFilteredList;
private ObservableList<AliasedTalkgroup> mTalkgroupList = FXCollections.observableArrayList();
private System mCurrentSystem;
private RadioReferenceDecoder mRadioReferenceDecoder;
private AliasList mAliasList;
private AliasListChangeListener mAliasListChangeListener = new AliasListChangeListener();
private Button mImportAllTalkgroupsButton;
private Label mPlaceholderLabel;
private ProgressIndicator mProgressIndicator;
private CheckBox mEncryptedAsDoNotMonitorCheckBox;
public SystemTalkgroupSelectionEditor(UserPreferences userPreferences, PlaylistManager playlistManager)
{
//Register to receive flash alias box requests
MyEventBus.getEventBus().register(this);
mUserPreferences = userPreferences;
mPlaylistManager = playlistManager;
ALL_TALKGROUPS.setName("(All Talkgroups)");
setPadding(new Insets(10,0,0,0));
setVgap(10);
setHgap(10);
setMaxHeight(Double.MAX_VALUE);
int row = 0;
ColumnConstraints column1 = new ColumnConstraints();
ColumnConstraints column2 = new ColumnConstraints();
column2.setPercentWidth(40);
getColumnConstraints().addAll(column1, column2);
HBox listBox = new HBox();
listBox.setSpacing(5);
listBox.setAlignment(Pos.CENTER);
Label importLabel = new Label("Import To Alias List:");
listBox.getChildren().addAll(importLabel, getAliasListNameComboBox(), getNewAliasListButton());
GridPane.setConstraints(listBox, 0, row);
getChildren().add(listBox);
GridPane.setConstraints(getEncryptedAsDoNotMonitorCheckBox(), 1, row);
GridPane.setHalignment(getEncryptedAsDoNotMonitorCheckBox(), HPos.CENTER);
getChildren().add(getEncryptedAsDoNotMonitorCheckBox());
HBox searchBox = new HBox();
searchBox.setSpacing(5);
searchBox.setAlignment(Pos.CENTER);
searchBox.getChildren().addAll(new Label("Search"), getSearchField());
GridPane.setConstraints(searchBox, 0, ++row);
getChildren().add(searchBox);
GridPane.setHalignment(getImportAllTalkgroupsButton(), HPos.CENTER);
GridPane.setConstraints(getImportAllTalkgroupsButton(), 1, row);
getChildren().add(getImportAllTalkgroupsButton());
HBox categoryBox = new HBox();
categoryBox.setAlignment(Pos.CENTER_LEFT);
categoryBox.setSpacing(5);
HBox.setHgrow(getTalkgroupCategoryComboBox(), Priority.ALWAYS);
categoryBox.getChildren().addAll(new Label("Category"), getTalkgroupCategoryComboBox());
GridPane.setHgrow(categoryBox, Priority.ALWAYS);
GridPane.setConstraints(categoryBox, 0, ++row);
getChildren().add(categoryBox);
Separator separator = new Separator(Orientation.HORIZONTAL);
GridPane.setHgrow(separator, Priority.ALWAYS);
GridPane.setConstraints(separator, 1, row);
getChildren().add(separator);
GridPane.setHgrow(getTalkgroupTableView(), Priority.ALWAYS);
GridPane.setVgrow(getTalkgroupTableView(), Priority.ALWAYS);
GridPane.setConstraints(getTalkgroupTableView(), 0, ++row);
getChildren().add(getTalkgroupTableView());
GridPane.setHgrow(getTalkgroupEditor(), Priority.ALWAYS);
GridPane.setVgrow(getTalkgroupEditor(), Priority.ALWAYS);
GridPane.setConstraints(getTalkgroupEditor(), 1, row);
getChildren().add(getTalkgroupEditor());
}
public void clear()
{
mTalkgroupList.clear();
getTalkgroupCategoryComboBox().getItems().clear();
}
public void clearAndSetLoading()
{
clear();
setLoading(true);
}
private CheckBox getEncryptedAsDoNotMonitorCheckBox()
{
if(mEncryptedAsDoNotMonitorCheckBox == null)
{
mEncryptedAsDoNotMonitorCheckBox = new CheckBox("Set Encrypted Talkgroups To Muted");
mEncryptedAsDoNotMonitorCheckBox.setDisable(true);
mEncryptedAsDoNotMonitorCheckBox.selectedProperty().set(mUserPreferences.getRadioReferencePreference()
.isEncryptedTalkgroupDoNotMonitor());
mEncryptedAsDoNotMonitorCheckBox.selectedProperty()
.addListener((observable, oldValue, newValue) -> mUserPreferences.getRadioReferencePreference()
.setEncryptedTalkgroupDoNotMonitor(mEncryptedAsDoNotMonitorCheckBox.isSelected()));
}
return mEncryptedAsDoNotMonitorCheckBox;
}
private void setLoading(boolean loading)
{
getTalkgroupTableView().setPlaceholder(loading ? getProgressIndicator() : getPlaceholderLabel());
}
private ProgressIndicator getProgressIndicator()
{
if(mProgressIndicator == null)
{
mProgressIndicator = new ProgressIndicator();
mProgressIndicator.setProgress(-1);
}
return mProgressIndicator;
}
private Label getPlaceholderLabel()
{
if(mPlaceholderLabel == null)
{
mPlaceholderLabel = new Label("No Talkgroups Available");
}
return mPlaceholderLabel;
}
private void updateFilter()
{
mTalkgroupFilter.setFilterText(getSearchField().getText());
TalkgroupCategory category = getTalkgroupCategoryComboBox().getSelectionModel().getSelectedItem();
if(category == ALL_TALKGROUPS)
{
mTalkgroupFilter.setCategory(null);
}
else
{
mTalkgroupFilter.setCategory(category != null ? category.getTalkgroupCategoryId() : null);
}
mTalkgroupFilteredList.setPredicate(null);
mTalkgroupFilteredList.setPredicate(mTalkgroupFilter);
}
public void setSystem(System system, List<Talkgroup> talkgroups, List<TalkgroupCategory> categories,
RadioReferenceDecoder decoder)
{
mCurrentSystem = system;
mRadioReferenceDecoder = decoder;
clearAndSetLoading();
if(talkgroups != null && !talkgroups.isEmpty())
{
Collections.sort(talkgroups, Comparator.comparingInt(Talkgroup::getDecimalValue));
for(Talkgroup talkgroup: talkgroups)
{
mTalkgroupList.add(new AliasedTalkgroup(talkgroup, getAlias(talkgroup)));
}
if(categories.size() > 0)
{
Collections.sort(categories, (o1, o2) -> o1.getName().compareTo(o2.getName()));
categories.add(0, ALL_TALKGROUPS);
getTalkgroupCategoryComboBox().getItems().addAll(categories);
getTalkgroupCategoryComboBox().getSelectionModel().select(ALL_TALKGROUPS);
}
}
Protocol protocol = getRadioReferenceDecoder().getProtocol(getCurrentSystem());
//If the protocol is supported then enable the talkgroup import controls
boolean supported = getRadioReferenceDecoder().hasSupportedProtocol(getCurrentSystem());
getImportAllTalkgroupsButton().setDisable(!supported);
getEncryptedAsDoNotMonitorCheckBox().setDisable(!supported);
setLoading(false);
}
@Subscribe
public void process(FlashAliasListComboBoxRequest request)
{
flashAliasListComboBox();
}
/**
* Flashes the alias list combobox to let the user know that they must select an alias list
*/
private void flashAliasListComboBox()
{
RotateTransition rt = new RotateTransition(Duration.millis(150), getAliasListNameComboBox());
rt.setByAngle(20);
rt.setCycleCount(6);
rt.setAutoReverse(true);
rt.play();
}
private Button getImportAllTalkgroupsButton()
{
if(mImportAllTalkgroupsButton == null)
{
mImportAllTalkgroupsButton = new Button("Import All Talkgroups");
mImportAllTalkgroupsButton.setOnAction(event -> {
String aliasList = getAliasListNameComboBox().getSelectionModel().getSelectedItem();
if(aliasList == null)
{
Alert alert = new Alert(Alert.AlertType.INFORMATION, "Please select an Alias List",
ButtonType.OK);
alert.setTitle("Alias List Required");
alert.setHeaderText("An alias list is required to create aliases");
alert.initOwner((getImportAllTalkgroupsButton()).getScene().getWindow());
alert.showAndWait();
flashAliasListComboBox();
}
else
{
for(AliasedTalkgroup aliasedTalkgroup : mTalkgroupFilteredList)
{
if(!aliasedTalkgroup.hasAlias())
{
createAlias(aliasedTalkgroup.getTalkgroup());
}
}
}
});
}
return mImportAllTalkgroupsButton;
}
/**
* Creates an alias for the specified talkgroup and adds it to the currently selected alias list
* @param talkgroup to alias
*/
public void createAlias(Talkgroup talkgroup)
{
TalkgroupCategory talkgroupCategory = getTalkgroupCategory(talkgroup);
String group = (talkgroupCategory != null ? talkgroupCategory.getName() : null);
Alias alias = getRadioReferenceDecoder().createAlias(talkgroup, getCurrentSystem(),
getAliasListNameComboBox().getSelectionModel().getSelectedItem(), group);
if(getEncryptedAsDoNotMonitorCheckBox().selectedProperty().get() &&
TalkgroupEncryption.lookup(talkgroup.getEncryptionState()) == TalkgroupEncryption.FULL)
{
int priority = io.github.dsheirer.alias.id.priority.Priority.DO_NOT_MONITOR;
alias.addAliasID(new io.github.dsheirer.alias.id.priority.Priority(priority));
}
mPlaylistManager.getAliasModel().addAlias(alias);
}
/**
* Retrieves the talkgroup category that matches the specified id from the current set in the talkgroup category
* combo box.
* @param talkgroup to match
* @return matching category or null
*/
private TalkgroupCategory getTalkgroupCategory(Talkgroup talkgroup)
{
if(talkgroup != null)
{
for(TalkgroupCategory category: getTalkgroupCategoryComboBox().getItems())
{
if(category.getTalkgroupCategoryId() == talkgroup.getTalkgroupCategoryId())
{
return category;
}
}
}
return null;
}
private System getCurrentSystem()
{
return mCurrentSystem;
}
private AliasList getAliasList()
{
if(mAliasList == null)
{
mAliasList = new AliasList("empty");
}
return mAliasList;
}
/**
* Retrieves any alias that matches the talkgroup value from the currently selected alias list
* @param talkgroup
* @return
*/
private Alias getAlias(Talkgroup talkgroup)
{
TalkgroupIdentifier talkgroupIdentifier = getRadioReferenceDecoder().getIdentifier(talkgroup, getCurrentSystem());
List<Alias> aliases = getAliasList().getAliases(talkgroupIdentifier);
if(aliases.size() > 0)
{
return aliases.get(0);
}
return null;
}
private RadioReferenceDecoder getRadioReferenceDecoder()
{
return mRadioReferenceDecoder;
}
private TalkgroupEditor getTalkgroupEditor()
{
if(mTalkgroupEditor == null)
{
mTalkgroupEditor = new TalkgroupEditor(mUserPreferences, mPlaylistManager);
}
return mTalkgroupEditor;
}
private TextField getSearchField()
{
if(mSearchField == null)
{
mSearchField = TextFields.createClearableTextField();
mSearchField.textProperty().addListener((observable, oldValue, newValue) -> updateFilter());
}
return mSearchField;
}
private ComboBox<String> getAliasListNameComboBox()
{
if(mAliasListNameComboBox == null)
{
Predicate<String> filterPredicate = s -> !s.contentEquals(AliasModel.NO_ALIAS_LIST);
FilteredList<String> filteredChannelList =
new FilteredList<>(mPlaylistManager.getAliasModel().aliasListNames(), filterPredicate);
mAliasListNameComboBox = new ComboBox<>(filteredChannelList);
mAliasListNameComboBox.setPrefWidth(150);
mAliasListNameComboBox.setOnAction(event -> updateAliasList(getAliasListNameComboBox()
.getSelectionModel().getSelectedItem()));
if(mAliasListNameComboBox.getItems().size() > 0)
{
mAliasListNameComboBox.getSelectionModel().select(0);
}
updateAliasList(getAliasListNameComboBox().getSelectionModel().getSelectedItem());
}
return mAliasListNameComboBox;
}
/**
* Updates the alias list whenever the alias list combo box changes. Refreshes the alias for each talkgroup
* table entry from the new list and registers a listener to detect changes to the alias list that might occur
* on the alias tab, so that this table stays in sync with any alias changes.
*/
private void updateAliasList(String aliasListName)
{
if(mAliasList != null)
{
mAliasList.aliases().removeListener(mAliasListChangeListener);
}
mAliasList = mPlaylistManager.getAliasModel().getAliasList(aliasListName);
if(mAliasList != null)
{
mAliasList.aliases().addListener(mAliasListChangeListener);
}
//Refresh the alias for each item in the table
for(AliasedTalkgroup item: mTalkgroupList)
{
item.setAlias(getAlias(item.getTalkgroup()));
}
}
/**
* Updates the talkgroup and alias table when there is an alias change detected. Discovers all talkgroup and
* talkgroup ranges that are aliased and then updates the corresponding alias for any talkgroups that are in the table.
*/
private void updateAlias(Alias alias)
{
AliasedTalkgroup currentlySelected = getTalkgroupTableView().getSelectionModel().getSelectedItem();
if(alias != null)
{
for(AliasID aliasID: alias.getAliasIdentifiers())
{
if(aliasID instanceof io.github.dsheirer.alias.id.talkgroup.Talkgroup)
{
int value = ((io.github.dsheirer.alias.id.talkgroup.Talkgroup)aliasID).getValue();
for(AliasedTalkgroup aliasedTalkgroup : mTalkgroupList)
{
if(aliasedTalkgroup.getTalkgroupValue() == value)
{
//Even though the new alias is triggering the change, we still use the alias that the
//alias list provides. That way if there's duplicate aliases, the user will always see the
//alias that will match the talkgroup during operation.
aliasedTalkgroup.setAlias(getAlias(aliasedTalkgroup.getTalkgroup()));
//Re-select the current item to refresh the editor view.
if(currentlySelected != null && currentlySelected == aliasedTalkgroup)
{
getTalkgroupTableView().getSelectionModel().select(null);
getTalkgroupTableView().getSelectionModel().select(aliasedTalkgroup);
}
}
}
}
else if(aliasID instanceof TalkgroupRange)
{
TalkgroupRange range = (TalkgroupRange)aliasID;
for(int x = range.getMinTalkgroup(); x <= range.getMaxTalkgroup(); x++)
{
for(AliasedTalkgroup aliasedTalkgroup : mTalkgroupList)
{
if(aliasedTalkgroup.getTalkgroupValue() == x)
{
//Even though the new alias is triggering the change, we still use the alias that the
//alias list provides. That way if there's duplicate aliases, the user will always see the
//alias that will match the talkgroup during operation.
aliasedTalkgroup.setAlias(getAlias(aliasedTalkgroup.getTalkgroup()));
//Re-select the current item to refresh the editor view.
if(currentlySelected != null && currentlySelected == aliasedTalkgroup)
{
getTalkgroupTableView().getSelectionModel().select(null);
getTalkgroupTableView().getSelectionModel().select(aliasedTalkgroup);
}
}
}
}
}
}
}
}
private Button getNewAliasListButton()
{
if(mNewAliasListButton == null)
{
mNewAliasListButton = new Button("New Alias List");
mNewAliasListButton.setOnAction(event -> {
TextInputDialog dialog = new TextInputDialog();
dialog.setTitle("Create New Alias List");
dialog.setHeaderText("Please enter an alias list name (max 25 chars).");
dialog.setContentText("Name:");
dialog.getEditor().setTextFormatter(new TextFormatter<String>(new MaxLengthUnaryOperator(25)));
Optional<String> result = dialog.showAndWait();
result.ifPresent(s -> {
String name = result.get();
if(name != null && !name.isEmpty())
{
name = name.trim();
mPlaylistManager.getAliasModel().addAliasList(name);
getAliasListNameComboBox().getSelectionModel().select(name);
}
});
});
}
return mNewAliasListButton;
}
private ComboBox<TalkgroupCategory> getTalkgroupCategoryComboBox()
{
if(mTalkgroupCategoryComboBox == null)
{
mTalkgroupCategoryComboBox = new ComboBox<>();
mTalkgroupCategoryComboBox.setMaxWidth(Double.MAX_VALUE);
mTalkgroupCategoryComboBox.setConverter(new TalkgroupCategoryStringConverter());
mTalkgroupCategoryComboBox.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> updateFilter());
}
return mTalkgroupCategoryComboBox;
}
private TableView<AliasedTalkgroup> getTalkgroupTableView()
{
if(mTalkgroupTableView == null)
{
mTalkgroupTableView = new TableView<>();
mTalkgroupTableView.setMaxHeight(Double.MAX_VALUE);
TableColumn<AliasedTalkgroup,String> talkgroupColumn = new TableColumn("Talkgroup");
talkgroupColumn.setCellValueFactory(new PropertyValueFactory<>("talkgroup"));
TableColumn<AliasedTalkgroup,String> descriptionColumn = new TableColumn("Description");
descriptionColumn.setPrefWidth(300);
descriptionColumn.setCellValueFactory(new PropertyValueFactory<>("description"));
TableColumn<AliasedTalkgroup,String> aliasColumn = new TableColumn("Alias");
aliasColumn.setPrefWidth(170);
aliasColumn.setCellValueFactory(new PropertyValueFactory<>("alias"));
mTalkgroupTableView.getColumns().addAll(talkgroupColumn, descriptionColumn, aliasColumn);
mTalkgroupTableView.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, selected) -> {
TalkgroupCategory talkgroupCategory =
getTalkgroupCategory(selected != null ? selected.getTalkgroup() : null);
String aliasListName = getAliasListNameComboBox().getSelectionModel().getSelectedItem();
getTalkgroupEditor().setTalkgroup((selected != null ? selected.getTalkgroup() : null),
getCurrentSystem(), getRadioReferenceDecoder(), (selected != null ? selected.getAlias() : null),
aliasListName, (talkgroupCategory != null ? talkgroupCategory.getName() : null),
getEncryptedAsDoNotMonitorCheckBox().selectedProperty().get());
});
mTalkgroupFilteredList = new FilteredList<>(mTalkgroupList);
SortedList<AliasedTalkgroup> sortedList = new SortedList<>(mTalkgroupFilteredList);
sortedList.comparatorProperty().bind(mTalkgroupTableView.comparatorProperty());
mTalkgroupTableView.setItems(sortedList);
}
return mTalkgroupTableView;
}
public class TalkgroupCategoryListCell extends ListCell<TalkgroupCategory>
{
@Override
protected void updateItem(TalkgroupCategory item, boolean empty)
{
super.updateItem(item, empty);
setText((empty || item == null) ? null : item.getName());
}
}
public class TalkgroupCategoryStringConverter extends StringConverter<TalkgroupCategory>
{
@Override
public String toString(TalkgroupCategory cat)
{
return cat != null ? cat.getName() : null;
}
@Override
public TalkgroupCategory fromString(String string)
{
for(TalkgroupCategory cat: getTalkgroupCategoryComboBox().getItems())
{
if(cat.getName().contentEquals(string))
{
return cat;
}
}
return null;
}
}
public class TalkgroupFilter implements Predicate<AliasedTalkgroup>
{
private String mFilterText;
private Integer mCategory;
public TalkgroupFilter()
{
}
public void setFilterText(String filterText)
{
mFilterText = filterText != null ? filterText.toLowerCase() : null;
}
public void setCategory(Integer category)
{
mCategory = category;
}
@Override
public boolean test(AliasedTalkgroup aliasedTalkgroup)
{
if(mCategory == null && (mFilterText == null || mFilterText.isEmpty()))
{
return true;
}
Talkgroup talkgroup = aliasedTalkgroup.getTalkgroup();
if(mCategory != null && mFilterText != null)
{
if(talkgroup.getTalkgroupCategoryId() != mCategory)
{
return false;
}
if(aliasedTalkgroup.descriptionProperty().get() != null &&
aliasedTalkgroup.descriptionProperty().get().toLowerCase().contains(mFilterText))
{
return true;
}
if(aliasedTalkgroup.talkgroupProperty().get() != null &&
aliasedTalkgroup.talkgroupProperty().get().toLowerCase().contains(mFilterText))
{
return true;
}
if(aliasedTalkgroup.aliasProperty().get() != null &&
aliasedTalkgroup.aliasProperty().get().toLowerCase().contains(mFilterText))
{
return true;
}
return false;
}
else if(mCategory != null)
{
return talkgroup.getTalkgroupCategoryId() == mCategory;
}
else
{
if(aliasedTalkgroup.descriptionProperty().get() != null &&
aliasedTalkgroup.descriptionProperty().get().toLowerCase().contains(mFilterText))
{
return true;
}
if(aliasedTalkgroup.talkgroupProperty().get() != null &&
aliasedTalkgroup.talkgroupProperty().get().toLowerCase().contains(mFilterText))
{
return true;
}
if(aliasedTalkgroup.aliasProperty().get() != null &&
aliasedTalkgroup.aliasProperty().get().toLowerCase().contains(mFilterText))
{
return true;
}
return false;
}
}
}
/**
* Wrapper class for talkgroups and correlated aliases
*/
public class AliasedTalkgroup
{
private Talkgroup mTalkgroup;
private Alias mAlias;
private StringProperty mAliasProperty = new SimpleStringProperty();
private StringProperty mDescriptionProperty = new SimpleStringProperty();
private StringProperty mTalkgroupProperty = new SimpleStringProperty();
public AliasedTalkgroup(Talkgroup talkgroup, Alias alias)
{
mTalkgroup = talkgroup;
mDescriptionProperty.setValue(mTalkgroup.getDescription());
setAlias(alias);
updateTalkgroup();
}
public boolean hasAlias()
{
return mAlias != null;
}
public Alias getAlias()
{
return mAlias;
}
public int getTalkgroupValue()
{
return getRadioReferenceDecoder().getTalkgroupValue(mTalkgroup, getCurrentSystem());
}
public void updateTalkgroup()
{
mTalkgroupProperty.set(getRadioReferenceDecoder().format(mTalkgroup, getCurrentSystem()));
}
public StringProperty aliasProperty()
{
return mAliasProperty;
}
public StringProperty descriptionProperty()
{
return mDescriptionProperty;
}
public StringProperty talkgroupProperty()
{
return mTalkgroupProperty;
}
public Talkgroup getTalkgroup()
{
return mTalkgroup;
}
public void setAlias(Alias alias)
{
mAliasProperty.unbind();
mAliasProperty.setValue(null);
mAlias = alias;
if(mAlias != null)
{
mAliasProperty.bind(mAlias.nameProperty());
}
}
}
/**
* Observable list change listener to detect alias changes and update the talkgroup and alias table
*/
public class AliasListChangeListener implements ListChangeListener<Alias>
{
@Override
public void onChanged(ListChangeListener.Change<? extends Alias> change)
{
while(change.next())
{
if(change.wasAdded())
{
for(Alias alias: change.getAddedSubList())
{
updateAlias(alias);
}
}
else if(change.wasRemoved())
{
for(Alias alias: change.getRemoved())
{
updateAlias(alias);
}
}
else if(change.wasUpdated())
{
for(int x = change.getFrom(); x < change.getTo(); x++)
{
updateAlias(change.getList().get(x));
}
}
}
}
}
}

View File

@@ -0,0 +1,368 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.radioreference;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.playlist.alias.ViewAliasRequest;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.rrapi.type.System;
import io.github.dsheirer.rrapi.type.Talkgroup;
import javafx.geometry.HPos;
import javafx.geometry.Orientation;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TalkgroupEditor extends GridPane
{
private static final Logger mLog = LoggerFactory.getLogger(TalkgroupEditor.class);
private UserPreferences mUserPreferences;
private PlaylistManager mPlaylistManager;
private TextField mAlphaTagTextField;
private TextField mDescriptionTextField;
private TextField mTalkgroupTextField;
private TextField mModeTextField;
private Button mEditAliasButton;
private TextField mAliasNameTextField;
private TextField mAliasGroupTextField;
private Button mCreateAliasButton;
private Label mCreateLabel;
private Label mNameLabel;
private Label mGroupLabel;
private Label mNotSupportedLabel;
private RadioReferenceDecoder mRadioReferenceDecoder;
private String mAliasListName;
private System mSystem;
private Talkgroup mTalkgroup;
private Alias mAlias;
private boolean mSetEncryptedDoNotMonitor;
public TalkgroupEditor(UserPreferences userPreferences, PlaylistManager playlistManager)
{
mUserPreferences = userPreferences;
mPlaylistManager = playlistManager;
setHgap(5);
setVgap(5);
int row = 0;
Label radioReferenceLabel = new Label("Radio Reference Talkgroup Details");
GridPane.setConstraints(radioReferenceLabel, 1, row);
GridPane.setHalignment(radioReferenceLabel, HPos.LEFT);
getChildren().add(radioReferenceLabel);
Label talkgroupLabel = new Label("Talkgroup");
GridPane.setConstraints(talkgroupLabel, 0, ++row);
GridPane.setHalignment(talkgroupLabel, HPos.RIGHT);
getChildren().add(talkgroupLabel);
GridPane.setHgrow(getTalkgroupTextField(), Priority.ALWAYS);
GridPane.setConstraints(getTalkgroupTextField(), 1, row);
getChildren().add(getTalkgroupTextField());
Label alphaLabel = new Label("Alpha Tag");
GridPane.setConstraints(alphaLabel, 0, ++row);
GridPane.setHalignment(alphaLabel, HPos.RIGHT);
getChildren().add(alphaLabel);
GridPane.setHgrow(getAlphaTagTextField(), Priority.ALWAYS);
GridPane.setConstraints(getAlphaTagTextField(), 1, row);
getChildren().add(getAlphaTagTextField());
Label descriptionLabel = new Label("Description");
GridPane.setConstraints(descriptionLabel, 0, ++row);
GridPane.setHalignment(descriptionLabel, HPos.RIGHT);
getChildren().add(descriptionLabel);
GridPane.setHgrow(getDescriptionTextField(), Priority.ALWAYS);
GridPane.setConstraints(getDescriptionTextField(), 1, row);
getChildren().add(getDescriptionTextField());
Label modeLabel = new Label("Mode");
GridPane.setConstraints(modeLabel, 0, ++row);
GridPane.setHalignment(modeLabel, HPos.RIGHT);
getChildren().add(modeLabel);
GridPane.setHgrow(getModeTextField(), Priority.ALWAYS);
GridPane.setConstraints(getModeTextField(), 1, row);
getChildren().add(getModeTextField());
Separator separator2 = new Separator(Orientation.HORIZONTAL);
GridPane.setConstraints(separator2, 0, ++row, 2,1);
getChildren().add(separator2);
//The following controls co-exist/overlap in the grid pane - visibility is controlled by the setTalkgroup()
// method so that only one set is visible at any time.
GridPane.setConstraints(getEditAliasButton(), 1, ++row);
getChildren().add(getEditAliasButton());
GridPane.setConstraints(getCreateLabel(), 1, row);
GridPane.setHalignment(getCreateLabel(), HPos.LEFT);
getChildren().add(getCreateLabel());
GridPane.setConstraints(getNotSupportedLabel(), 1, row);
GridPane.setHalignment(getNotSupportedLabel(), HPos.LEFT);
getChildren().add(getNotSupportedLabel());
GridPane.setConstraints(getNameLabel(), 0, ++row);
GridPane.setHalignment(getNameLabel(), HPos.RIGHT);
getChildren().add(getNameLabel());
GridPane.setConstraints(getAliasNameTextField(), 1, row);
getChildren().add(getAliasNameTextField());
GridPane.setConstraints(getGroupLabel(), 0, ++row);
GridPane.setHalignment(getGroupLabel(), HPos.RIGHT);
getChildren().add(getGroupLabel());
GridPane.setConstraints(getAliasGroupTextField(), 1, row);
getChildren().add(getAliasGroupTextField());
GridPane.setConstraints(getCreateAliasButton(), 1, ++row);
getChildren().add(getCreateAliasButton());
}
public void setTalkgroup(Talkgroup talkgroup, System system, RadioReferenceDecoder decoder, Alias alias,
String aliasListName, String group, boolean setEncryptedDoNotMonitor)
{
mRadioReferenceDecoder = decoder;
mTalkgroup = talkgroup;
mSystem = system;
mAliasListName = aliasListName;
mAlias = alias;
mSetEncryptedDoNotMonitor = setEncryptedDoNotMonitor;
if(talkgroup != null)
{
getTalkgroupTextField().setText(decoder.format(talkgroup, system));
getAlphaTagTextField().setText(talkgroup.getAlphaTag());
getAliasNameTextField().setText(talkgroup.getAlphaTag());
getDescriptionTextField().setText(talkgroup.getDescription());
TalkgroupMode talkgroupMode = TalkgroupMode.lookup(talkgroup.getMode());
TalkgroupEncryption talkgroupEncryption = TalkgroupEncryption.lookup(talkgroup.getEncryptionState());
getModeTextField().setText(talkgroupMode.toString() + (talkgroupEncryption != TalkgroupEncryption.UNENCRYPTED ?
" - " + talkgroupEncryption.toString() : ""));
}
else
{
getTalkgroupTextField().setText(null);
getAlphaTagTextField().setText(null);
getDescriptionTextField().setText(null);
getModeTextField().setText(null);
getAliasNameTextField().setText(null);
}
boolean supported = decoder.hasSupportedProtocol(system);
getEditAliasButton().setVisible(mAlias != null);
getCreateAliasButton().setVisible(mTalkgroup != null && mAlias == null && supported);
getNameLabel().setVisible(mTalkgroup != null && mAlias == null && supported);
getAliasNameTextField().setVisible(mTalkgroup != null && mAlias == null && supported);
getGroupLabel().setVisible(mTalkgroup != null && mAlias == null && supported);
getAliasGroupTextField().setText(group);
getAliasGroupTextField().setVisible(mTalkgroup != null && mAlias == null && supported);
getCreateLabel().setVisible(mTalkgroup != null && mAlias == null && supported);
getNotSupportedLabel().setVisible(mTalkgroup != null && !supported);
}
private Label getNotSupportedLabel()
{
if(mNotSupportedLabel == null)
{
mNotSupportedLabel = new Label("Protocol Not Supported");
mNotSupportedLabel.setVisible(false);
}
return mNotSupportedLabel;
}
private Label getCreateLabel()
{
if(mCreateLabel == null)
{
mCreateLabel = new Label("Create Talkgroup Alias");
mCreateLabel.setVisible(false);
}
return mCreateLabel;
}
private Label getNameLabel()
{
if(mNameLabel == null)
{
mNameLabel = new Label("Name");
mNameLabel.setVisible(false);
}
return mNameLabel;
}
private Label getGroupLabel()
{
if(mGroupLabel == null)
{
mGroupLabel = new Label("Group");
mGroupLabel.setVisible(false);
}
return mGroupLabel;
}
private TextField getAliasNameTextField()
{
if(mAliasNameTextField == null)
{
mAliasNameTextField = new TextField();
mAliasNameTextField.setMaxWidth(Double.MAX_VALUE);
mAliasNameTextField.setVisible(false);
}
return mAliasNameTextField;
}
private TextField getAliasGroupTextField()
{
if(mAliasGroupTextField == null)
{
mAliasGroupTextField = new TextField();
mAliasGroupTextField.setMaxWidth(Double.MAX_VALUE);
mAliasGroupTextField.setVisible(false);
}
return mAliasGroupTextField;
}
private Button getCreateAliasButton()
{
if(mCreateAliasButton == null)
{
mCreateAliasButton = new Button("Create Talkgroup Alias");
mCreateAliasButton.setVisible(false);
mCreateAliasButton.setOnAction(event -> {
if(mAliasListName == null)
{
Alert alert = new Alert(Alert.AlertType.INFORMATION, "Please select an Alias List",
ButtonType.OK);
alert.setTitle("Alias List Required");
alert.setHeaderText("An alias list is required to create aliases");
alert.initOwner((getCreateAliasButton()).getScene().getWindow());
alert.showAndWait();
MyEventBus.getEventBus().post(new FlashAliasListComboBoxRequest());
}
else if(mRadioReferenceDecoder != null && mTalkgroup != null && mSystem != null)
{
Alias alias = mRadioReferenceDecoder.createAlias(mTalkgroup, mSystem, mAliasListName,
getAliasGroupTextField().getText());
alias.setName(getAliasNameTextField().getText());
if(mSetEncryptedDoNotMonitor &&
TalkgroupEncryption.lookup(mTalkgroup.getEncryptionState()) == TalkgroupEncryption.FULL)
{
int priority = io.github.dsheirer.alias.id.priority.Priority.DO_NOT_MONITOR;
alias.addAliasID(new io.github.dsheirer.alias.id.priority.Priority(priority));
}
mPlaylistManager.getAliasModel().addAlias(alias);
}
});
}
return mCreateAliasButton;
}
private Button getEditAliasButton()
{
if(mEditAliasButton == null)
{
mEditAliasButton = new Button("View/Edit Alias");
mEditAliasButton.setVisible(false);
mEditAliasButton.setOnAction(event -> {
if(mAlias != null)
{
MyEventBus.getEventBus().post(new ViewAliasRequest(mAlias));
}
});
}
return mEditAliasButton;
}
public TextField getAlphaTagTextField()
{
if(mAlphaTagTextField == null)
{
mAlphaTagTextField = new TextField();
mAlphaTagTextField.setMaxWidth(Double.MAX_VALUE);
mAlphaTagTextField.setDisable(true);
}
return mAlphaTagTextField;
}
public TextField getDescriptionTextField()
{
if(mDescriptionTextField == null)
{
mDescriptionTextField = new TextField();
mDescriptionTextField.setMaxWidth(Double.MAX_VALUE);
mDescriptionTextField.setDisable(true);
}
return mDescriptionTextField;
}
public TextField getTalkgroupTextField()
{
if(mTalkgroupTextField == null)
{
mTalkgroupTextField = new TextField();
mTalkgroupTextField.setMaxWidth(Double.MAX_VALUE);
mTalkgroupTextField.setDisable(true);
}
return mTalkgroupTextField;
}
public TextField getModeTextField()
{
if(mModeTextField == null)
{
mModeTextField = new TextField();
mModeTextField.setMaxWidth(Double.MAX_VALUE);
mModeTextField.setPrefWidth(25);
mModeTextField.setDisable(true);
}
return mModeTextField;
}
}

View File

@@ -0,0 +1,61 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.radioreference;
/**
* Enumeration of radio reference talkgroup encryption status
*/
public enum TalkgroupEncryption
{
UNENCRYPTED("No Encryption", 0),
PARTIAL("Partial Encrypted", 1),
FULL("Encrypted", 2),
UNKNOWN("Unknown", -1);
private String mLabel;
private int mValue;
TalkgroupEncryption(String label, int value)
{
mLabel = label;
mValue = value;
}
@Override
public String toString()
{
return mLabel;
}
public static TalkgroupEncryption lookup(int value)
{
switch(value)
{
case 0:
return UNENCRYPTED;
case 1:
return PARTIAL;
case 2:
return FULL;
default:
return UNKNOWN;
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* *****************************************************************************
* Copyright (C) 2014-2020 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.playlist.radioreference;
/**
* Enumeration of radio reference talkgroup modes
*
* Note: service uses trailing value of 'E' and 'e' for full or partial encryption. Those are mapped to FE and PE
* in the enumeration values.
*/
public enum TalkgroupMode
{
A("Analog"),
D("Digital"),
M("Analog/Digital"),
T("TDMA"),
UNKNOWN("Unknown");
private String mLabel;
TalkgroupMode(String label)
{
mLabel = label;
}
@Override
public String toString()
{
return mLabel;
}
public static TalkgroupMode lookup(String value)
{
if(value == null || value.isEmpty())
{
return TalkgroupMode.UNKNOWN;
}
switch(value)
{
case "A":
return A;
case "D":
return D;
case "M":
return M;
case "T":
return T;
}
return TalkgroupMode.UNKNOWN;
}
}

View File

@@ -0,0 +1,142 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.record;
import io.github.dsheirer.gui.playlist.Editor;
import io.github.dsheirer.record.RecorderType;
import io.github.dsheirer.record.config.RecordConfiguration;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import org.controlsfx.control.ToggleSwitch;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Editor for event logging configuration objects using a VBox to visually display a vertical list of boolean toggle
* switches to turn on/off event logging for a custom set of event log types.
*/
public class RecordConfigurationEditor extends Editor<RecordConfiguration>
{
private List<RecorderControl> mControls = new ArrayList<>();
public RecordConfigurationEditor(Collection<RecorderType> types)
{
for(RecorderType type: types)
{
RecorderControl control = new RecorderControl(type);
mControls.add(control);
getChildren().add(control);
}
}
@Override
public void setItem(RecordConfiguration item)
{
if(item == null)
{
item = new RecordConfiguration();
}
super.setItem(item);
for(RecorderControl control: mControls)
{
control.getToggleSwitch().setDisable(false);
control.getToggleSwitch().setSelected(item.getRecorders().contains(control.getRecorderType()));
}
modifiedProperty().set(false);
}
@Override
public void save()
{
RecordConfiguration config = getItem();
if(config == null)
{
config = new RecordConfiguration();
}
config.clearRecorders();
for(RecorderControl control: mControls)
{
if(control.getToggleSwitch().isSelected())
{
config.addRecorder(control.getRecorderType());
}
}
setItem(config);
}
@Override
public void dispose()
{
}
public class RecorderControl extends GridPane
{
private RecorderType mRecorderType;
private ToggleSwitch mToggleSwitch;
public RecorderControl(RecorderType type)
{
mRecorderType = type;
setPadding(new Insets(5,5,5,0));
setHgap(10);
GridPane.setConstraints(getToggleSwitch(), 0, 0);
getChildren().add(getToggleSwitch());
Label label = new Label(mRecorderType.getDisplayString());
GridPane.setHalignment(label, HPos.LEFT);
GridPane.setConstraints(label, 1, 0);
getChildren().add(label);
}
private ToggleSwitch getToggleSwitch()
{
if(mToggleSwitch == null)
{
mToggleSwitch = new ToggleSwitch();
mToggleSwitch.setDisable(true);
mToggleSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mToggleSwitch;
}
public RecorderType getRecorderType()
{
return mRecorderType;
}
}
}

View File

@@ -0,0 +1,479 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.source;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfigTunerMultipleFrequency;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.TunerModel;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Frequency editor that supports editing single or multiple frequencies.
*/
public class FrequencyEditor extends SourceConfigurationEditor<SourceConfiguration>
{
private final static Logger mLog = LoggerFactory.getLogger(FrequencyEditor.class);
private static final String NONE = "(none) ";
private TunerModel mTunerModel;
private FrequencyBox mPrimaryFrequencyBox;
private List<FrequencyBox> mFrequencyBoxes = new ArrayList<>();
private ComboBox<String> mPreferredTunerComboBox;
private VBox mFrequencyBoxContainer;
private boolean mAllowMultipleFrequencies;
/**
* Constructs an instance.
* @param tunerModel for accessing list of tuners to select a preferred tuner
* @param allowMultipleFrequencies toggles behavior of the editor between single frequency (false) or multiple
* frequencies (true).
*/
public FrequencyEditor(TunerModel tunerModel, boolean allowMultipleFrequencies)
{
mTunerModel = tunerModel;
mAllowMultipleFrequencies = allowMultipleFrequencies;
HBox hBox = new HBox();
hBox.setPadding(new Insets(10,10,10,10));
hBox.setSpacing(10);
hBox.setAlignment(Pos.CENTER_LEFT);
Label label = new Label(mAllowMultipleFrequencies ? "Frequencies (MHz)" : "Frequency (MHz)");
label.setAlignment(Pos.BASELINE_RIGHT);
hBox.getChildren().addAll(label, getFrequencyBoxContainer());
Label preferredTunerLabel = new Label("Preferred Tuner");
preferredTunerLabel.setAlignment(Pos.BASELINE_RIGHT);
preferredTunerLabel.setPadding(new Insets(0,0,0,20));
hBox.getChildren().addAll(preferredTunerLabel, getPreferredTunerComboBox());
setAlignment(Pos.TOP_LEFT);
getChildren().add(hBox);
}
/**
* Sets the disabled status for each of the frequency boxes and the preferred tuner combo box.
* @param disable true to disable each of the controls or false to enable.
*/
@Override
public void disable(boolean disable)
{
for(FrequencyBox frequencyBox: mFrequencyBoxes)
{
frequencyBox.disable(disable);
}
getPreferredTunerComboBox().setDisable(disable);
}
/**
* Saves the current state of the editor to the source configuration which can then be accessed by invoking
* the getSourceConfiguration() method.
*
* Note: the source configuration will be a single frequency config if the editor has only a single frequency or
* if the single frequency is not specified. It will create/use a multiple frequency configuration if the editor
* has more than one frequency listed.
*/
@Override
public void save()
{
String preferredTuner = getPreferredTunerComboBox().getSelectionModel().getSelectedItem();
//Remove the preferred tuner value if it contains the default 'none' value
if(preferredTuner != null && preferredTuner.contentEquals(NONE))
{
preferredTuner = null;
}
List<Long> frequencies = getFrequencies();
if(frequencies.size() <= 1)
{
SourceConfigTuner sourceConfigTuner = null;
if(getSourceConfiguration() instanceof SourceConfigTuner)
{
sourceConfigTuner = (SourceConfigTuner)getSourceConfiguration();
}
else
{
sourceConfigTuner = new SourceConfigTuner();
}
if(frequencies.size() == 1)
{
sourceConfigTuner.setFrequency(frequencies.get(0));
}
else
{
sourceConfigTuner.setFrequency(0);
}
sourceConfigTuner.setPreferredTuner(preferredTuner);
setSourceConfiguration(sourceConfigTuner);
}
else
{
SourceConfigTunerMultipleFrequency sourceConfigMulti = null;
if(getSourceConfiguration() instanceof SourceConfigTunerMultipleFrequency)
{
sourceConfigMulti = (SourceConfigTunerMultipleFrequency)getSourceConfiguration();
}
else
{
sourceConfigMulti = new SourceConfigTunerMultipleFrequency();
}
sourceConfigMulti.setFrequencies(frequencies);
sourceConfigMulti.setPreferredTuner(preferredTuner);
setSourceConfiguration(sourceConfigMulti);
}
}
/**
* Loads the source configuration into the editor.
* @param sourceConfiguration to load
*/
@Override
public void setSourceConfiguration(SourceConfiguration sourceConfiguration)
{
super.setSourceConfiguration(sourceConfiguration);
String preferredTuner = null;
disable(sourceConfiguration == null);
if(sourceConfiguration == null)
{
setFrequencies(Collections.emptyList());
getPreferredTunerComboBox().setDisable(true);
getPreferredTunerComboBox().getSelectionModel().select(null);
}
else if(sourceConfiguration instanceof SourceConfigTuner)
{
SourceConfigTuner sourceConfigTuner = (SourceConfigTuner)sourceConfiguration;
setFrequency(sourceConfigTuner.getFrequency());
preferredTuner = sourceConfigTuner.getPreferredTuner();
}
else if(sourceConfiguration instanceof SourceConfigTunerMultipleFrequency)
{
SourceConfigTunerMultipleFrequency sourceMulti = (SourceConfigTunerMultipleFrequency)sourceConfiguration;
setFrequencies(sourceMulti.getFrequencies());
preferredTuner = sourceMulti.getPreferredTuner();
}
else
{
setFrequencies(Collections.emptyList());
getPreferredTunerComboBox().setDisable(false);
getPreferredTunerComboBox().getSelectionModel().select(null);
}
updatePreferredTuners();
if(preferredTuner != null)
{
if(!getPreferredTunerComboBox().getItems().contains(preferredTuner))
{
getPreferredTunerComboBox().getItems().add(preferredTuner);
}
getPreferredTunerComboBox().getSelectionModel().select(preferredTuner);
}
else
{
getPreferredTunerComboBox().getSelectionModel().select(NONE);
}
modifiedProperty().set(false);
}
/**
* Updates the preferred tuner's combo box to reflect the current set of tuners.
*/
private void updatePreferredTuners()
{
getPreferredTunerComboBox().getItems().clear();
getPreferredTunerComboBox().getItems().add(NONE);
if(mTunerModel != null)
{
getPreferredTunerComboBox().getItems().addAll(mTunerModel.getTunerNames());
}
}
/**
* Primary frequency editor control
*/
private FrequencyBox getPrimaryFrequencyBox()
{
if(mPrimaryFrequencyBox == null)
{
mPrimaryFrequencyBox = new FrequencyBox(mAllowMultipleFrequencies ? Buttons.ADD : Buttons.NONE);
mFrequencyBoxes.add(mPrimaryFrequencyBox);
}
return mPrimaryFrequencyBox;
}
/**
* Preferred tuner combo box control
*/
private ComboBox<String> getPreferredTunerComboBox()
{
if(mPreferredTunerComboBox == null)
{
mPreferredTunerComboBox = new ComboBox<>();
mPreferredTunerComboBox.setDisable(true);
mPreferredTunerComboBox.getItems().add(NONE);
mPreferredTunerComboBox.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mPreferredTunerComboBox;
}
/**
* Container for all frequency controls.
*/
private VBox getFrequencyBoxContainer()
{
if(mFrequencyBoxContainer == null)
{
mFrequencyBoxContainer = new VBox();
mFrequencyBoxContainer.setSpacing(5);
mFrequencyBoxContainer.getChildren().addAll(getPrimaryFrequencyBox());
}
return mFrequencyBoxContainer;
}
/**
* Updates the control with the list of frequencies
*/
public void setFrequencies(List<Long> frequencies)
{
resetFrequencyBoxes();
for(int x = 0; x < frequencies.size(); x++)
{
if(x == 0)
{
getPrimaryFrequencyBox().getFrequencyField().set(frequencies.get(0));
}
else
{
FrequencyBox frequencyBox = addFrequencyBox();
frequencyBox.getFrequencyField().set(frequencies.get(x));
}
}
}
/**
* Updates the control with the specified single frequency
*/
public void setFrequency(Long frequency)
{
resetFrequencyBoxes();
getPrimaryFrequencyBox().getFrequencyField().set(frequency);
}
/**
* List of frequencies contained in this control
*/
public List<Long> getFrequencies()
{
List<Long> frequencies = new ArrayList<>();
for(FrequencyBox frequencyBox: mFrequencyBoxes)
{
long frequency = frequencyBox.getFrequencyField().get();
if(frequency > 0)
{
frequencies.add(frequency);
}
}
return frequencies;
}
/**
* Resets the frequency boxes to a single frequency box with an empty frequency value.
*/
private void resetFrequencyBoxes()
{
getFrequencyBoxContainer().getChildren().clear();
mFrequencyBoxes.clear();
getFrequencyBoxContainer().getChildren().add(getPrimaryFrequencyBox());
mFrequencyBoxes.add(getPrimaryFrequencyBox());
getPrimaryFrequencyBox().getFrequencyField().set(0);
}
/**
* Adds a frequency box to the editor
*/
private FrequencyBox addFrequencyBox()
{
FrequencyBox frequencyBox = new FrequencyBox(Buttons.REMOVE);
frequencyBox.disable(false);
mFrequencyBoxes.add(frequencyBox);
getFrequencyBoxContainer().getChildren().add(frequencyBox);
return frequencyBox;
}
/**
* Removes the specified frequency box from this editor
*/
private void removeFrequencyBox(FrequencyBox toRemove)
{
getFrequencyBoxContainer().getChildren().remove(toRemove);
mFrequencyBoxes.remove(toRemove);
modifiedProperty().set(true);
}
/**
* Buttons enum used for styling FrequencyBox controls
*/
public enum Buttons {ADD, REMOVE, NONE }
/**
* Frequency field with optional ADD or REMOVE button
*/
public class FrequencyBox extends HBox
{
private FrequencyField mFrequencyField;
private Button mAddButton;
private Button mRemoveButton;
/**
* Constructs a frequency box with the specified button styling
*/
public FrequencyBox(Buttons buttons)
{
setSpacing(10);
getChildren().add(getFrequencyField());
switch(buttons)
{
case ADD:
getChildren().add(getAddButton());
break;
case REMOVE:
getChildren().add(getRemoveButton());
break;
}
}
/**
* Sets the disabled state for this frequency editor
*/
public void disable(boolean disable)
{
getFrequencyField().setDisable(disable);
getAddButton().setDisable(disable);
getRemoveButton().setDisable(disable);
}
/**
* Frequency field control for this editor
*/
public FrequencyField getFrequencyField()
{
if(mFrequencyField == null)
{
mFrequencyField = new FrequencyField();
mFrequencyField.setDisable(true);
mFrequencyField.textProperty().addListener(new ChangeListener<String>()
{
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
{
modifiedProperty().set(true);
}
});
}
return mFrequencyField;
}
/**
* Add button
*/
private Button getAddButton()
{
if(mAddButton == null)
{
mAddButton = new Button("Add");
mAddButton.setDisable(true);
mAddButton.setOnAction(event -> addFrequencyBox());
}
return mAddButton;
}
/**
* Remove button
*/
private Button getRemoveButton()
{
if(mRemoveButton == null)
{
mRemoveButton = new Button("Remove");
mRemoveButton.setDisable(true);
mRemoveButton.setOnAction(event -> {
Alert confirm = new Alert(Alert.AlertType.CONFIRMATION,
"Do you want to remove this frequency?", ButtonType.YES, ButtonType.NO);
confirm.setTitle("Remove Frequency");
confirm.setHeaderText("Remove frequency?");
confirm.initOwner(((Node)event.getTarget()).getScene().getWindow());
confirm.showAndWait().ifPresent(buttonType -> {
if(buttonType == ButtonType.YES)
{
removeFrequencyBox(FrequencyBox.this);
}
});
});
}
return mRemoveButton;
}
}
}

View File

@@ -0,0 +1,113 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.source;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.util.converter.DoubleStringConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DecimalFormat;
import java.util.function.UnaryOperator;
/**
* Control for editing frequency values in MHz that allows access/setting value in Hz
*
* Allows values in range: 1.000000 to 9999.999999 MHz
*/
public class FrequencyField extends TextField
{
private final static Logger mLog = LoggerFactory.getLogger(FrequencyField.class);
private DecimalFormat mDecimalFormat = new DecimalFormat("0.000000");
private static final double CONVERT_HZ_TO_MHZ = 0.000001;
private static final double CONVERT_MHZ_TO_HZ = 1000000;
private static final String INTEGER_REGEX = "[1-9]\\d{0,3}";
private static final String FRACTIONAL_REGEX = "[1-9]\\d{0,3}\\.\\d{0,6}";
/**
* Constructs an instance
*/
public FrequencyField()
{
UnaryOperator<TextFormatter.Change> filter = change ->
{
String update = change.getControlNewText();
if(update.matches(INTEGER_REGEX) || update.matches(FRACTIONAL_REGEX) || update.isEmpty())
{
return change;
}
return null;
};
TextFormatter<Double> formatter = new TextFormatter<Double>(new DoubleStringConverter(), null, filter);
setTextFormatter(formatter);
}
/**
* Sets the frequency for this control
* @param frequency in range 0 <> 9,999,999,999 Hz</>
*/
public void set(long frequency)
{
if(frequency < 0 || frequency > 9999999999l)
{
throw new IllegalArgumentException("Frequency [" + frequency + "] must be in range 0 <> 9,999,999,999");
}
if(frequency == 0)
{
setText("");
}
else
{
setText(mDecimalFormat.format(frequency * CONVERT_HZ_TO_MHZ));
}
}
/**
* Retrieves the frequency value from this control
* @return frequency in Hz
*/
public long get()
{
String raw = getText();
if(raw != null)
{
try
{
double mhz = Double.parseDouble(raw);
return (long)(mhz * CONVERT_MHZ_TO_HZ);
}
catch(Exception e)
{
//Ignore ... we'll return 0 for this value
}
}
return 0;
}
}

View File

@@ -0,0 +1,76 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.source;
import io.github.dsheirer.source.config.SourceConfiguration;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.layout.HBox;
public abstract class SourceConfigurationEditor<T extends SourceConfiguration> extends HBox
{
private T mSourceConfiguration;
private BooleanProperty mModifiedProperty = new SimpleBooleanProperty();
public SourceConfigurationEditor()
{
}
/**
* Indicates if this editor's contents have been modified
*/
public BooleanProperty modifiedProperty()
{
return mModifiedProperty;
}
/**
* Disable (true) or enable (false) the controls on this editor
*/
public abstract void disable(boolean disable);
/**
* Saves the (modified) contents in the editor. If the editor is not modified, this is a no-op.
*/
public abstract void save();
/**
* Sets or resets the source configuration in the editor
* @param sourceConfiguration
*/
public void setSourceConfiguration(T sourceConfiguration)
{
mSourceConfiguration = sourceConfiguration;
disable(mSourceConfiguration == null);
}
/**
* Retrieves the source configuration.
* @return source config or null
*/
public T getSourceConfiguration()
{
return mSourceConfiguration;
}
}

View File

@@ -0,0 +1,343 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.streaming;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.gui.control.IntegerTextField;
import io.github.dsheirer.gui.playlist.Editor;
import io.github.dsheirer.playlist.PlaylistManager;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.controlsfx.control.ToggleSwitch;
import java.util.Optional;
/**
* Base class for broadcast configuration editors.
*/
public abstract class AbstractStreamEditor<T extends BroadcastConfiguration> extends Editor<T>
{
private PlaylistManager mPlaylistManager;
private Button mSaveButton;
private Button mResetButton;
private TextField mFormatField;
private TextField mNameTextField;
private TextField mHostTextField;
private TextField mUnMaskedPasswordTextField;
private PasswordField mMaskedPasswordTextField;
private CheckBox mShowPasswordCheckBox;
private IntegerTextField mPortTextField;
private IntegerTextField mDelayTextField;
private IntegerTextField mMaxAgeTextField;
private ToggleSwitch mEnabledSwitch;
protected EditorModificationListener mEditorModificationListener = new EditorModificationListener();
/**
* Constructs an instance
*/
public AbstractStreamEditor(PlaylistManager playlistManager)
{
mPlaylistManager = playlistManager;
VBox buttonBox = new VBox();
buttonBox.setPadding(new Insets(10,10,10,10));
buttonBox.setSpacing(10);
buttonBox.getChildren().addAll(getSaveButton(), getResetButton());
HBox editorBox = new HBox();
HBox.setHgrow(getEditorPane(), Priority.ALWAYS);
editorBox.getChildren().addAll(getEditorPane(), buttonBox);
getChildren().addAll(editorBox);
}
@Override
public void setItem(T item)
{
super.setItem(item);
getNameTextField().setDisable(item == null);
getHostTextField().setDisable(item == null);
getPortTextField().setDisable(item == null);
getMaskedPasswordTextField().setDisable(item == null);
getUnMaskedPasswordTextField().setDisable(item == null);
getEnabledSwitch().setDisable(item == null);
getMaxAgeTextField().setDisable(item == null);
getDelayTextField().setDisable(item == null);
if(item != null)
{
getNameTextField().setText(item.getName());
getHostTextField().setText(item.getHost());
getPortTextField().set(item.getPort());
getMaskedPasswordTextField().setText(item.getPassword());
getEnabledSwitch().setSelected(item.isEnabled());
getMaxAgeTextField().set((int)(item.getMaximumRecordingAge() / 1000)); //Convert millis to seconds
getDelayTextField().set((int)(item.getDelay() / 1000)); //Convert millis to seconds
}
else
{
getNameTextField().setText(null);
getHostTextField().setText(null);
getPortTextField().set(0);
getMaskedPasswordTextField().setText(null);
getEnabledSwitch().setSelected(false);
getMaxAgeTextField().set(0);
getDelayTextField().set(0);
}
modifiedProperty().set(false);
}
@Override
public void dispose()
{
}
@Override
public void save()
{
BroadcastConfiguration configuration = getItem();
if(configuration != null)
{
configuration.setEnabled(getEnabledSwitch().isSelected());
configuration.setHost(getHostTextField().getText());
configuration.setPort(getPortTextField().get());
configuration.setPassword(getMaskedPasswordTextField().getText());
configuration.setDelay(getDelayTextField().get() * 1000); //Convert seconds to millis
configuration.setMaximumRecordingAge(getMaxAgeTextField().get() * 1000); //Convert seconds to millis
//Detect stream name change so that we can update any aliases that might be using the previous name
String previousName = configuration.getName();
String updatedName = getNameTextField().getText();
configuration.setName(getNameTextField().getText());
if(previousName != null && !previousName.isEmpty() && !updatedName.contentEquals(previousName))
{
if(mPlaylistManager.getAliasModel().hasAliasesWithBroadcastChannel(previousName))
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.getButtonTypes().clear();
alert.getButtonTypes().addAll(ButtonType.NO, ButtonType.YES);
alert.setTitle("Update Aliases");
alert.setHeaderText("Rename requires updating aliases for this stream");
alert.setContentText("Do you want to update aliases to new stream name?");
alert.initOwner(((Node)getSaveButton()).getScene().getWindow());
//Workaround for JavaFX KDE on Linux bug in FX 10/11: https://bugs.openjdk.java.net/browse/JDK-8179073
alert.setResizable(true);
alert.onShownProperty().addListener(e -> {
Platform.runLater(() -> alert.setResizable(false));
});
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.YES)
{
mPlaylistManager.getAliasModel().updateBroadcastChannel(previousName, updatedName);
}
}
}
}
modifiedProperty().set(false);
}
public abstract BroadcastServerType getBroadcastServerType();
protected abstract GridPane getEditorPane();
protected TextField getFormatField()
{
if(mFormatField == null)
{
mFormatField = new TextField();
mFormatField.setDisable(true);
}
return mFormatField;
}
protected TextField getNameTextField()
{
if(mNameTextField == null)
{
mNameTextField = new TextField();
mNameTextField.setDisable(true);
mNameTextField.textProperty().addListener(mEditorModificationListener);
}
return mNameTextField;
}
protected TextField getHostTextField()
{
if(mHostTextField == null)
{
mHostTextField = new TextField();
mHostTextField.setDisable(true);
mHostTextField.textProperty().addListener(mEditorModificationListener);
}
return mHostTextField;
}
protected TextField getUnMaskedPasswordTextField()
{
if(mUnMaskedPasswordTextField == null)
{
mUnMaskedPasswordTextField = new TextField();
mUnMaskedPasswordTextField.setDisable(true);
mUnMaskedPasswordTextField.visibleProperty().bind(getShowPasswordCheckBox().selectedProperty());
mUnMaskedPasswordTextField.textProperty().bindBidirectional(getMaskedPasswordTextField().textProperty());
}
return mUnMaskedPasswordTextField;
}
protected PasswordField getMaskedPasswordTextField()
{
if(mMaskedPasswordTextField == null)
{
mMaskedPasswordTextField = new PasswordField();
mMaskedPasswordTextField.setDisable(true);
mMaskedPasswordTextField.visibleProperty().bind(getShowPasswordCheckBox().selectedProperty().not());
mMaskedPasswordTextField.textProperty().addListener(mEditorModificationListener);
}
return mMaskedPasswordTextField;
}
protected CheckBox getShowPasswordCheckBox()
{
if(mShowPasswordCheckBox == null)
{
mShowPasswordCheckBox = new CheckBox("Show");
}
return mShowPasswordCheckBox;
}
protected IntegerTextField getPortTextField()
{
if(mPortTextField == null)
{
mPortTextField = new IntegerTextField();
mPortTextField.setDisable(true);
mPortTextField.textProperty().addListener(mEditorModificationListener);
}
return mPortTextField;
}
protected IntegerTextField getDelayTextField()
{
if(mDelayTextField == null)
{
mDelayTextField = new IntegerTextField();
mDelayTextField.setDisable(true);
mDelayTextField.textProperty().addListener(mEditorModificationListener);
}
return mDelayTextField;
}
protected IntegerTextField getMaxAgeTextField()
{
if(mMaxAgeTextField == null)
{
mMaxAgeTextField = new IntegerTextField();
mMaxAgeTextField.setDisable(true);
mMaxAgeTextField.textProperty().addListener(mEditorModificationListener);
}
return mMaxAgeTextField;
}
protected ToggleSwitch getEnabledSwitch()
{
if(mEnabledSwitch == null)
{
mEnabledSwitch = new ToggleSwitch();
mEnabledSwitch.setDisable(true);
mEnabledSwitch.selectedProperty()
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mEnabledSwitch;
}
private Button getSaveButton()
{
if(mSaveButton == null)
{
mSaveButton = new Button("Save");
mSaveButton.setDisable(true);
mSaveButton.setMaxWidth(Double.MAX_VALUE);
mSaveButton.setOnAction(event -> save());
mSaveButton.disableProperty().bind(modifiedProperty().not());
}
return mSaveButton;
}
private Button getResetButton()
{
if(mResetButton == null)
{
mResetButton = new Button("Reset");
mResetButton.setDisable(true);
mResetButton.setMaxWidth(Double.MAX_VALUE);
mResetButton.setOnAction(event -> setItem(getItem()));
mResetButton.disableProperty().bind(modifiedProperty().not());
}
return mResetButton;
}
/**
* Simple string change listener that sets the editor modified flag to true any time text fields are edited.
*/
public class EditorModificationListener implements ChangeListener<String>
{
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
{
modifiedProperty().set(true);
}
}
}

View File

@@ -0,0 +1,210 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.streaming;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyConfiguration;
import io.github.dsheirer.gui.control.IntegerTextField;
import io.github.dsheirer.playlist.PlaylistManager;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
/**
* Broadcastify streaming configuration editor
*/
public class BroadcastifyStreamEditor extends AbstractStreamEditor<BroadcastifyConfiguration>
{
private GridPane mEditorPane;
private TextField mMountPointTextField;
private IntegerTextField mFeedIdTextField;
public BroadcastifyStreamEditor(PlaylistManager playlistManager)
{
super(playlistManager);
}
@Override
public void setItem(BroadcastifyConfiguration item)
{
super.setItem(item);
getFeedIdTextField().setDisable(item == null);
getMountPointTextField().setDisable(item == null);
if(item != null)
{
getFeedIdTextField().set(item.getFeedID());
getMountPointTextField().setText(item.getMountPoint());
}
else
{
getFeedIdTextField().set(0);
getMountPointTextField().setText(null);
}
modifiedProperty().set(false);
}
@Override
public void save()
{
super.save();
if(getItem() != null)
{
getItem().setFeedID(getFeedIdTextField().get());
getItem().setMountPoint(getMountPointTextField().getText());
}
}
public BroadcastServerType getBroadcastServerType()
{
return BroadcastServerType.BROADCASTIFY;
}
protected GridPane getEditorPane()
{
if(mEditorPane == null)
{
mEditorPane = new GridPane();
mEditorPane.setPadding(new Insets(10, 5, 10,10));
mEditorPane.setVgap(10);
mEditorPane.setHgap(5);
Label formatLabel = new Label("Format");
GridPane.setHalignment(formatLabel, HPos.RIGHT);
GridPane.setConstraints(formatLabel, 0, 0);
mEditorPane.getChildren().add(formatLabel);
getFormatField().setText(getBroadcastServerType().toString());
GridPane.setConstraints(getFormatField(), 1, 0);
mEditorPane.getChildren().add(getFormatField());
Label enabledLabel = new Label("Enabled");
GridPane.setHalignment(enabledLabel, HPos.RIGHT);
GridPane.setConstraints(enabledLabel, 2, 0);
mEditorPane.getChildren().add(enabledLabel);
GridPane.setConstraints(getEnabledSwitch(), 3, 0);
mEditorPane.getChildren().add(getEnabledSwitch());
Label systemLabel = new Label("Name");
GridPane.setHalignment(systemLabel, HPos.RIGHT);
GridPane.setConstraints(systemLabel, 0, 1);
mEditorPane.getChildren().add(systemLabel);
GridPane.setConstraints(getNameTextField(), 1, 1);
mEditorPane.getChildren().add(getNameTextField());
Label hostLabel = new Label("Server");
GridPane.setHalignment(hostLabel, HPos.RIGHT);
GridPane.setConstraints(hostLabel, 0, 2);
mEditorPane.getChildren().add(hostLabel);
GridPane.setConstraints(getHostTextField(), 1, 2);
mEditorPane.getChildren().add(getHostTextField());
Label portLabel = new Label("Port");
GridPane.setHalignment(portLabel, HPos.RIGHT);
GridPane.setConstraints(portLabel, 2, 2);
mEditorPane.getChildren().add(portLabel);
GridPane.setConstraints(getPortTextField(), 3, 2);
mEditorPane.getChildren().add(getPortTextField());
Label mountPointLabel = new Label("Mount Point");
GridPane.setHalignment(mountPointLabel, HPos.RIGHT);
GridPane.setConstraints(mountPointLabel, 0, 3);
mEditorPane.getChildren().add(mountPointLabel);
GridPane.setConstraints(getMountPointTextField(), 1, 3);
mEditorPane.getChildren().add(getMountPointTextField());
Label passwordLabel = new Label("Password");
GridPane.setHalignment(passwordLabel, HPos.RIGHT);
GridPane.setConstraints(passwordLabel, 2, 3);
mEditorPane.getChildren().add(passwordLabel);
GridPane.setConstraints(getMaskedPasswordTextField(), 3, 3);
mEditorPane.getChildren().add(getMaskedPasswordTextField());
GridPane.setConstraints(getUnMaskedPasswordTextField(), 3, 3);
mEditorPane.getChildren().add(getUnMaskedPasswordTextField());
GridPane.setConstraints(getShowPasswordCheckBox(), 4, 3);
mEditorPane.getChildren().add(getShowPasswordCheckBox());
Label maxAgeLabel = new Label("Max Recording Age (seconds)");
GridPane.setHalignment(maxAgeLabel, HPos.RIGHT);
GridPane.setConstraints(maxAgeLabel, 0, 4);
mEditorPane.getChildren().add(maxAgeLabel);
GridPane.setConstraints(getMaxAgeTextField(), 1, 4);
mEditorPane.getChildren().add(getMaxAgeTextField());
Label delayLabel = new Label("Delay (seconds)");
GridPane.setHalignment(delayLabel, HPos.RIGHT);
GridPane.setConstraints(delayLabel, 2, 4);
mEditorPane.getChildren().add(delayLabel);
GridPane.setConstraints(getDelayTextField(), 3, 4);
mEditorPane.getChildren().add(getDelayTextField());
Label feedIdLabel = new Label("Feed ID");
GridPane.setHalignment(feedIdLabel, HPos.RIGHT);
GridPane.setConstraints(feedIdLabel, 0, 5);
getEditorPane().getChildren().add(feedIdLabel);
GridPane.setConstraints(getFeedIdTextField(), 1, 5);
getEditorPane().getChildren().add(getFeedIdTextField());
}
return mEditorPane;
}
private TextField getMountPointTextField()
{
if(mMountPointTextField == null)
{
mMountPointTextField = new TextField();
mMountPointTextField.setDisable(true);
mMountPointTextField.textProperty().addListener(mEditorModificationListener);
}
return mMountPointTextField;
}
private IntegerTextField getFeedIdTextField()
{
if(mFeedIdTextField == null)
{
mFeedIdTextField = new IntegerTextField();
mFeedIdTextField.setDisable(true);
mFeedIdTextField.textProperty().addListener(mEditorModificationListener);
}
return mFeedIdTextField;
}
}

View File

@@ -0,0 +1,43 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.streaming;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.playlist.PlaylistManager;
/**
* Stream configuration editor for Icecast 2.4+ HTTP broadcast
*/
public class IcecastHTTPStreamEditor extends IcecastStreamEditor
{
public IcecastHTTPStreamEditor(PlaylistManager playlistManager)
{
super(playlistManager);
}
@Override
public BroadcastServerType getBroadcastServerType()
{
return BroadcastServerType.ICECAST_HTTP;
}
}

View File

@@ -0,0 +1,280 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.streaming;
import io.github.dsheirer.audio.broadcast.icecast.IcecastConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
/**
* Icecast streaming configuration editor
*/
public abstract class IcecastStreamEditor extends AbstractStreamEditor<IcecastConfiguration>
{
private GridPane mEditorPane;
private TextField mMountPointTextField;
private TextField mUserNameTextField;
private TextField mDescriptionTextField;
private TextField mGenreTextField;
private TextField mURLTextField;
public IcecastStreamEditor(PlaylistManager playlistManager)
{
super(playlistManager);
}
@Override
public void setItem(IcecastConfiguration item)
{
super.setItem(item);
getMountPointTextField().setDisable(item == null);
getUserNameTextField().setDisable(item == null);
getDescriptionTextField().setDisable(item == null);
getGenreTextField().setDisable(item == null);
getURLTextField().setDisable(item == null);
if(item != null)
{
getMountPointTextField().setText(item.getMountPoint());
getUserNameTextField().setText(item.getUserName());
getDescriptionTextField().setText(item.getDescription());
getGenreTextField().setText(item.getGenre());
getURLTextField().setText(item.getURL());
}
else
{
getMountPointTextField().setText(null);
getUserNameTextField().setText(null);
getDescriptionTextField().setText(null);
getGenreTextField().setText(null);
getURLTextField().setText(null);
}
modifiedProperty().set(false);
}
@Override
public void save()
{
super.save();
if(getItem() != null)
{
getItem().setMountPoint(getMountPointTextField().getText());
getItem().setUserName(getUserNameTextField().getText());
getItem().setDescription(getDescriptionTextField().getText());
getItem().setGenre(getGenreTextField().getText());
getItem().setURL(getURLTextField().getText());
}
modifiedProperty().set(false);
}
protected GridPane getEditorPane()
{
if(mEditorPane == null)
{
mEditorPane = new GridPane();
mEditorPane.setPadding(new Insets(10, 5, 10,10));
mEditorPane.setVgap(10);
mEditorPane.setHgap(5);
Label formatLabel = new Label("Format");
GridPane.setHalignment(formatLabel, HPos.RIGHT);
GridPane.setConstraints(formatLabel, 0, 0);
mEditorPane.getChildren().add(formatLabel);
getFormatField().setText(getBroadcastServerType().toString());
GridPane.setConstraints(getFormatField(), 1, 0);
mEditorPane.getChildren().add(getFormatField());
Label enabledLabel = new Label("Enabled");
GridPane.setHalignment(enabledLabel, HPos.RIGHT);
GridPane.setConstraints(enabledLabel, 2, 0);
mEditorPane.getChildren().add(enabledLabel);
GridPane.setConstraints(getEnabledSwitch(), 3, 0);
mEditorPane.getChildren().add(getEnabledSwitch());
Label systemLabel = new Label("Name");
GridPane.setHalignment(systemLabel, HPos.RIGHT);
GridPane.setConstraints(systemLabel, 0, 1);
mEditorPane.getChildren().add(systemLabel);
GridPane.setConstraints(getNameTextField(), 1, 1);
mEditorPane.getChildren().add(getNameTextField());
Label hostLabel = new Label("Server");
GridPane.setHalignment(hostLabel, HPos.RIGHT);
GridPane.setConstraints(hostLabel, 0, 2);
mEditorPane.getChildren().add(hostLabel);
GridPane.setConstraints(getHostTextField(), 1, 2);
mEditorPane.getChildren().add(getHostTextField());
Label portLabel = new Label("Port");
GridPane.setHalignment(portLabel, HPos.RIGHT);
GridPane.setConstraints(portLabel, 2, 2);
mEditorPane.getChildren().add(portLabel);
GridPane.setConstraints(getPortTextField(), 3, 2);
mEditorPane.getChildren().add(getPortTextField());
Label mountPointLabel = new Label("Mount Point");
GridPane.setHalignment(mountPointLabel, HPos.RIGHT);
GridPane.setConstraints(mountPointLabel, 0, 3);
mEditorPane.getChildren().add(mountPointLabel);
GridPane.setConstraints(getMountPointTextField(), 1, 3);
mEditorPane.getChildren().add(getMountPointTextField());
Label userNameLabel = new Label("User Name");
GridPane.setHalignment(userNameLabel, HPos.RIGHT);
GridPane.setConstraints(userNameLabel, 0, 4);
mEditorPane.getChildren().add(userNameLabel);
GridPane.setConstraints(getUserNameTextField(), 1, 4);
mEditorPane.getChildren().add(getUserNameTextField());
Label passwordLabel = new Label("Password");
GridPane.setHalignment(passwordLabel, HPos.RIGHT);
GridPane.setConstraints(passwordLabel, 2, 4);
mEditorPane.getChildren().add(passwordLabel);
GridPane.setConstraints(getMaskedPasswordTextField(), 3, 4);
mEditorPane.getChildren().add(getMaskedPasswordTextField());
GridPane.setConstraints(getUnMaskedPasswordTextField(), 3, 4);
mEditorPane.getChildren().add(getUnMaskedPasswordTextField());
GridPane.setConstraints(getShowPasswordCheckBox(), 4, 4);
mEditorPane.getChildren().add(getShowPasswordCheckBox());
Label maxAgeLabel = new Label("Max Recording Age (seconds)");
GridPane.setHalignment(maxAgeLabel, HPos.RIGHT);
GridPane.setConstraints(maxAgeLabel, 0, 5);
mEditorPane.getChildren().add(maxAgeLabel);
GridPane.setConstraints(getMaxAgeTextField(), 1, 5);
mEditorPane.getChildren().add(getMaxAgeTextField());
Label delayLabel = new Label("Delay (seconds)");
GridPane.setHalignment(delayLabel, HPos.RIGHT);
GridPane.setConstraints(delayLabel, 2, 5);
mEditorPane.getChildren().add(delayLabel);
GridPane.setConstraints(getDelayTextField(), 3, 5);
mEditorPane.getChildren().add(getDelayTextField());
Label descriptionLabel = new Label("Description");
GridPane.setHalignment(descriptionLabel, HPos.RIGHT);
GridPane.setConstraints(descriptionLabel, 0, 6);
mEditorPane.getChildren().add(descriptionLabel);
GridPane.setConstraints(getDescriptionTextField(), 1, 6, 3, 1);
mEditorPane.getChildren().add(getDescriptionTextField());
Label genreLabel = new Label("Genre");
GridPane.setHalignment(genreLabel, HPos.RIGHT);
GridPane.setConstraints(genreLabel, 0, 7);
mEditorPane.getChildren().add(genreLabel);
GridPane.setConstraints(getGenreTextField(), 1, 7, 3, 1);
mEditorPane.getChildren().add(getGenreTextField());
Label urlLabel = new Label("URL");
GridPane.setHalignment(urlLabel, HPos.RIGHT);
GridPane.setConstraints(urlLabel, 0, 8);
mEditorPane.getChildren().add(urlLabel);
GridPane.setConstraints(getURLTextField(), 1, 8, 3, 1);
mEditorPane.getChildren().add(getURLTextField());
}
return mEditorPane;
}
private TextField getMountPointTextField()
{
if(mMountPointTextField == null)
{
mMountPointTextField = new TextField();
mMountPointTextField.setDisable(true);
mMountPointTextField.textProperty().addListener(mEditorModificationListener);
}
return mMountPointTextField;
}
private TextField getUserNameTextField()
{
if(mUserNameTextField == null)
{
mUserNameTextField = new TextField();
mUserNameTextField.setDisable(true);
mUserNameTextField.textProperty().addListener(mEditorModificationListener);
}
return mUserNameTextField;
}
private TextField getDescriptionTextField()
{
if(mDescriptionTextField == null)
{
mDescriptionTextField = new TextField();
mDescriptionTextField.setDisable(true);
mDescriptionTextField.textProperty().addListener(mEditorModificationListener);
}
return mDescriptionTextField;
}
private TextField getGenreTextField()
{
if(mGenreTextField == null)
{
mGenreTextField = new TextField();
mGenreTextField.setDisable(true);
mGenreTextField.textProperty().addListener(mEditorModificationListener);
}
return mGenreTextField;
}
private TextField getURLTextField()
{
if(mURLTextField == null)
{
mURLTextField = new TextField();
mURLTextField.setDisable(true);
mURLTextField.textProperty().addListener(mEditorModificationListener);
}
return mURLTextField;
}
}

View File

@@ -0,0 +1,43 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.streaming;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.playlist.PlaylistManager;
/**
* Stream configuration editor for Icecast 2.3 TCP broadcast
*/
public class IcecastTCPStreamEditor extends IcecastStreamEditor
{
public IcecastTCPStreamEditor(PlaylistManager playlistManager)
{
super(playlistManager);
}
@Override
public BroadcastServerType getBroadcastServerType()
{
return BroadcastServerType.ICECAST_TCP;
}
}

View File

@@ -0,0 +1,238 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.streaming;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
import io.github.dsheirer.playlist.PlaylistManager;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import org.controlsfx.control.ToggleSwitch;
/**
* Shoutcast V1 streaming configuration editor
*/
public class ShoutcastV1StreamEditor extends AbstractStreamEditor<ShoutcastV1Configuration>
{
private GridPane mEditorPane;
private TextField mDescriptionTextField;
private TextField mGenreTextField;
private ToggleSwitch mPublicToggleSwitch;
public ShoutcastV1StreamEditor(PlaylistManager playlistManager)
{
super(playlistManager);
}
@Override
public BroadcastServerType getBroadcastServerType()
{
return BroadcastServerType.SHOUTCAST_V1;
}
@Override
public void setItem(ShoutcastV1Configuration item)
{
super.setItem(item);
getDescriptionTextField().setDisable(item == null);
getGenreTextField().setDisable(item == null);
getPublicToggleSwitch().setDisable(item == null);
if(item != null)
{
getDescriptionTextField().setText(item.getDescription());
getGenreTextField().setText(item.getGenre());
getPublicToggleSwitch().setSelected(item.isPublic());
}
else
{
getDescriptionTextField().setText(null);
getGenreTextField().setText(null);
getPublicToggleSwitch().setSelected(false);
}
modifiedProperty().set(false);
}
@Override
public void save()
{
super.save();
if(getItem() != null)
{
getItem().setDescription(getDescriptionTextField().getText());
getItem().setGenre(getGenreTextField().getText());
getItem().setPublic(getPublicToggleSwitch().isSelected());
}
modifiedProperty().set(false);
}
protected GridPane getEditorPane()
{
if(mEditorPane == null)
{
mEditorPane = new GridPane();
mEditorPane.setPadding(new Insets(10, 5, 10,10));
mEditorPane.setVgap(10);
mEditorPane.setHgap(5);
Label formatLabel = new Label("Format");
GridPane.setHalignment(formatLabel, HPos.RIGHT);
GridPane.setConstraints(formatLabel, 0, 0);
mEditorPane.getChildren().add(formatLabel);
getFormatField().setText(getBroadcastServerType().toString());
GridPane.setConstraints(getFormatField(), 1, 0);
mEditorPane.getChildren().add(getFormatField());
Label enabledLabel = new Label("Enabled");
GridPane.setHalignment(enabledLabel, HPos.RIGHT);
GridPane.setConstraints(enabledLabel, 2, 0);
mEditorPane.getChildren().add(enabledLabel);
GridPane.setConstraints(getEnabledSwitch(), 3, 0);
mEditorPane.getChildren().add(getEnabledSwitch());
Label systemLabel = new Label("Name");
GridPane.setHalignment(systemLabel, HPos.RIGHT);
GridPane.setConstraints(systemLabel, 0, 1);
mEditorPane.getChildren().add(systemLabel);
GridPane.setConstraints(getNameTextField(), 1, 1);
mEditorPane.getChildren().add(getNameTextField());
Label publicLabel = new Label("Public");
GridPane.setHalignment(publicLabel, HPos.RIGHT);
GridPane.setConstraints(publicLabel, 2, 1);
mEditorPane.getChildren().add(publicLabel);
GridPane.setConstraints(getPublicToggleSwitch(), 3, 1);
mEditorPane.getChildren().add(getPublicToggleSwitch());
Label hostLabel = new Label("Server");
GridPane.setHalignment(hostLabel, HPos.RIGHT);
GridPane.setConstraints(hostLabel, 0, 2);
mEditorPane.getChildren().add(hostLabel);
GridPane.setConstraints(getHostTextField(), 1, 2);
mEditorPane.getChildren().add(getHostTextField());
Label portLabel = new Label("Port");
GridPane.setHalignment(portLabel, HPos.RIGHT);
GridPane.setConstraints(portLabel, 2, 2);
mEditorPane.getChildren().add(portLabel);
GridPane.setConstraints(getPortTextField(), 3, 2);
mEditorPane.getChildren().add(getPortTextField());
Label passwordLabel = new Label("Password");
GridPane.setHalignment(passwordLabel, HPos.RIGHT);
GridPane.setConstraints(passwordLabel, 0, 3);
mEditorPane.getChildren().add(passwordLabel);
GridPane.setConstraints(getMaskedPasswordTextField(), 1, 3);
mEditorPane.getChildren().add(getMaskedPasswordTextField());
GridPane.setConstraints(getUnMaskedPasswordTextField(), 1, 3);
mEditorPane.getChildren().add(getUnMaskedPasswordTextField());
GridPane.setConstraints(getShowPasswordCheckBox(), 2, 3);
mEditorPane.getChildren().add(getShowPasswordCheckBox());
Label maxAgeLabel = new Label("Max Recording Age (seconds)");
GridPane.setHalignment(maxAgeLabel, HPos.RIGHT);
GridPane.setConstraints(maxAgeLabel, 0, 4);
mEditorPane.getChildren().add(maxAgeLabel);
GridPane.setConstraints(getMaxAgeTextField(), 1, 4);
mEditorPane.getChildren().add(getMaxAgeTextField());
Label delayLabel = new Label("Delay (seconds)");
GridPane.setHalignment(delayLabel, HPos.RIGHT);
GridPane.setConstraints(delayLabel, 2, 4);
mEditorPane.getChildren().add(delayLabel);
GridPane.setConstraints(getDelayTextField(), 3, 4);
mEditorPane.getChildren().add(getDelayTextField());
Label descriptionLabel = new Label("Description");
GridPane.setHalignment(descriptionLabel, HPos.RIGHT);
GridPane.setConstraints(descriptionLabel, 0, 5);
mEditorPane.getChildren().add(descriptionLabel);
GridPane.setConstraints(getDescriptionTextField(), 1, 5, 3, 1);
mEditorPane.getChildren().add(getDescriptionTextField());
Label genreLabel = new Label("Genre");
GridPane.setHalignment(genreLabel, HPos.RIGHT);
GridPane.setConstraints(genreLabel, 0, 6);
mEditorPane.getChildren().add(genreLabel);
GridPane.setConstraints(getGenreTextField(), 1, 6, 3, 1);
mEditorPane.getChildren().add(getGenreTextField());
}
return mEditorPane;
}
private TextField getDescriptionTextField()
{
if(mDescriptionTextField == null)
{
mDescriptionTextField = new TextField();
mDescriptionTextField.setDisable(true);
mDescriptionTextField.textProperty().addListener(mEditorModificationListener);
}
return mDescriptionTextField;
}
private TextField getGenreTextField()
{
if(mGenreTextField == null)
{
mGenreTextField = new TextField();
mGenreTextField.setDisable(true);
mGenreTextField.textProperty().addListener(mEditorModificationListener);
}
return mGenreTextField;
}
private ToggleSwitch getPublicToggleSwitch()
{
if(mPublicToggleSwitch == null)
{
mPublicToggleSwitch = new ToggleSwitch();
mPublicToggleSwitch.setDisable(true);
mPublicToggleSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mPublicToggleSwitch;
}
}

View File

@@ -0,0 +1,289 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.streaming;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
import io.github.dsheirer.gui.control.IntegerTextField;
import io.github.dsheirer.playlist.PlaylistManager;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import org.controlsfx.control.ToggleSwitch;
/**
* Shoutcast V2 streaming configuration editor
*/
public class ShoutcastV2StreamEditor extends AbstractStreamEditor<ShoutcastV2Configuration>
{
private GridPane mEditorPane;
private TextField mUserIdTextField;
private IntegerTextField mStreamIdTextField;
private TextField mUrlTextField;
private TextField mGenreTextField;
private ToggleSwitch mPublicToggleSwitch;
public ShoutcastV2StreamEditor(PlaylistManager playlistManager)
{
super(playlistManager);
}
@Override
public BroadcastServerType getBroadcastServerType()
{
return BroadcastServerType.SHOUTCAST_V2;
}
@Override
public void setItem(ShoutcastV2Configuration item)
{
super.setItem(item);
getUrlTextField().setDisable(item == null);
getGenreTextField().setDisable(item == null);
getPublicToggleSwitch().setDisable(item == null);
getUserIdTextField().setDisable(item == null);
getStreamIdTextField().setDisable(item == null);
if(item != null)
{
getUrlTextField().setText(item.getURL());
getGenreTextField().setText(item.getGenre());
getPublicToggleSwitch().setSelected(item.isPublic());
getStreamIdTextField().set(item.getStreamID());
getUserIdTextField().setText(item.getUserID());
}
else
{
getUrlTextField().setText(null);
getGenreTextField().setText(null);
getPublicToggleSwitch().setSelected(false);
getStreamIdTextField().set(0);
getUserIdTextField().setText(null);
}
modifiedProperty().set(false);
}
@Override
public void save()
{
super.save();
if(getItem() != null)
{
getItem().setURL(getUrlTextField().getText());
getItem().setGenre(getGenreTextField().getText());
getItem().setPublic(getPublicToggleSwitch().isSelected());
getItem().setUserID(getUserIdTextField().getText());
getItem().setStreamID(getStreamIdTextField().get());
}
modifiedProperty().set(false);
}
protected GridPane getEditorPane()
{
if(mEditorPane == null)
{
mEditorPane = new GridPane();
mEditorPane.setPadding(new Insets(10, 5, 10,10));
mEditorPane.setVgap(10);
mEditorPane.setHgap(5);
Label formatLabel = new Label("Format");
GridPane.setHalignment(formatLabel, HPos.RIGHT);
GridPane.setConstraints(formatLabel, 0, 0);
mEditorPane.getChildren().add(formatLabel);
getFormatField().setText(getBroadcastServerType().toString());
GridPane.setConstraints(getFormatField(), 1, 0);
mEditorPane.getChildren().add(getFormatField());
Label enabledLabel = new Label("Enabled");
GridPane.setHalignment(enabledLabel, HPos.RIGHT);
GridPane.setConstraints(enabledLabel, 2, 0);
mEditorPane.getChildren().add(enabledLabel);
GridPane.setConstraints(getEnabledSwitch(), 3, 0);
mEditorPane.getChildren().add(getEnabledSwitch());
Label systemLabel = new Label("Name");
GridPane.setHalignment(systemLabel, HPos.RIGHT);
GridPane.setConstraints(systemLabel, 0, 1);
mEditorPane.getChildren().add(systemLabel);
GridPane.setConstraints(getNameTextField(), 1, 1);
mEditorPane.getChildren().add(getNameTextField());
Label publicLabel = new Label("Public");
GridPane.setHalignment(publicLabel, HPos.RIGHT);
GridPane.setConstraints(publicLabel, 2, 1);
mEditorPane.getChildren().add(publicLabel);
GridPane.setConstraints(getPublicToggleSwitch(), 3, 1);
mEditorPane.getChildren().add(getPublicToggleSwitch());
Label hostLabel = new Label("Server");
GridPane.setHalignment(hostLabel, HPos.RIGHT);
GridPane.setConstraints(hostLabel, 0, 2);
mEditorPane.getChildren().add(hostLabel);
GridPane.setConstraints(getHostTextField(), 1, 2);
mEditorPane.getChildren().add(getHostTextField());
Label portLabel = new Label("Port");
GridPane.setHalignment(portLabel, HPos.RIGHT);
GridPane.setConstraints(portLabel, 2, 2);
mEditorPane.getChildren().add(portLabel);
GridPane.setConstraints(getPortTextField(), 3, 2);
mEditorPane.getChildren().add(getPortTextField());
Label userIdLabel = new Label("User ID");
GridPane.setHalignment(userIdLabel, HPos.RIGHT);
GridPane.setConstraints(userIdLabel, 0, 3);
mEditorPane.getChildren().add(userIdLabel);
GridPane.setConstraints(getUserIdTextField(), 1, 3);
mEditorPane.getChildren().add(getUserIdTextField());
Label streamIdLabel = new Label("Stream ID");
GridPane.setHalignment(streamIdLabel, HPos.RIGHT);
GridPane.setConstraints(streamIdLabel, 2, 3);
mEditorPane.getChildren().add(streamIdLabel);
GridPane.setConstraints(getStreamIdTextField(), 3, 3);
mEditorPane.getChildren().add(getStreamIdTextField());
Label passwordLabel = new Label("Password");
GridPane.setHalignment(passwordLabel, HPos.RIGHT);
GridPane.setConstraints(passwordLabel, 0, 4);
mEditorPane.getChildren().add(passwordLabel);
GridPane.setConstraints(getMaskedPasswordTextField(), 1, 4);
mEditorPane.getChildren().add(getMaskedPasswordTextField());
GridPane.setConstraints(getUnMaskedPasswordTextField(), 1, 4);
mEditorPane.getChildren().add(getUnMaskedPasswordTextField());
GridPane.setConstraints(getShowPasswordCheckBox(), 2, 4);
mEditorPane.getChildren().add(getShowPasswordCheckBox());
Label maxAgeLabel = new Label("Max Recording Age (seconds)");
GridPane.setHalignment(maxAgeLabel, HPos.RIGHT);
GridPane.setConstraints(maxAgeLabel, 0, 5);
mEditorPane.getChildren().add(maxAgeLabel);
GridPane.setConstraints(getMaxAgeTextField(), 1, 5);
mEditorPane.getChildren().add(getMaxAgeTextField());
Label delayLabel = new Label("Delay (seconds)");
GridPane.setHalignment(delayLabel, HPos.RIGHT);
GridPane.setConstraints(delayLabel, 2, 5);
mEditorPane.getChildren().add(delayLabel);
GridPane.setConstraints(getDelayTextField(), 3, 5);
mEditorPane.getChildren().add(getDelayTextField());
Label descriptionLabel = new Label("Description");
GridPane.setHalignment(descriptionLabel, HPos.RIGHT);
GridPane.setConstraints(descriptionLabel, 0, 6);
mEditorPane.getChildren().add(descriptionLabel);
GridPane.setConstraints(getUrlTextField(), 1, 6, 3, 1);
mEditorPane.getChildren().add(getUrlTextField());
Label genreLabel = new Label("Genre");
GridPane.setHalignment(genreLabel, HPos.RIGHT);
GridPane.setConstraints(genreLabel, 0, 7);
mEditorPane.getChildren().add(genreLabel);
GridPane.setConstraints(getGenreTextField(), 1, 7, 3, 1);
mEditorPane.getChildren().add(getGenreTextField());
}
return mEditorPane;
}
private TextField getUserIdTextField()
{
if(mUserIdTextField == null)
{
mUserIdTextField = new TextField();
mUserIdTextField.setDisable(true);
mUserIdTextField.textProperty().addListener(mEditorModificationListener);
}
return mUserIdTextField;
}
private IntegerTextField getStreamIdTextField()
{
if(mStreamIdTextField == null)
{
mStreamIdTextField = new IntegerTextField();
mStreamIdTextField.setDisable(true);
mStreamIdTextField.textProperty().addListener(mEditorModificationListener);
}
return mStreamIdTextField;
}
private TextField getUrlTextField()
{
if(mUrlTextField == null)
{
mUrlTextField = new TextField();
mUrlTextField.setDisable(true);
mUrlTextField.textProperty().addListener(mEditorModificationListener);
}
return mUrlTextField;
}
private TextField getGenreTextField()
{
if(mGenreTextField == null)
{
mGenreTextField = new TextField();
mGenreTextField.setDisable(true);
mGenreTextField.textProperty().addListener(mEditorModificationListener);
}
return mGenreTextField;
}
private ToggleSwitch getPublicToggleSwitch()
{
if(mPublicToggleSwitch == null)
{
mPublicToggleSwitch = new ToggleSwitch();
mPublicToggleSwitch.setDisable(true);
mPublicToggleSwitch.selectedProperty().addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
}
return mPublicToggleSwitch;
}
}

View File

@@ -0,0 +1,518 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.streaming;
import io.github.dsheirer.alias.Alias;
import io.github.dsheirer.alias.AliasEvent;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
import javafx.collections.ListChangeListener;
import javafx.collections.transformation.FilteredList;
import javafx.collections.transformation.SortedList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconNode;
import org.controlsfx.control.textfield.TextFields;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
/**
* Editor for managing which aliases are configured for a specific broadcast configuration audio stream
*/
public class StreamAliasSelectionEditor extends VBox
{
private final static Logger mLog = LoggerFactory.getLogger(StreamAliasSelectionEditor.class);
private PlaylistManager mPlaylistManager;
private BroadcastConfiguration mSelectedBroadcastConfiguration;
private HBox mSearchBox;
private TextField mSearchField;
private TableView<Alias> mAvailableAliasTableView;
private TableView<Alias> mSelectedAliasTableView;
private Button mAddButton;
private Button mAddAllButton;
private Button mRemoveButton;
private Button mRemoveAllButton;
private FilteredList<Alias> mAvailableFilteredList;
private FilteredList<Alias> mSelectedFilteredList;
private AvailableAliasPredicate mAvailableAliasPredicate = new AvailableAliasPredicate();
private SelectedAliasPredicate mSelectedAliasPredicate = new SelectedAliasPredicate();
/**
* Constructs an instance
* @param playlistManager for access to alias model
*/
public StreamAliasSelectionEditor(PlaylistManager playlistManager)
{
mPlaylistManager = playlistManager;
VBox buttonBox = new VBox();
buttonBox.setMaxHeight(Double.MAX_VALUE);
buttonBox.setAlignment(Pos.CENTER);
buttonBox.setSpacing(5);
buttonBox.getChildren().addAll(getAddAllButton(), getAddButton(), getRemoveButton(), getRemoveAllButton());
VBox availableBox = new VBox();
availableBox.setMaxHeight(Double.MAX_VALUE);
VBox.setVgrow(getAvailableAliasTableView(), Priority.ALWAYS);
availableBox.getChildren().addAll(new Label("Available"), getAvailableAliasTableView());
VBox selectedBox = new VBox();
selectedBox.setMaxHeight(Double.MAX_VALUE);
VBox.setVgrow(getSelectedAliasTableView(), Priority.ALWAYS);
selectedBox.getChildren().addAll(new Label("Selected"), getSelectedAliasTableView());
HBox listsBox = new HBox();
listsBox.setMaxHeight(Double.MAX_VALUE);
listsBox.setPadding(new Insets(5,10,10,10));
listsBox.setSpacing(10);
HBox.setHgrow(availableBox, Priority.ALWAYS);
HBox.setHgrow(selectedBox, Priority.ALWAYS);
listsBox.getChildren().addAll(availableBox, buttonBox, selectedBox);
VBox.setVgrow(listsBox, Priority.ALWAYS);
getChildren().addAll(getSearchBox(), listsBox);
}
public void setBroadcastConfiguration(BroadcastConfiguration broadcastConfiguration)
{
mSearchField.setText(null);
mSelectedBroadcastConfiguration = broadcastConfiguration;
getSearchField().setDisable(mSelectedBroadcastConfiguration == null);
getAvailableAliasTableView().setDisable(mSelectedBroadcastConfiguration == null);
getSelectedAliasTableView().setDisable(mSelectedBroadcastConfiguration == null);
updateListFilters();
}
private FilteredList<Alias> getAvailableFilteredList()
{
if(mAvailableFilteredList == null)
{
mAvailableFilteredList = new FilteredList<>(mPlaylistManager.getAliasModel().aliasList(),
mAvailableAliasPredicate);
}
return mAvailableFilteredList;
}
private FilteredList<Alias> getSelectedFilteredList()
{
if(mSelectedFilteredList == null)
{
mSelectedFilteredList = new FilteredList<>(mPlaylistManager.getAliasModel().aliasList(),
mSelectedAliasPredicate);
}
return mSelectedFilteredList;
}
private HBox getSearchBox()
{
if(mSearchBox == null)
{
mSearchBox = new HBox();
mSearchBox.setAlignment(Pos.CENTER_LEFT);
mSearchBox.setPadding(new Insets(5, 5, 0, 15));
mSearchBox.setSpacing(5);
Label searchLabel = new Label("Search:");
searchLabel.setAlignment(Pos.CENTER_RIGHT);
mSearchBox.getChildren().addAll(searchLabel, getSearchField());
}
return mSearchBox;
}
private TextField getSearchField()
{
if(mSearchField == null)
{
mSearchField = TextFields.createClearableTextField();
mSearchField.setDisable(true);
mSearchField.textProperty().addListener((observable, oldValue, newValue) -> {
updateListFilters();
});
}
return mSearchField;
}
/**
* Updates the predicate filters applied to the available and selected alias lists
*/
private void updateListFilters()
{
String streamName = getSelectedStreamName();
String filterText = getSearchField().getText();
mAvailableAliasPredicate.setStreamName(streamName);
mAvailableAliasPredicate.setFilterText(filterText);
mSelectedAliasPredicate.setStreamName(streamName);
mSelectedAliasPredicate.setFilterText(filterText);
getAvailableFilteredList().setPredicate(null);
getAvailableFilteredList().setPredicate(mAvailableAliasPredicate);
getSelectedFilteredList().setPredicate(null);
getSelectedFilteredList().setPredicate(mSelectedAliasPredicate);
}
/**
* Selected stream name or null
*/
private String getSelectedStreamName()
{
if(mSelectedBroadcastConfiguration != null && mSelectedBroadcastConfiguration.getName() != null &&
!mSelectedBroadcastConfiguration.getName().isEmpty())
{
return mSelectedBroadcastConfiguration.getName();
}
return null;
}
private TableView<Alias> getAvailableAliasTableView()
{
if(mAvailableAliasTableView == null)
{
mAvailableAliasTableView = new TableView<>();
mAvailableAliasTableView.setMaxHeight(Double.MAX_VALUE);
mAvailableAliasTableView.setDisable(true);
mAvailableAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
TableColumn aliasListNameColumn = new TableColumn();
aliasListNameColumn.setText("Alias List");
aliasListNameColumn.setPrefWidth(150);
aliasListNameColumn.setCellValueFactory(new PropertyValueFactory<>("aliasListName"));
TableColumn groupColumn = new TableColumn();
groupColumn.setText("Group");
groupColumn.setPrefWidth(150);
groupColumn.setCellValueFactory(new PropertyValueFactory<>("group"));
TableColumn nameColumn = new TableColumn();
nameColumn.setText("Alias");
nameColumn.setPrefWidth(150);
nameColumn.setCellValueFactory(new PropertyValueFactory<Alias,String>("name"));
mAvailableAliasTableView.getColumns().addAll(aliasListNameColumn, groupColumn, nameColumn);
mAvailableAliasTableView.setPlaceholder(new Label("No aliases available"));
mAvailableAliasTableView.getSelectionModel().getSelectedItems().addListener((ListChangeListener<Alias>)c -> {
int selectedCount = mAvailableAliasTableView.getSelectionModel().getSelectedItems().size();
getAddButton().setDisable(selectedCount != 1);
getAddAllButton().setDisable(selectedCount < 2);
if(selectedCount > 0)
{
getSelectedAliasTableView().getSelectionModel().clearSelection();
}
});
SortedList<Alias> sortedList = new SortedList<>(getAvailableFilteredList());
sortedList.comparatorProperty().bind(mAvailableAliasTableView.comparatorProperty());
mAvailableAliasTableView.setItems(sortedList);
}
return mAvailableAliasTableView;
}
private TableView<Alias> getSelectedAliasTableView()
{
if(mSelectedAliasTableView == null)
{
mSelectedAliasTableView = new TableView<>();
mSelectedAliasTableView.setMaxHeight(Double.MAX_VALUE);
mSelectedAliasTableView.setDisable(true);
mSelectedAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
TableColumn aliasListNameColumn = new TableColumn();
aliasListNameColumn.setText("Alias List");
aliasListNameColumn.setPrefWidth(150);
aliasListNameColumn.setCellValueFactory(new PropertyValueFactory<>("aliasListName"));
TableColumn groupColumn = new TableColumn();
groupColumn.setText("Group");
groupColumn.setPrefWidth(150);
groupColumn.setCellValueFactory(new PropertyValueFactory<>("group"));
TableColumn nameColumn = new TableColumn();
nameColumn.setText("Alias");
nameColumn.setPrefWidth(150);
nameColumn.setCellValueFactory(new PropertyValueFactory<Alias,String>("name"));
mSelectedAliasTableView.getColumns().addAll(aliasListNameColumn, groupColumn, nameColumn);
mSelectedAliasTableView.setPlaceholder(new Label("No aliases selected"));
mSelectedAliasTableView.getSelectionModel().getSelectedItems().addListener((ListChangeListener<Alias>)c -> {
int selectedCount = mSelectedAliasTableView.getSelectionModel().getSelectedItems().size();
getRemoveButton().setDisable(selectedCount != 1);
getRemoveAllButton().setDisable(selectedCount < 2);
if(selectedCount > 0)
{
getAvailableAliasTableView().getSelectionModel().clearSelection();
}
});
SortedList<Alias> sortedList = new SortedList<>(getSelectedFilteredList());
sortedList.comparatorProperty().bind(mSelectedAliasTableView.comparatorProperty());
mSelectedAliasTableView.setItems(sortedList);
}
return mSelectedAliasTableView;
}
private Button getAddButton()
{
if(mAddButton == null)
{
mAddButton = new Button();
mAddButton.setDisable(true);
mAddButton.setMaxWidth(Double.MAX_VALUE);
mAddButton.setGraphic(new IconNode(FontAwesome.ANGLE_RIGHT));
mAddButton.setAlignment(Pos.CENTER);
mAddButton.setOnAction(event -> {
Alias selectedAlias = getAvailableAliasTableView().getSelectionModel().getSelectedItem();
String stream = getSelectedStreamName();
if(selectedAlias != null && stream != null)
{
selectedAlias.addAliasID(new BroadcastChannel(stream));
mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE));
updateListFilters();
}
});
}
return mAddButton;
}
private Button getAddAllButton()
{
if(mAddAllButton == null)
{
mAddAllButton = new Button();
mAddAllButton.setDisable(true);
mAddAllButton.setMaxWidth(Double.MAX_VALUE);
mAddAllButton.setGraphic(new IconNode(FontAwesome.ANGLE_DOUBLE_RIGHT));
mAddAllButton.setAlignment(Pos.CENTER);
mAddAllButton.setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
List<Alias> selectedAliases =
new ArrayList(getAvailableAliasTableView().getSelectionModel().getSelectedItems());
String stream = getSelectedStreamName();
if(!selectedAliases.isEmpty() && stream != null)
{
for(Alias selectedAlias: selectedAliases)
{
selectedAlias.addAliasID(new BroadcastChannel(stream));
mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE));
}
updateListFilters();
}
}
});
}
return mAddAllButton;
}
private Button getRemoveButton()
{
if(mRemoveButton == null)
{
mRemoveButton = new Button();
mRemoveButton.setDisable(true);
mRemoveButton.setMaxWidth(Double.MAX_VALUE);
mRemoveButton.setGraphic(new IconNode(FontAwesome.ANGLE_LEFT));
mRemoveButton.setAlignment(Pos.CENTER);
mRemoveButton.setOnAction(event -> {
Alias selectedAlias = getSelectedAliasTableView().getSelectionModel().getSelectedItem();
String stream = getSelectedStreamName();
if(selectedAlias != null && stream != null)
{
selectedAlias.removeBroadcastChannel(stream);
mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE));
updateListFilters();
}
});
}
return mRemoveButton;
}
private Button getRemoveAllButton()
{
if(mRemoveAllButton == null)
{
mRemoveAllButton = new Button();
mRemoveAllButton.setDisable(true);
mRemoveAllButton.setMaxWidth(Double.MAX_VALUE);
mRemoveAllButton.setGraphic(new IconNode(FontAwesome.ANGLE_DOUBLE_LEFT));
mRemoveAllButton.setAlignment(Pos.CENTER);
mRemoveAllButton.setOnAction(event -> {
List<Alias> selectedAliases =
new ArrayList(getSelectedAliasTableView().getSelectionModel().getSelectedItems());
String stream = getSelectedStreamName();
if(!selectedAliases.isEmpty() && stream != null)
{
for(Alias selectedAlias: selectedAliases)
{
selectedAlias.removeBroadcastChannel(stream);
mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE));
}
updateListFilters();
}
});
}
return mRemoveAllButton;
}
/**
* Predicate for filtering aliases by stream name and optional filter text
*/
public static class AvailableAliasPredicate implements Predicate<Alias>
{
private String mStreamName;
private String mFilterText;
public AvailableAliasPredicate()
{
}
public void setStreamName(String streamName)
{
mStreamName = streamName;
}
public void setFilterText(String filterText)
{
mFilterText = (filterText == null || filterText.isEmpty()) ? null : filterText.toLowerCase();
}
@Override
public boolean test(Alias alias)
{
if(mStreamName == null || alias.hasBroadcastChannel(mStreamName))
{
return false;
}
if(mFilterText == null)
{
return true;
}
if(alias.getAliasListName() != null && alias.getAliasListName().toLowerCase().contains(mFilterText))
{
return true;
}
if(alias.getGroup() != null && alias.getGroup().toLowerCase().contains(mFilterText))
{
return true;
}
if(alias.getName() != null && alias.getName().toLowerCase().contains(mFilterText))
{
return true;
}
return false;
}
}
/**
* Predicate for filtering aliases by stream name and optional filter text
*/
public static class SelectedAliasPredicate implements Predicate<Alias>
{
private String mStreamName;
private String mFilterText;
public SelectedAliasPredicate()
{
}
public void setStreamName(String streamName)
{
mStreamName = streamName;
}
public void setFilterText(String filterText)
{
mFilterText = (filterText == null || filterText.isEmpty()) ? null : filterText.toLowerCase();
}
@Override
public boolean test(Alias alias)
{
if(!alias.hasBroadcastChannel(mStreamName))
{
return false;
}
if(mFilterText == null)
{
return true;
}
if(alias.getAliasListName() != null && alias.getAliasListName().toLowerCase().contains(mFilterText))
{
return true;
}
if(alias.getGroup() != null && alias.getGroup().toLowerCase().contains(mFilterText))
{
return true;
}
if(alias.getName() != null && alias.getName().toLowerCase().contains(mFilterText))
{
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,56 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.streaming;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.playlist.PlaylistManager;
/**
* Factory for creating broadcast configuration editors
*/
public class StreamEditorFactory
{
/**
* Creates a new editor for the specified broadcast server type
* @param broadcastServerType to edit
* @return editor or the default unknown editor
*/
public static AbstractStreamEditor getEditor(BroadcastServerType broadcastServerType, PlaylistManager playlistManager)
{
switch(broadcastServerType)
{
case BROADCASTIFY:
return new BroadcastifyStreamEditor(playlistManager);
case ICECAST_HTTP:
return new IcecastHTTPStreamEditor(playlistManager);
case ICECAST_TCP:
return new IcecastTCPStreamEditor(playlistManager);
case SHOUTCAST_V1:
return new ShoutcastV1StreamEditor(playlistManager);
case SHOUTCAST_V2:
return new ShoutcastV2StreamEditor(playlistManager);
default:
return new UnknownStreamEditor(playlistManager);
}
}
}

View File

@@ -0,0 +1,521 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.streaming;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastFactory;
import io.github.dsheirer.audio.broadcast.BroadcastFormat;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.audio.broadcast.ConfiguredBroadcast;
import io.github.dsheirer.audio.broadcast.broadcastify.BroadcastifyConfiguration;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.rrapi.type.UserFeedBroadcast;
import io.github.dsheirer.util.ThreadPool;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SplitPane;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.javafx.IconNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* Editor for broadcast audio stream configurations
*/
public class StreamingEditor extends SplitPane
{
private final static Logger mLog = LoggerFactory.getLogger(StreamingEditor.class);
private PlaylistManager mPlaylistManager;
private TableView<ConfiguredBroadcast> mConfiguredBroadcastTableView;
private MenuButton mNewButton;
private Button mDeleteButton;
private TabPane mTabPane;
private Tab mConfigurationTab;
private Tab mAliasTab;
private Label mRadioReferenceLoginLabel;
private AbstractStreamEditor mCurrentEditor;
private UnknownStreamEditor mUnknownEditor;
private Map<BroadcastServerType, AbstractStreamEditor> mEditorMap = new HashMap();
private List<UserFeedBroadcast> mBroadcastifyFeeds = new ArrayList<>();
private ScrollPane mEditorScrollPane;
private StreamAliasSelectionEditor mStreamAliasSelectionEditor;
private StreamConfigurationEditorModificationListener mStreamConfigurationEditorModificationListener =
new StreamConfigurationEditorModificationListener();
/**
* Constructs an instance
* @param playlistManager for accessing streaming model
*/
public StreamingEditor(PlaylistManager playlistManager)
{
mPlaylistManager = playlistManager;
mUnknownEditor = new UnknownStreamEditor(mPlaylistManager);
mPlaylistManager.getRadioReference().availableProperty()
.addListener((observable, oldValue, newValue) -> refreshBroadcastifyStreams());
refreshBroadcastifyStreams();
VBox buttonsBox = new VBox();
buttonsBox.getChildren().addAll(getNewButton(), getDeleteButton());
buttonsBox.setPadding(new Insets(0, 0, 0, 10));
buttonsBox.setSpacing(10);
VBox tableAndLabelBox = new VBox();
VBox.setVgrow(getConfiguredBroadcastTableView(), Priority.ALWAYS);
tableAndLabelBox.getChildren().addAll(getConfiguredBroadcastTableView(), getRadioReferenceLoginLabel());
HBox editorBox = new HBox();
editorBox.setPadding(new Insets(10, 10, 10, 10));
HBox.setHgrow(tableAndLabelBox, Priority.ALWAYS);
editorBox.getChildren().addAll(tableAndLabelBox, buttonsBox);
editorBox.setPrefHeight(50);
setOrientation(Orientation.VERTICAL);
getItems().addAll(editorBox, getTabPane());
}
private void setEditor(AbstractStreamEditor<?> editor)
{
if(editor != getCurrentEditor())
{
if(mCurrentEditor != null)
{
mCurrentEditor.modifiedProperty().removeListener(mStreamConfigurationEditorModificationListener);
}
mCurrentEditor = editor;
//Register a listener on the editor modified property to detect configuration changes and refresh the
//aliases tab
mCurrentEditor.modifiedProperty().addListener(mStreamConfigurationEditorModificationListener);
getEditorScrollPane().setContent(getCurrentEditor());
}
}
private ScrollPane getEditorScrollPane()
{
if(mEditorScrollPane == null)
{
mEditorScrollPane = new ScrollPane();
mEditorScrollPane.setMaxWidth(Double.MAX_VALUE);
mEditorScrollPane.setFitToWidth(true);
mEditorScrollPane.setContent(getCurrentEditor());
}
return mEditorScrollPane;
}
private void setBroadcastConfiguration(ConfiguredBroadcast configuredBroadcast)
{
//Prompt the user to save if the contents of the current channel editor have been modified
if(getCurrentEditor().modifiedProperty().get())
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.getButtonTypes().clear();
alert.getButtonTypes().addAll(ButtonType.NO, ButtonType.YES);
alert.setTitle("Save Changes");
alert.setHeaderText("Streaming configuration has been modified");
alert.setContentText("Do you want to save these changes?");
alert.initOwner(((Node)getNewButton()).getScene().getWindow());
//Workaround for JavaFX KDE on Linux bug in FX 10/11: https://bugs.openjdk.java.net/browse/JDK-8179073
alert.setResizable(true);
alert.onShownProperty().addListener(e -> {
Platform.runLater(() -> alert.setResizable(false));
});
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.YES)
{
getCurrentEditor().save();
}
}
getDeleteButton().setDisable(configuredBroadcast == null);
if(configuredBroadcast == null)
{
setEditor(mUnknownEditor);
}
else
{
BroadcastServerType configType = configuredBroadcast.getBroadcastServerType();
if(configType == null)
{
setEditor(mUnknownEditor);
}
else
{
BroadcastServerType editorType = getCurrentEditor().getBroadcastServerType();
if(editorType == null || editorType != configType)
{
AbstractStreamEditor editor = mEditorMap.get(configType);
if(editor == null)
{
editor = StreamEditorFactory.getEditor(configType, mPlaylistManager);
if(editor != null)
{
mEditorMap.put(configType, editor);
}
}
if(editor == null)
{
editor = mUnknownEditor;
}
setEditor(editor);
}
}
}
BroadcastConfiguration broadcastConfiguration = configuredBroadcast != null ?
configuredBroadcast.getBroadcastConfiguration() : null;
getCurrentEditor().setItem(broadcastConfiguration);
getStreamAliasSelectionEditor().setBroadcastConfiguration(broadcastConfiguration);
}
/**
* Updates the list of broadcastify stream configurations if the service is logged in.
*/
private void refreshBroadcastifyStreams()
{
if(mPlaylistManager.getRadioReference().availableProperty().get())
{
ThreadPool.SCHEDULED.submit(new Runnable()
{
@Override
public void run()
{
try
{
List<UserFeedBroadcast> feeds = mPlaylistManager.getRadioReference().getService().getUserFeeds();
mBroadcastifyFeeds.clear();
mBroadcastifyFeeds.addAll(feeds);
}
catch(Throwable t)
{
mLog.error("Unable to refresh broadcastify stream configuration(s)");
}
}
});
}
}
private StreamAliasSelectionEditor getStreamAliasSelectionEditor()
{
if(mStreamAliasSelectionEditor == null)
{
mStreamAliasSelectionEditor = new StreamAliasSelectionEditor(mPlaylistManager);
}
return mStreamAliasSelectionEditor;
}
private Label getRadioReferenceLoginLabel()
{
if(mRadioReferenceLoginLabel == null)
{
mRadioReferenceLoginLabel = new Label("Note: use Radio Reference tab to login and access Broadcastify stream configuration(s)");
mRadioReferenceLoginLabel.visibleProperty().bind(mPlaylistManager.getRadioReference().availableProperty().not());
}
return mRadioReferenceLoginLabel;
}
private AbstractStreamEditor getCurrentEditor()
{
if(mCurrentEditor == null)
{
mCurrentEditor = mUnknownEditor;
}
return mCurrentEditor;
}
private TabPane getTabPane()
{
if(mTabPane == null)
{
mTabPane = new TabPane();
mTabPane.setMaxHeight(Double.MAX_VALUE);
mTabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE);
mTabPane.getTabs().addAll(getConfigurationTab(), getAliasTab());
}
return mTabPane;
}
private Tab getConfigurationTab()
{
if(mConfigurationTab == null)
{
mConfigurationTab = new Tab("Configuration");
mConfigurationTab.setContent(getEditorScrollPane());
}
return mConfigurationTab;
}
private Tab getAliasTab()
{
if(mAliasTab == null)
{
mAliasTab = new Tab("Aliases");
mAliasTab.setContent(getStreamAliasSelectionEditor());
}
return mAliasTab;
}
private MenuButton getNewButton()
{
if(mNewButton == null)
{
mNewButton = new MenuButton("New");
mNewButton.setMaxWidth(Double.MAX_VALUE);
mNewButton.setOnShowing(event -> {
mNewButton.getItems().clear();
for(UserFeedBroadcast feed: mBroadcastifyFeeds)
{
//Only show a menu item for the feed if it's not already defined
if(mPlaylistManager.getBroadcastModel().getBroadcastConfiguration(feed.getDescription()) == null)
{
mNewButton.getItems().add(new CreateBroadcastifyMenuItem(feed));
}
}
for(BroadcastServerType type: BroadcastServerType.values())
{
if(type != BroadcastServerType.BROADCASTIFY && type != BroadcastServerType.UNKNOWN)
{
mNewButton.getItems().add(new CreateBroadcastConfigurationMenuItem(type));
}
}
});
}
return mNewButton;
}
private Button getDeleteButton()
{
if(mDeleteButton == null)
{
mDeleteButton = new Button("Delete");
mDeleteButton.setMaxWidth(Double.MAX_VALUE);
mDeleteButton.setOnAction(event -> {
BroadcastConfiguration config = getConfiguredBroadcastTableView().getSelectionModel()
.getSelectedItem().getBroadcastConfiguration();
if(config != null)
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION,
"Do you want to delete the selected stream?", ButtonType.NO, ButtonType.YES);
alert.setTitle("Delete Stream Configuration");
alert.setHeaderText("Are you sure?");
alert.initOwner(((Node)getDeleteButton()).getScene().getWindow());
Optional<ButtonType> result = alert.showAndWait();
if(result.get() == ButtonType.YES)
{
mPlaylistManager.getBroadcastModel().removeBroadcastConfiguration(config);
}
}
});
}
return mDeleteButton;
}
private TableView<ConfiguredBroadcast> getConfiguredBroadcastTableView()
{
if(mConfiguredBroadcastTableView == null)
{
mConfiguredBroadcastTableView = new TableView<>();
mConfiguredBroadcastTableView.setPlaceholder(new Label("Click the New button to create a new " +
"audio streaming configuration"));
mConfiguredBroadcastTableView.setItems(mPlaylistManager.getBroadcastModel().getConfiguredBroadcasts());
TableColumn<ConfiguredBroadcast,Boolean> enabledColumn = new TableColumn("Enabled");
enabledColumn.setCellValueFactory(new PropertyValueFactory<>("enabled"));
enabledColumn.setCellFactory(param -> {
TableCell<ConfiguredBroadcast,Boolean> tableCell = new TableCell<>()
{
@Override
protected void updateItem(Boolean item, boolean empty)
{
setAlignment(Pos.CENTER);
setText(null);
if(empty || item == null || !item)
{
setGraphic(null);
}
else
{
IconNode iconNode = new IconNode(FontAwesome.CHECK);
iconNode.setFill(Color.GREEN);
setGraphic(iconNode);
}
}
};
return tableCell;
});
TableColumn nameColumn = new TableColumn("Name");
nameColumn.setPrefWidth(300);
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
TableColumn typeColumn = new TableColumn();
typeColumn.setPrefWidth(125);
typeColumn.setText("Format");
typeColumn.setCellValueFactory(new PropertyValueFactory<>("broadcastServerType"));
TableColumn stateColumn = new TableColumn("Stream Status");
stateColumn.setCellValueFactory(new PropertyValueFactory<>("broadcastState"));
mConfiguredBroadcastTableView.getColumns().addAll(enabledColumn, nameColumn, typeColumn, stateColumn);
mConfiguredBroadcastTableView.getSelectionModel().selectedItemProperty()
.addListener((observable, oldValue, newValue) -> setBroadcastConfiguration(newValue));
}
return mConfiguredBroadcastTableView;
}
/**
* Menu item to create a broadcastify configuration
*/
public class CreateBroadcastifyMenuItem extends MenuItem
{
private UserFeedBroadcast mUserFeedBroadcast;
public CreateBroadcastifyMenuItem(UserFeedBroadcast userFeedBroadcast)
{
mUserFeedBroadcast = userFeedBroadcast;
setText("Broadcastify: " + mUserFeedBroadcast.getDescription());
setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
BroadcastConfiguration configuration = BroadcastifyConfiguration.from(mUserFeedBroadcast);
if(configuration != null)
{
ConfiguredBroadcast configuredBroadcast = mPlaylistManager.getBroadcastModel()
.addBroadcastConfiguration(configuration);
getConfiguredBroadcastTableView().getSelectionModel().select(configuredBroadcast);
}
}
});
}
}
/**
* Menu item to create a new broadcast configuration
*/
public class CreateBroadcastConfigurationMenuItem extends MenuItem
{
private BroadcastServerType mBroadcastServerType;
public CreateBroadcastConfigurationMenuItem(BroadcastServerType type)
{
setText(type.toString());
mBroadcastServerType = type;
setOnAction(new EventHandler<ActionEvent>()
{
@Override
public void handle(ActionEvent event)
{
BroadcastConfiguration config = BroadcastFactory.getConfiguration(mBroadcastServerType, BroadcastFormat.MP3);
ConfiguredBroadcast configuredBroadcast = mPlaylistManager.getBroadcastModel()
.addBroadcastConfiguration(config);
getConfiguredBroadcastTableView().getSelectionModel().select(configuredBroadcast);
}
});
}
}
public class StreamConfigurationEditorModificationListener implements ChangeListener<Boolean>
{
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
{
//Only fire when the modification property changes from true to false. Set the selection to null and then
//reselect the broadcast to get the streams tab to refresh
if(oldValue != null && newValue != null && oldValue && !newValue)
{
ConfiguredBroadcast configuredBroadcast = getConfiguredBroadcastTableView().getSelectionModel().getSelectedItem();
if(configuredBroadcast != null)
{
getConfiguredBroadcastTableView().getSelectionModel().select(null);
getConfiguredBroadcastTableView().getSelectionModel().select(configuredBroadcast);
}
}
}
}
}

View File

@@ -0,0 +1,142 @@
/*
*
* * ******************************************************************************
* * Copyright (C) 2014-2020 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.playlist.streaming;
import io.github.dsheirer.audio.broadcast.BroadcastConfiguration;
import io.github.dsheirer.audio.broadcast.BroadcastServerType;
import io.github.dsheirer.playlist.PlaylistManager;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
/**
* Default broadcast configuration editor for unknown server types
*/
public class UnknownStreamEditor extends AbstractStreamEditor<BroadcastConfiguration>
{
private GridPane mEditorPane;
public UnknownStreamEditor(PlaylistManager playlistManager)
{
super(playlistManager);
}
public BroadcastServerType getBroadcastServerType()
{
return BroadcastServerType.UNKNOWN;
}
@Override
public void setItem(BroadcastConfiguration item)
{
super.setItem(item);
getNameTextField().setDisable(true);
getHostTextField().setDisable(true);
getPortTextField().setDisable(true);
getMaskedPasswordTextField().setDisable(true);
getEnabledSwitch().setDisable(true);
getMaxAgeTextField().setDisable(true);
getDelayTextField().setDisable(true);
}
protected GridPane getEditorPane()
{
if(mEditorPane == null)
{
mEditorPane = new GridPane();
mEditorPane.setPadding(new Insets(10, 5, 10,10));
mEditorPane.setVgap(10);
mEditorPane.setHgap(5);
Label formatLabel = new Label("Format");
GridPane.setHalignment(formatLabel, HPos.RIGHT);
GridPane.setConstraints(formatLabel, 0, 0);
mEditorPane.getChildren().add(formatLabel);
getFormatField().setText(getBroadcastServerType().name());
GridPane.setConstraints(getFormatField(), 1, 0);
mEditorPane.getChildren().add(getFormatField());
Label enabledLabel = new Label("Enabled");
GridPane.setHalignment(enabledLabel, HPos.RIGHT);
GridPane.setConstraints(enabledLabel, 2, 0);
mEditorPane.getChildren().add(enabledLabel);
GridPane.setConstraints(getEnabledSwitch(), 3, 0);
mEditorPane.getChildren().add(getEnabledSwitch());
Label systemLabel = new Label("Name");
GridPane.setHalignment(systemLabel, HPos.RIGHT);
GridPane.setConstraints(systemLabel, 0, 1);
mEditorPane.getChildren().add(systemLabel);
GridPane.setConstraints(getNameTextField(), 1, 1);
mEditorPane.getChildren().add(getNameTextField());
Label hostLabel = new Label("Host");
GridPane.setHalignment(hostLabel, HPos.RIGHT);
GridPane.setConstraints(hostLabel, 0, 2);
mEditorPane.getChildren().add(hostLabel);
GridPane.setConstraints(getHostTextField(), 1, 2);
mEditorPane.getChildren().add(getHostTextField());
Label portLabel = new Label("Port");
GridPane.setHalignment(portLabel, HPos.RIGHT);
GridPane.setConstraints(portLabel, 2, 2);
mEditorPane.getChildren().add(portLabel);
GridPane.setConstraints(getPortTextField(), 3, 2);
mEditorPane.getChildren().add(getPortTextField());
Label passwordLabel = new Label("Password");
GridPane.setHalignment(passwordLabel, HPos.RIGHT);
GridPane.setConstraints(passwordLabel, 0, 3);
mEditorPane.getChildren().add(passwordLabel);
GridPane.setConstraints(getMaskedPasswordTextField(), 1, 3);
mEditorPane.getChildren().add(getMaskedPasswordTextField());
Label maxAgeLabel = new Label("Max Recording Age (seconds)");
GridPane.setHalignment(maxAgeLabel, HPos.RIGHT);
GridPane.setConstraints(maxAgeLabel, 0, 4);
mEditorPane.getChildren().add(maxAgeLabel);
GridPane.setConstraints(getMaxAgeTextField(), 1, 4);
mEditorPane.getChildren().add(getMaxAgeTextField());
Label delayLabel = new Label("Delay (seconds)");
GridPane.setHalignment(delayLabel, HPos.RIGHT);
GridPane.setConstraints(delayLabel, 2, 4);
mEditorPane.getChildren().add(delayLabel);
GridPane.setConstraints(getDelayTextField(), 3, 4);
mEditorPane.getChildren().add(getDelayTextField());
}
return mEditorPane;
}
}

View File

@@ -37,7 +37,7 @@ public class PreferenceEditorFactory
{
switch(preferenceEditorType)
{
case AUDIO_PLAYBACK:
case AUDIO_OUTPUT:
return new PlaybackPreferenceEditor(userPreferences);
case AUDIO_RECORD:
return new RecordPreferenceEditor(userPreferences);

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