mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
* 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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
139
src/main/java/io/github/dsheirer/gui/control/HexFormatter.java
Normal file
139
src/main/java/io/github/dsheirer/gui/control/HexFormatter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
230
src/main/java/io/github/dsheirer/gui/control/LtrFormatter.java
Normal file
230
src/main/java/io/github/dsheirer/gui/control/LtrFormatter.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
|
||||
70
src/main/java/io/github/dsheirer/gui/playlist/Editor.java
Normal file
70
src/main/java/io/github/dsheirer/gui/playlist/Editor.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
{
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
{
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user