mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
* #6 DMR decoder feature - work in progress * #6 DMR Decoder. * #6 DMR Decoder. Code review updates. Co-authored-by: Zhenyu Mao <maozhenyu@foxmail.com> Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
@@ -120,7 +120,7 @@ public class JavaFxWindowManager extends Application
|
||||
private void setup()
|
||||
{
|
||||
//Register this class to receive events via each method annotated with @Subscribe
|
||||
MyEventBus.getEventBus().register(this);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
|
||||
//Register JavaFX icon fonts
|
||||
IconFontFX.register(jiconfont.icons.font_awesome.FontAwesome.getIconFont());
|
||||
@@ -162,6 +162,8 @@ public class JavaFxWindowManager extends Application
|
||||
*/
|
||||
public void shutdown()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
|
||||
if(mChannelMapStage != null)
|
||||
{
|
||||
mUserPreferences.getJavaFxPreferences().unmonitor(mChannelMapStage);
|
||||
|
||||
@@ -337,15 +337,15 @@ public class SDRTrunk implements Listener<TunerEvent>
|
||||
JMenu viewMenu = new JMenu("View");
|
||||
|
||||
JMenuItem viewPlaylistItem = new JMenuItem("Playlist Editor");
|
||||
viewPlaylistItem.addActionListener(e -> MyEventBus.getEventBus().post(new ViewPlaylistRequest()));
|
||||
viewPlaylistItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewPlaylistRequest()));
|
||||
viewMenu.add(viewPlaylistItem);
|
||||
|
||||
JMenuItem preferencesItem = new JMenuItem("User Preferences");
|
||||
preferencesItem.addActionListener(e -> MyEventBus.getEventBus().post(new ViewUserPreferenceEditorRequest()));
|
||||
preferencesItem.addActionListener(e -> MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest()));
|
||||
viewMenu.add(preferencesItem);
|
||||
|
||||
JMenuItem settingsMenu = new JMenuItem("Icon Manager");
|
||||
settingsMenu.addActionListener(arg0 -> MyEventBus.getEventBus().post(new ViewIconManagerRequest()));
|
||||
settingsMenu.addActionListener(arg0 -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
|
||||
viewMenu.add(settingsMenu);
|
||||
|
||||
JMenuItem logFilesMenu = new JMenuItem("Logs & Recordings");
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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.instrument.decoder;
|
||||
|
||||
import io.github.dsheirer.gui.instrument.chart.ComplexSampleLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.DoubleLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.EyeDiagramChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.PhaseLineChart;
|
||||
import io.github.dsheirer.gui.instrument.chart.SymbolChart;
|
||||
import io.github.dsheirer.message.IMessage;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.dmr.DMRDecoderInstrumented;
|
||||
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import io.github.dsheirer.sample.buffer.ReusableBufferBroadcaster;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class DMRPane extends ComplexDecoderPane
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DMRPane.class);
|
||||
|
||||
private HBox mSampleChartBox;
|
||||
private ComplexSampleLineChart mSampleLineChart;
|
||||
private EyeDiagramChart mEyeDiagramChart;
|
||||
private HBox mDecoderChartBox;
|
||||
private SymbolChart mSymbolChart;
|
||||
private PhaseLineChart mPLLPhaseErrorLineChart;
|
||||
private DoubleLineChart mPLLFrequencyLineChart;
|
||||
private DoubleLineChart mSamplesPerSymbolLineChart;
|
||||
private ReusableBufferBroadcaster mFilteredBufferBroadcaster = new ReusableBufferBroadcaster();
|
||||
private DMRDecoderInstrumented mDecoder = new DMRDecoderInstrumented(new DecodeConfigDMR());
|
||||
|
||||
public DMRPane()
|
||||
{
|
||||
super(DecoderType.P25_PHASE1);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
addListener(getDecoder());
|
||||
|
||||
getDecoder().setFilteredBufferListener(mFilteredBufferBroadcaster);
|
||||
getDecoder().setComplexSymbolListener(getSymbolChart());
|
||||
getDecoder().setPLLPhaseErrorListener(getPLLPhaseErrorLineChart());
|
||||
getDecoder().setPLLFrequencyListener(getPLLFrequencyLineChart());
|
||||
getDecoder().setSymbolDecisionDataListener(getEyeDiagramChart());
|
||||
getDecoder().setSamplesPerSymbolListener(getSamplesPerSymbolLineChart());
|
||||
mFilteredBufferBroadcaster.addListener(getSampleLineChart());
|
||||
|
||||
HBox.setHgrow(getSampleChartBox(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getDecoderChartBox(), Priority.ALWAYS);
|
||||
getChildren().addAll(getSampleChartBox(), getDecoderChartBox());
|
||||
|
||||
mDecoder.setMessageListener(new Listener<IMessage>()
|
||||
{
|
||||
@Override
|
||||
public void receive(IMessage message)
|
||||
{
|
||||
mLog.debug(message.toString());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSampleRate(double sampleRate)
|
||||
{
|
||||
mLog.debug("Configuring for sample rate: " + sampleRate);
|
||||
|
||||
mDecoder.setSampleRate(sampleRate);
|
||||
double samplesPerSymbol = sampleRate / 4800.0;
|
||||
|
||||
getSampleLineChart().setSamplesPerSymbol((int) samplesPerSymbol);
|
||||
}
|
||||
|
||||
private DMRDecoderInstrumented getDecoder()
|
||||
{
|
||||
return mDecoder;
|
||||
}
|
||||
|
||||
private SymbolChart getSymbolChart()
|
||||
{
|
||||
if(mSymbolChart == null)
|
||||
{
|
||||
mSymbolChart = new SymbolChart(10);
|
||||
}
|
||||
|
||||
return mSymbolChart;
|
||||
}
|
||||
|
||||
private HBox getDecoderChartBox()
|
||||
{
|
||||
if(mDecoderChartBox == null)
|
||||
{
|
||||
mDecoderChartBox = new HBox();
|
||||
mDecoderChartBox.setMaxHeight(Double.MAX_VALUE);
|
||||
getSymbolChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getPLLPhaseErrorLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getPLLFrequencyLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(getSymbolChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getPLLPhaseErrorLineChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getPLLFrequencyLineChart(), Priority.ALWAYS);
|
||||
mDecoderChartBox.getChildren().addAll(getSymbolChart(), getPLLPhaseErrorLineChart(),
|
||||
getPLLFrequencyLineChart());
|
||||
}
|
||||
|
||||
return mDecoderChartBox;
|
||||
}
|
||||
|
||||
private HBox getSampleChartBox()
|
||||
{
|
||||
if(mSampleChartBox == null)
|
||||
{
|
||||
mSampleChartBox = new HBox();
|
||||
mSampleChartBox.setMaxHeight(Double.MAX_VALUE);
|
||||
getSampleLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getEyeDiagramChart().setMaxWidth(Double.MAX_VALUE);
|
||||
getSamplesPerSymbolLineChart().setMaxWidth(Double.MAX_VALUE);
|
||||
HBox.setHgrow(getSampleLineChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getEyeDiagramChart(), Priority.ALWAYS);
|
||||
HBox.setHgrow(getSamplesPerSymbolLineChart(), Priority.ALWAYS);
|
||||
mSampleChartBox.getChildren().addAll(getSampleLineChart(), getEyeDiagramChart(),
|
||||
getSamplesPerSymbolLineChart());
|
||||
}
|
||||
|
||||
return mSampleChartBox;
|
||||
}
|
||||
|
||||
private ComplexSampleLineChart getSampleLineChart()
|
||||
{
|
||||
if(mSampleLineChart == null)
|
||||
{
|
||||
mSampleLineChart = new ComplexSampleLineChart("Raw Samples", 100, 10);
|
||||
}
|
||||
|
||||
return mSampleLineChart;
|
||||
}
|
||||
|
||||
private EyeDiagramChart getEyeDiagramChart()
|
||||
{
|
||||
if(mEyeDiagramChart == null)
|
||||
{
|
||||
mEyeDiagramChart = new EyeDiagramChart(10, "Symbol: 3-4");
|
||||
}
|
||||
|
||||
return mEyeDiagramChart;
|
||||
}
|
||||
|
||||
private PhaseLineChart getPLLPhaseErrorLineChart()
|
||||
{
|
||||
if(mPLLPhaseErrorLineChart == null)
|
||||
{
|
||||
mPLLPhaseErrorLineChart = new PhaseLineChart(40);
|
||||
}
|
||||
|
||||
return mPLLPhaseErrorLineChart;
|
||||
}
|
||||
|
||||
private DoubleLineChart getPLLFrequencyLineChart()
|
||||
{
|
||||
if(mPLLFrequencyLineChart == null)
|
||||
{
|
||||
mPLLFrequencyLineChart = new DoubleLineChart("PLL Frequency", -500, 500, 50, 40);
|
||||
}
|
||||
|
||||
return mPLLFrequencyLineChart;
|
||||
}
|
||||
|
||||
private DoubleLineChart getSamplesPerSymbolLineChart()
|
||||
{
|
||||
if(mSamplesPerSymbolLineChart == null)
|
||||
{
|
||||
mSamplesPerSymbolLineChart = new DoubleLineChart("Sample Point", 9.5, 11.5, 0.1, 40);
|
||||
}
|
||||
|
||||
return mSamplesPerSymbolLineChart;
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,8 @@ public class DecoderPaneFactory
|
||||
DecoderType.LTR_NET,
|
||||
DecoderType.MDC1200,
|
||||
DecoderType.MPT1327,
|
||||
DecoderType.TAIT_1200);
|
||||
DecoderType.TAIT_1200,
|
||||
DecoderType.DMR);
|
||||
|
||||
/**
|
||||
* Creates a decoder pane for the decoder type
|
||||
@@ -59,6 +60,8 @@ public class DecoderPaneFactory
|
||||
throw new IllegalArgumentException("Use the getP25P1DecoderPane() method for P25 decoder type");
|
||||
case P25_PHASE2:
|
||||
return new P25Phase2HDQPSKPane();
|
||||
case DMR:
|
||||
return new DMRPane();
|
||||
}
|
||||
|
||||
return getDefaultPane();
|
||||
|
||||
@@ -151,12 +151,12 @@ public class PlaylistEditor extends BorderPane
|
||||
|
||||
MenuItem iconManagerItem = new MenuItem("_Icon Manager");
|
||||
iconManagerItem.setAccelerator(new KeyCodeCombination(KeyCode.I, KeyCombination.ALT_ANY));
|
||||
iconManagerItem.setOnAction(event -> MyEventBus.getEventBus().post(new ViewIconManagerRequest()));
|
||||
iconManagerItem.setOnAction(event -> MyEventBus.getGlobalEventBus().post(new ViewIconManagerRequest()));
|
||||
viewMenu.getItems().add(iconManagerItem);
|
||||
|
||||
MenuItem userPreferenceItem = new MenuItem("_User Preferences");
|
||||
userPreferenceItem.setAccelerator(new KeyCodeCombination(KeyCode.U, KeyCombination.ALT_ANY));
|
||||
userPreferenceItem.setOnAction(event -> MyEventBus.getEventBus()
|
||||
userPreferenceItem.setOnAction(event -> MyEventBus.getGlobalEventBus()
|
||||
.post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.TALKGROUP_FORMAT)));
|
||||
viewMenu.getItems().add(userPreferenceItem);
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
mPlaylistManager.getBroadcastModel().getConfiguredBroadcasts()
|
||||
.addListener((ListChangeListener<ConfiguredBroadcast>)c -> updateStreamViews());
|
||||
|
||||
MyEventBus.getEventBus().register(this);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
|
||||
setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
@@ -410,6 +410,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
}
|
||||
|
||||
private VBox getTitledPanesBox()
|
||||
@@ -659,7 +660,7 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
|
||||
if(selected != null)
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewAliasIdentifierRequest(selected));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewAliasIdentifierRequest(selected));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -687,6 +688,12 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
p25Menu.getItems().add(new SeparatorMenuItem());
|
||||
p25Menu.getItems().add(new AddTonesItem("Audio Tones (Phase 2 Only)"));
|
||||
|
||||
Menu dmrMenu = new ProtocolMenu(Protocol.DMR);
|
||||
dmrMenu.getItems().add(new AddTalkgroupItem(Protocol.DMR));
|
||||
dmrMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.DMR));
|
||||
dmrMenu.getItems().add(new AddRadioIdItem(Protocol.DMR));
|
||||
dmrMenu.getItems().add(new AddRadioIdRangeItem(Protocol.DMR));
|
||||
|
||||
Menu fleetsyncMenu = new ProtocolMenu(Protocol.FLEETSYNC);
|
||||
fleetsyncMenu.getItems().add(new AddTalkgroupItem(Protocol.FLEETSYNC));
|
||||
fleetsyncMenu.getItems().add(new AddTalkgroupRangeItem(Protocol.FLEETSYNC));
|
||||
@@ -715,8 +722,8 @@ public class AliasItemEditor extends Editor<Alias>
|
||||
Menu lojackMenu = new ProtocolMenu(Protocol.LOJACK);
|
||||
lojackMenu.getItems().add(new AddLojackItem());
|
||||
|
||||
mAddIdentifierButton.getItems().addAll(p25Menu, fleetsyncMenu, ltrMenu, mdcMenu, mptMenu, passportMenu,
|
||||
taitMenu, new SeparatorMenuItem(), lojackMenu);
|
||||
mAddIdentifierButton.getItems().addAll(p25Menu, dmrMenu, fleetsyncMenu, ltrMenu, mdcMenu, mptMenu,
|
||||
passportMenu, taitMenu, new SeparatorMenuItem(), lojackMenu);
|
||||
}
|
||||
|
||||
return mAddIdentifierButton;
|
||||
|
||||
@@ -178,7 +178,7 @@ public class AliasViewByIdentifierEditor extends VBox
|
||||
|
||||
if(selected != null)
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewAliasRequest(selected.getAlias()));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewAliasRequest(selected.getAlias()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -237,6 +237,10 @@ public class RadioIdEditor extends IdentifierEditor<Radio>
|
||||
"Format: 0 - 16777215"));
|
||||
mRadioDetails.add(new RadioDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
|
||||
"Format: 0 - FFFFFF"));
|
||||
mRadioDetails.add(new RadioDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
|
||||
"Format: 0 - 16777215"));
|
||||
mRadioDetails.add(new RadioDetail(Protocol.DMR, 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),
|
||||
|
||||
@@ -239,6 +239,10 @@ public class TalkgroupEditor extends IdentifierEditor<Talkgroup>
|
||||
"Format: 0 - 65535"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.APCO25, IntegerFormat.HEXADECIMAL, new HexFormatter(0,65535),
|
||||
"Format: 0 - FFFF"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.DECIMAL, new IntegerFormatter(0,0xFFFFFF),
|
||||
"Format: 0 - 16,777,215"));
|
||||
mTalkgroupDetails.add(new TalkgroupDetail(Protocol.DMR, IntegerFormat.HEXADECIMAL, new HexFormatter(0,0xFFFFFF),
|
||||
"Format: 0 - FFFFFF"));
|
||||
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),
|
||||
|
||||
@@ -358,7 +358,7 @@ public abstract class ChannelConfigurationEditor extends Editor<Channel>
|
||||
|
||||
if(optionalButtonType.isPresent() && optionalButtonType.get() == ButtonType.YES)
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.JMBE_LIBRARY));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewUserPreferenceEditorRequest(PreferenceEditorType.JMBE_LIBRARY));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ public class ChannelConfigurationEditorFactory
|
||||
{
|
||||
case AM:
|
||||
return new AMConfigurationEditor(playlistManager, userPreferences);
|
||||
case DMR:
|
||||
return new DMRConfigurationEditor(playlistManager, userPreferences);
|
||||
case NBFM:
|
||||
return new NBFMConfigurationEditor(playlistManager, userPreferences);
|
||||
case LTR_NET:
|
||||
|
||||
@@ -0,0 +1,726 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * 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.FrequencyField;
|
||||
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.dmr.DecodeConfigDMR;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||
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.preference.UserPreferences;
|
||||
import io.github.dsheirer.record.RecorderType;
|
||||
import io.github.dsheirer.record.config.RecordConfiguration;
|
||||
import io.github.dsheirer.source.config.SourceConfiguration;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
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.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.control.SpinnerValueFactory;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TitledPane;
|
||||
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.VBox;
|
||||
import javafx.util.Callback;
|
||||
import org.controlsfx.control.ToggleSwitch;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DMR channel configuration editor
|
||||
*/
|
||||
public class DMRConfigurationEditor extends ChannelConfigurationEditor
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(DMRConfigurationEditor.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 TableView<TimeslotFrequency> mTimeslotFrequencyTable;
|
||||
private IntegerTextField mLogicalSlotNumberField;
|
||||
private FrequencyField mDownlinkFrequencyField;
|
||||
// private FrequencyField mUplinkFrequencyField;
|
||||
private Button mAddTimeslotFrequencyButton;
|
||||
private Button mDeleteTimeslotFrequencyButton;
|
||||
private Spinner<Integer> mChannelRotationDelaySpinner;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param playlistManager
|
||||
*/
|
||||
public DMRConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences)
|
||||
{
|
||||
super(playlistManager, userPreferences);
|
||||
getTitledPanesBox().getChildren().add(getSourcePane());
|
||||
getTitledPanesBox().getChildren().add(getDecoderPane());
|
||||
getTitledPanesBox().getChildren().add(getEventLogPane());
|
||||
getTitledPanesBox().getChildren().add(getRecordPane());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DecoderType getDecoderType()
|
||||
{
|
||||
return DecoderType.DMR;
|
||||
}
|
||||
|
||||
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: DMR");
|
||||
mDecoderPane.setExpanded(true);
|
||||
|
||||
GridPane gridPane = new GridPane();
|
||||
gridPane.setPadding(new Insets(10,10,10,10));
|
||||
gridPane.setHgap(10);
|
||||
gridPane.setVgap(10);
|
||||
|
||||
int row = 0;
|
||||
|
||||
Label poolSizeLabel = new Label("Max Traffic Channels");
|
||||
GridPane.setHalignment(poolSizeLabel, HPos.RIGHT);
|
||||
GridPane.setConstraints(poolSizeLabel, 0, row);
|
||||
gridPane.getChildren().add(poolSizeLabel);
|
||||
|
||||
GridPane.setConstraints(getTrafficChannelPoolSizeSpinner(), 1, row);
|
||||
gridPane.getChildren().add(getTrafficChannelPoolSizeSpinner());
|
||||
|
||||
GridPane.setConstraints(getIgnoreDataCallsButton(), 2, row);
|
||||
gridPane.getChildren().add(getIgnoreDataCallsButton());
|
||||
|
||||
Label directionLabel = new Label("Ignore Data Calls");
|
||||
GridPane.setHalignment(directionLabel, HPos.LEFT);
|
||||
GridPane.setConstraints(directionLabel, 3, row);
|
||||
gridPane.getChildren().add(directionLabel);
|
||||
|
||||
Label timeslotTableLabel = new Label("Logical Slot Number (LSN) to Frequency Map. Required for: Connect Plus and Tier-III systems that don't use absolute frequencies");
|
||||
GridPane.setHalignment(timeslotTableLabel, HPos.LEFT);
|
||||
GridPane.setConstraints(timeslotTableLabel, 0, ++row, 4, 1);
|
||||
gridPane.getChildren().add(timeslotTableLabel);
|
||||
|
||||
GridPane.setConstraints(getTimeslotTable(), 0, ++row, 4, 3);
|
||||
gridPane.getChildren().add(getTimeslotTable());
|
||||
|
||||
VBox buttonsBox = new VBox();
|
||||
buttonsBox.setAlignment(Pos.CENTER);
|
||||
buttonsBox.setSpacing(10);
|
||||
buttonsBox.getChildren().addAll(getAddTimeslotFrequencyButton(), getDeleteTimeslotFrequencyButton());
|
||||
|
||||
GridPane.setConstraints(buttonsBox, 4, row, 1, 3);
|
||||
gridPane.getChildren().addAll(buttonsBox);
|
||||
|
||||
row += 3;
|
||||
|
||||
HBox editorBox = new HBox();
|
||||
editorBox.setAlignment(Pos.CENTER_LEFT);
|
||||
editorBox.setSpacing(5);
|
||||
|
||||
Label lsnLabel = new Label("LSN");
|
||||
editorBox.getChildren().addAll(lsnLabel,getLogicalSlotNumberField());
|
||||
|
||||
Label downlinkLabel = new Label("Frequency (MHz)");
|
||||
downlinkLabel.setPadding(new Insets(0,0,0,5));
|
||||
editorBox.getChildren().addAll(downlinkLabel,getDownlinkFrequencyField());
|
||||
|
||||
// Label uplinkLabel = new Label("Uplink");
|
||||
// uplinkLabel.setPadding(new Insets(0,0,0,5));
|
||||
// editorBox.getChildren().addAll(uplinkLabel,getUplinkFrequencyField());
|
||||
|
||||
GridPane.setConstraints(editorBox, 0, row, 4, 1);
|
||||
gridPane.getChildren().add(editorBox);
|
||||
|
||||
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 TableView<TimeslotFrequency> getTimeslotTable()
|
||||
{
|
||||
if(mTimeslotFrequencyTable == null)
|
||||
{
|
||||
mTimeslotFrequencyTable = new TableView<>(FXCollections.observableArrayList(TimeslotFrequency.extractor()));
|
||||
mTimeslotFrequencyTable.setPrefHeight(100.0);
|
||||
|
||||
TableColumn<TimeslotFrequency,Number> numberColumn = new TableColumn("LSN");
|
||||
numberColumn.setPrefWidth(70);
|
||||
numberColumn.setCellValueFactory(cellData -> cellData.getValue().getNumberProperty());
|
||||
mTimeslotFrequencyTable.getColumns().addAll(numberColumn);
|
||||
mTimeslotFrequencyTable.getSortOrder().add(numberColumn);
|
||||
|
||||
TableColumn<TimeslotFrequency,Number> downlinkColumn = new TableColumn("Frequency (MHz)");
|
||||
downlinkColumn.setCellValueFactory(cellData -> cellData.getValue().getDownlinkMHz());
|
||||
downlinkColumn.setPrefWidth(175);
|
||||
mTimeslotFrequencyTable.getColumns().addAll(downlinkColumn);
|
||||
|
||||
// TableColumn<TimeslotFrequency,Number> uplinkColumn = new TableColumn("Uplink (MHz)");
|
||||
// uplinkColumn.setCellValueFactory(cellData -> cellData.getValue().getUplinkMHz());
|
||||
// mTimeslotFrequencyTable.getColumns().addAll(uplinkColumn);
|
||||
|
||||
mTimeslotFrequencyTable.getSelectionModel().selectedItemProperty()
|
||||
.addListener((observable, oldValue, newValue) -> setTimeslot(newValue));
|
||||
}
|
||||
|
||||
return mTimeslotFrequencyTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the specified timeslot frequency into the editor
|
||||
*/
|
||||
private void setTimeslot(TimeslotFrequency timeslot)
|
||||
{
|
||||
//Preserve the current modified flag state since setting values in the editor will change it.
|
||||
boolean modified = modifiedProperty().get();
|
||||
|
||||
getLogicalSlotNumberField().setDisable(timeslot == null);
|
||||
getDownlinkFrequencyField().setDisable(timeslot == null);
|
||||
// getUplinkFrequencyField().setDisable(timeslot == null);
|
||||
getDeleteTimeslotFrequencyButton().setDisable(timeslot == null);
|
||||
|
||||
if(timeslot != null)
|
||||
{
|
||||
getLogicalSlotNumberField().set(timeslot.getNumber());
|
||||
getDownlinkFrequencyField().set(timeslot.getDownlinkFrequency());
|
||||
// getUplinkFrequencyField().set(timeslot.getUplinkFrequency());
|
||||
}
|
||||
else
|
||||
{
|
||||
getLogicalSlotNumberField().set(0);
|
||||
getDownlinkFrequencyField().set(0);
|
||||
// getUplinkFrequencyField().set(0);
|
||||
}
|
||||
|
||||
modifiedProperty().set(modified);
|
||||
}
|
||||
|
||||
private Button getAddTimeslotFrequencyButton()
|
||||
{
|
||||
if(mAddTimeslotFrequencyButton == null)
|
||||
{
|
||||
mAddTimeslotFrequencyButton = new Button("Add");
|
||||
mAddTimeslotFrequencyButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mAddTimeslotFrequencyButton.setOnAction(event -> addTimeslot());
|
||||
}
|
||||
|
||||
return mAddTimeslotFrequencyButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new timeslot frequency value and makes a best guess of the next sequential LSN number
|
||||
*/
|
||||
private void addTimeslot()
|
||||
{
|
||||
int lsn = 1;
|
||||
|
||||
while(hasLSN(lsn) && lsn <= 64) //64 is an arbitrary value to keep it from going too high
|
||||
{
|
||||
lsn++;
|
||||
}
|
||||
|
||||
TimeslotFrequency timeslotFrequency = new TimeslotFrequency();
|
||||
timeslotFrequency.setNumber(lsn);
|
||||
getTimeslotTable().getItems().add(timeslotFrequency);
|
||||
getTimeslotTable().scrollTo(timeslotFrequency);
|
||||
getTimeslotTable().getSelectionModel().select(timeslotFrequency);
|
||||
modifiedProperty().set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the current timeslot frequency list to determine if the specified lsn is already listed
|
||||
*/
|
||||
private boolean hasLSN(int lsn)
|
||||
{
|
||||
for(TimeslotFrequency timeslotFrequency: getTimeslotTable().getItems())
|
||||
{
|
||||
if(timeslotFrequency.getNumber() == lsn)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Button getDeleteTimeslotFrequencyButton()
|
||||
{
|
||||
if(mDeleteTimeslotFrequencyButton == null)
|
||||
{
|
||||
mDeleteTimeslotFrequencyButton = new Button("Delete");
|
||||
mDeleteTimeslotFrequencyButton.setDisable(true);
|
||||
mDeleteTimeslotFrequencyButton.setMaxWidth(Double.MAX_VALUE);
|
||||
mDeleteTimeslotFrequencyButton.setOnAction(new EventHandler<ActionEvent>()
|
||||
{
|
||||
@Override
|
||||
public void handle(ActionEvent event)
|
||||
{
|
||||
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
|
||||
|
||||
if(selected != null)
|
||||
{
|
||||
getTimeslotTable().getItems().remove(selected);
|
||||
modifiedProperty().set(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mDeleteTimeslotFrequencyButton;
|
||||
}
|
||||
|
||||
private IntegerTextField getLogicalSlotNumberField()
|
||||
{
|
||||
if(mLogicalSlotNumberField == null)
|
||||
{
|
||||
mLogicalSlotNumberField = new IntegerTextField();
|
||||
mLogicalSlotNumberField.setDisable(true);
|
||||
mLogicalSlotNumberField.setPrefWidth(65);
|
||||
mLogicalSlotNumberField.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
|
||||
|
||||
if(selected != null)
|
||||
{
|
||||
Integer value = mLogicalSlotNumberField.get();
|
||||
|
||||
if(value != null)
|
||||
{
|
||||
selected.setNumber(value);
|
||||
}
|
||||
}
|
||||
|
||||
modifiedProperty().set(true);
|
||||
});
|
||||
}
|
||||
|
||||
return mLogicalSlotNumberField;
|
||||
}
|
||||
|
||||
private FrequencyField getDownlinkFrequencyField()
|
||||
{
|
||||
if(mDownlinkFrequencyField == null)
|
||||
{
|
||||
mDownlinkFrequencyField = new FrequencyField();
|
||||
mDownlinkFrequencyField.setDisable(true);
|
||||
mDownlinkFrequencyField.textProperty().addListener(new ChangeListener<String>()
|
||||
{
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
|
||||
{
|
||||
TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
|
||||
|
||||
if(selected != null)
|
||||
{
|
||||
selected.setDownlinkFrequency(mDownlinkFrequencyField.get());
|
||||
}
|
||||
|
||||
modifiedProperty().set(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mDownlinkFrequencyField;
|
||||
}
|
||||
|
||||
// private FrequencyField getUplinkFrequencyField()
|
||||
// {
|
||||
// if(mUplinkFrequencyField == null)
|
||||
// {
|
||||
// mUplinkFrequencyField = new FrequencyField();
|
||||
// mUplinkFrequencyField.setDisable(true);
|
||||
// mUplinkFrequencyField.textProperty().addListener(new ChangeListener<String>()
|
||||
// {
|
||||
// @Override
|
||||
// public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue)
|
||||
// {
|
||||
// TimeslotFrequency selected = getTimeslotTable().getSelectionModel().getSelectedItem();
|
||||
//
|
||||
// if(selected != null)
|
||||
// {
|
||||
// selected.setUplinkFrequency(mUplinkFrequencyField.get());
|
||||
// int lsn = selected.getNumber();
|
||||
// selected.setNumber(-1);
|
||||
// selected.setNumber(lsn);
|
||||
// }
|
||||
//
|
||||
// modifiedProperty().set(true);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// return mUplinkFrequencyField;
|
||||
// }
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel rotation monitor delay value. This dictates how long the decoder will remain on each frequency before
|
||||
* rotating to the next frequency in the list
|
||||
* @return spinner
|
||||
*/
|
||||
private Spinner<Integer> getChannelRotationDelaySpinner()
|
||||
{
|
||||
if(mChannelRotationDelaySpinner == null)
|
||||
{
|
||||
mChannelRotationDelaySpinner = new Spinner();
|
||||
mChannelRotationDelaySpinner.setDisable(true);
|
||||
mChannelRotationDelaySpinner.setTooltip(
|
||||
new Tooltip("Delay on each frequency before rotating to next when seeking to next active channel frequency"));
|
||||
mChannelRotationDelaySpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
|
||||
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(200, 2000, 200, 50);
|
||||
mChannelRotationDelaySpinner.setValueFactory(svf);
|
||||
mChannelRotationDelaySpinner.getValueFactory().valueProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
}
|
||||
|
||||
return mChannelRotationDelaySpinner;
|
||||
}
|
||||
|
||||
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);
|
||||
getTimeslotTable().getItems().clear();
|
||||
getTimeslotTable().setDisable(config == null);
|
||||
getAddTimeslotFrequencyButton().setDisable(config == null);
|
||||
getDeleteTimeslotFrequencyButton().setDisable(true);
|
||||
getLogicalSlotNumberField().set(0);
|
||||
getLogicalSlotNumberField().setDisable(true);
|
||||
getDownlinkFrequencyField().set(0);
|
||||
getDownlinkFrequencyField().setDisable(true);
|
||||
// getUplinkFrequencyField().set(0);
|
||||
// getUplinkFrequencyField().setDisable(true);
|
||||
getChannelRotationDelaySpinner().setDisable(config == null);
|
||||
|
||||
if(config instanceof DecodeConfigDMR)
|
||||
{
|
||||
DecodeConfigDMR decodeConfig = (DecodeConfigDMR)config;
|
||||
|
||||
getIgnoreDataCallsButton().setSelected(decodeConfig.getIgnoreDataCalls());
|
||||
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(decodeConfig.getTrafficChannelPoolSize());
|
||||
|
||||
for(TimeslotFrequency timeslotFrequency: decodeConfig.getTimeslotMap())
|
||||
{
|
||||
getTimeslotTable().getItems().add(timeslotFrequency.copy());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
getIgnoreDataCallsButton().setSelected(false);
|
||||
getTrafficChannelPoolSizeSpinner().getValueFactory().setValue(0);
|
||||
getChannelRotationDelaySpinner().getValueFactory().setValue(200);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveDecoderConfiguration()
|
||||
{
|
||||
DecodeConfigDMR config;
|
||||
|
||||
if(getItem().getDecodeConfiguration() instanceof DecodeConfigDMR)
|
||||
{
|
||||
config = (DecodeConfigDMR)getItem().getDecodeConfiguration();
|
||||
}
|
||||
else
|
||||
{
|
||||
config = new DecodeConfigDMR();
|
||||
}
|
||||
|
||||
// config.setChannelRotationDelay(getChannelRotationDelaySpinner().getValue());
|
||||
config.setIgnoreDataCalls(getIgnoreDataCallsButton().isSelected());
|
||||
config.setTrafficChannelPoolSize(getTrafficChannelPoolSizeSpinner().getValue());
|
||||
config.setTimeslotMap(new ArrayList<>(getTimeslotTable().getItems()));
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel tuner channel source frequencies value factory
|
||||
*/
|
||||
public class FrequencyCellValueFactory implements Callback<TableColumn.CellDataFeatures<TimeslotFrequency, String>,
|
||||
ObservableValue<String>>
|
||||
{
|
||||
private SimpleStringProperty mFrequency = new SimpleStringProperty();
|
||||
private boolean mIsDownlink;
|
||||
|
||||
public FrequencyCellValueFactory(boolean isDownlink)
|
||||
{
|
||||
mIsDownlink = isDownlink;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> call(TableColumn.CellDataFeatures<TimeslotFrequency, String> param)
|
||||
{
|
||||
if(param.getValue() != null)
|
||||
{
|
||||
long frequency = (mIsDownlink ? param.getValue().getDownlinkFrequency() : param.getValue().getUplinkFrequency());
|
||||
mFrequency.set(String.valueOf(frequency / 1E6));
|
||||
}
|
||||
else
|
||||
{
|
||||
mFrequency.set(null);
|
||||
}
|
||||
|
||||
return mFrequency;
|
||||
}
|
||||
}
|
||||
|
||||
public class DownlinkPropertyValueFactory extends PropertyValueFactory<TimeslotFrequency,String>
|
||||
{
|
||||
private StringProperty mStringProperty = new SimpleStringProperty();
|
||||
|
||||
public DownlinkPropertyValueFactory()
|
||||
{
|
||||
super("downlinkFrequency");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> call(TableColumn.CellDataFeatures<TimeslotFrequency,String> param)
|
||||
{
|
||||
if(param.getValue() != null)
|
||||
{
|
||||
mStringProperty.set(String.valueOf(param.getValue().getDownlinkFrequency() / 1E6));
|
||||
}
|
||||
else
|
||||
{
|
||||
mStringProperty.setValue(null);
|
||||
}
|
||||
|
||||
return mStringProperty;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -276,7 +276,7 @@ public class MPT1327ConfigurationEditor extends ChannelConfigurationEditor
|
||||
channelMapName = getChannelMapComboBox().getSelectionModel().getSelectedItem().getName();
|
||||
}
|
||||
|
||||
MyEventBus.getEventBus().post(new ViewChannelMapEditorRequest(channelMapName));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewChannelMapEditorRequest(channelMapName));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ public class PlaylistManagerEditor extends HBox
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
//Register to receive preferences updates
|
||||
MyEventBus.getEventBus().register(this);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
|
||||
setPadding(new Insets(5, 5, 5, 5));
|
||||
HBox.setHgrow(getPlaylistTableView(), Priority.ALWAYS);
|
||||
@@ -102,6 +102,11 @@ public class PlaylistManagerEditor extends HBox
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the path argument is the same as the current playlist path
|
||||
*/
|
||||
|
||||
@@ -376,7 +376,7 @@ public class FrequencyEditor extends VBox
|
||||
|
||||
if(getShowCreatedChannelCheckBox().selectedProperty().get())
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewChannelRequest(channel));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -31,7 +31,7 @@ public enum ModeDecoderType
|
||||
{
|
||||
AM("am", DecoderType.AM),
|
||||
APCO25("p25", DecoderType.P25_PHASE1),
|
||||
DMR("dmr", null),
|
||||
DMR("dmr", DecoderType.DMR),
|
||||
DSTAR("d-star", null),
|
||||
FM("fm", DecoderType.NBFM),
|
||||
FMN("fmn", DecoderType.NBFM),
|
||||
|
||||
@@ -24,6 +24,7 @@ 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.dmr.channel.TimeslotFrequency;
|
||||
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;
|
||||
@@ -33,7 +34,9 @@ import io.github.dsheirer.preference.identifier.talkgroup.MPT1327TalkgroupFormat
|
||||
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.SiteFrequency;
|
||||
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.Type;
|
||||
@@ -42,6 +45,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -228,6 +232,19 @@ public class RadioReferenceDecoder
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the radio reference Type for the system information instance
|
||||
*/
|
||||
public Type getType(SystemInformation systemInformation)
|
||||
{
|
||||
if(systemInformation != null)
|
||||
{
|
||||
return mTypeMap.get(systemInformation.getTypeId());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the radio reference Flavor for the system
|
||||
*/
|
||||
@@ -241,6 +258,19 @@ public class RadioReferenceDecoder
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the radio reference Flavor for the system information instance
|
||||
*/
|
||||
public Flavor getFlavor(SystemInformation systemInformation)
|
||||
{
|
||||
if(systemInformation != null)
|
||||
{
|
||||
return mFlavorMap.get(systemInformation.getFlavorId());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the radio reference voice/traffic channel protocol for the system
|
||||
*/
|
||||
@@ -302,6 +332,66 @@ public class RadioReferenceDecoder
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the specified site is a DMR Connect Plus site that has site frequencies that can be converted
|
||||
* to timeslot frequencies
|
||||
* @param systemInformation to determine the system type
|
||||
* @param site containing site frequencies
|
||||
*/
|
||||
public boolean hasTimeslotFrequencies(SystemInformation systemInformation, Site site)
|
||||
{
|
||||
Type type = getType(systemInformation);
|
||||
Flavor flavor = getFlavor(systemInformation);
|
||||
return type != null && flavor != null && type.getName().contains("DMR") && !site.getSiteFrequencies().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of timeslot to frequency mappings from the radio reference site's list of site frequencies for a
|
||||
* MotoTRBO Connect Plus site
|
||||
* @param systemInformation to detect the system type (DMR) and flavor (Connect Plus).
|
||||
* @param site containing 1 or more site frequencies
|
||||
* @return list of timeslot frequency mappings or an empty list.
|
||||
*/
|
||||
public List<TimeslotFrequency> getTimeslotFrequencies(SystemInformation systemInformation, Site site)
|
||||
{
|
||||
if(hasTimeslotFrequencies(systemInformation, site))
|
||||
{
|
||||
List<TimeslotFrequency> frequencies = new ArrayList<>();
|
||||
|
||||
for(SiteFrequency siteFrequency: site.getSiteFrequencies())
|
||||
{
|
||||
int lcn = siteFrequency.getLogicalChannelNumber();
|
||||
|
||||
if(siteFrequency.getChannelId() != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
lcn = Integer.parseInt(siteFrequency.getChannelId());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//Do nothing, we couldn't parse the LSN from the channel ID value
|
||||
}
|
||||
}
|
||||
|
||||
int lsn = (lcn - 1) * 2 + 1;
|
||||
TimeslotFrequency timeslot1Frequency = new TimeslotFrequency();
|
||||
timeslot1Frequency.setNumber(lsn);
|
||||
timeslot1Frequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
|
||||
frequencies.add(timeslot1Frequency);
|
||||
|
||||
TimeslotFrequency timeslot2Frequency = new TimeslotFrequency();
|
||||
timeslot2Frequency.setNumber(lsn + 1);
|
||||
timeslot2Frequency.setDownlinkFrequency((long)(siteFrequency.getFrequency() * 1E6));
|
||||
frequencies.add(timeslot2Frequency);
|
||||
}
|
||||
|
||||
return frequencies;
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies the sdrtrunk protocol used by the system.
|
||||
* @param system to identify
|
||||
@@ -320,6 +410,8 @@ public class RadioReferenceDecoder
|
||||
|
||||
switch(type.getName())
|
||||
{
|
||||
case "DMR":
|
||||
return Protocol.DMR;
|
||||
case "LTR":
|
||||
if(flavor.getName().contentEquals("Standard") || flavor.getName().contentEquals("Net"))
|
||||
{
|
||||
@@ -341,7 +433,6 @@ public class RadioReferenceDecoder
|
||||
return Protocol.APCO25;
|
||||
}
|
||||
break;
|
||||
case "DMR":
|
||||
case "NXDN":
|
||||
case "EDACS":
|
||||
case "TETRA":
|
||||
@@ -371,6 +462,8 @@ public class RadioReferenceDecoder
|
||||
{
|
||||
switch(type.getName())
|
||||
{
|
||||
case "DMR":
|
||||
return DecoderType.DMR;
|
||||
case "LTR":
|
||||
if(flavor.getName().contentEquals("Net"))
|
||||
{
|
||||
@@ -403,7 +496,6 @@ public class RadioReferenceDecoder
|
||||
return DecoderType.P25_PHASE1;
|
||||
}
|
||||
break;
|
||||
case "DMR":
|
||||
case "NXDN":
|
||||
|
||||
case "EDACS":
|
||||
|
||||
@@ -27,6 +27,8 @@ import io.github.dsheirer.gui.playlist.channel.ViewChannelRequest;
|
||||
import io.github.dsheirer.module.decode.DecoderFactory;
|
||||
import io.github.dsheirer.module.decode.DecoderType;
|
||||
import io.github.dsheirer.module.decode.config.DecodeConfiguration;
|
||||
import io.github.dsheirer.module.decode.dmr.DecodeConfigDMR;
|
||||
import io.github.dsheirer.module.decode.dmr.channel.TimeslotFrequency;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.DecodeConfigP25Phase1;
|
||||
import io.github.dsheirer.module.decode.p25.phase1.P25P1Decoder;
|
||||
import io.github.dsheirer.module.decode.p25.phase2.DecodeConfigP25Phase2;
|
||||
@@ -222,6 +224,12 @@ public class SiteEditor extends GridPane
|
||||
|
||||
switch(decoderType)
|
||||
{
|
||||
case DMR:
|
||||
DecodeConfigDMR dmr = new DecodeConfigDMR();
|
||||
List<TimeslotFrequency> timeslotFrequencies = mRadioReferenceDecoder
|
||||
.getTimeslotFrequencies(systemInformation, site);
|
||||
dmr.setTimeslotMap(timeslotFrequencies);
|
||||
return dmr;
|
||||
case P25_PHASE1:
|
||||
DecodeConfiguration p1config = DecoderFactory.getDecodeConfiguration(decoderType);
|
||||
|
||||
@@ -290,6 +298,7 @@ public class SiteEditor extends GridPane
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void setSite(EnrichedSite site, System system, SystemInformation systemInformation, RadioReferenceDecoder decoder)
|
||||
{
|
||||
mCurrentSite = site;
|
||||
@@ -473,7 +482,7 @@ public class SiteEditor extends GridPane
|
||||
|
||||
if(getGoToChannelEditorCheckBox().isSelected() && gotoChannel != null)
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewChannelRequest(gotoChannel));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(gotoChannel));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,7 +544,7 @@ public class SiteEditor extends GridPane
|
||||
|
||||
if(getGoToChannelEditorCheckBox().isSelected())
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewChannelRequest(channel));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -609,7 +618,7 @@ public class SiteEditor extends GridPane
|
||||
|
||||
if(getGoToChannelEditorCheckBox().isSelected())
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewChannelRequest(channel));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -637,7 +646,7 @@ public class SiteEditor extends GridPane
|
||||
|
||||
if(getGoToChannelEditorCheckBox().isSelected() && gotoChannel != null)
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewChannelRequest(gotoChannel));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(gotoChannel));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -701,7 +710,7 @@ public class SiteEditor extends GridPane
|
||||
|
||||
if(getGoToChannelEditorCheckBox().isSelected())
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewChannelRequest(channel));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(channel));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -730,7 +739,7 @@ public class SiteEditor extends GridPane
|
||||
|
||||
if(getGoToChannelEditorCheckBox().isSelected() && gotoChannel != null)
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewChannelRequest(gotoChannel));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewChannelRequest(gotoChannel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public class SystemTalkgroupSelectionEditor extends GridPane
|
||||
public SystemTalkgroupSelectionEditor(UserPreferences userPreferences, PlaylistManager playlistManager)
|
||||
{
|
||||
//Register to receive flash alias box requests
|
||||
MyEventBus.getEventBus().register(this);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
|
||||
mUserPreferences = userPreferences;
|
||||
mPlaylistManager = playlistManager;
|
||||
@@ -172,6 +172,11 @@ public class SystemTalkgroupSelectionEditor extends GridPane
|
||||
getChildren().add(getTalkgroupEditor());
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
mTalkgroupList.clear();
|
||||
|
||||
@@ -278,7 +278,7 @@ public class TalkgroupEditor extends GridPane
|
||||
alert.setHeaderText("An alias list is required to create aliases");
|
||||
alert.initOwner((getCreateAliasButton()).getScene().getWindow());
|
||||
alert.showAndWait();
|
||||
MyEventBus.getEventBus().post(new FlashAliasListComboBoxRequest());
|
||||
MyEventBus.getGlobalEventBus().post(new FlashAliasListComboBoxRequest());
|
||||
}
|
||||
else if(mRadioReferenceDecoder != null && mTalkgroup != null && mSystem != null)
|
||||
{
|
||||
@@ -309,7 +309,7 @@ public class TalkgroupEditor extends GridPane
|
||||
mEditAliasButton.setOnAction(event -> {
|
||||
if(mAlias != null)
|
||||
{
|
||||
MyEventBus.getEventBus().post(new ViewAliasRequest(mAlias));
|
||||
MyEventBus.getGlobalEventBus().post(new ViewAliasRequest(mAlias));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,8 +26,12 @@ 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 io.github.dsheirer.source.tuner.channel.rotation.ChannelRotationMonitor;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
@@ -36,6 +40,9 @@ import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.control.SpinnerValueFactory;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.slf4j.Logger;
|
||||
@@ -54,9 +61,10 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
|
||||
private static final String NONE = "(none) ";
|
||||
private TunerModel mTunerModel;
|
||||
private FrequencyBox mPrimaryFrequencyBox;
|
||||
private List<FrequencyBox> mFrequencyBoxes = new ArrayList<>();
|
||||
private ObservableList<FrequencyBox> mFrequencyBoxes = FXCollections.observableArrayList();
|
||||
private ComboBox<String> mPreferredTunerComboBox;
|
||||
private VBox mFrequencyBoxContainer;
|
||||
private Spinner<Integer> mChannelRotationDelaySpinner;
|
||||
private boolean mAllowMultipleFrequencies;
|
||||
|
||||
/**
|
||||
@@ -80,9 +88,24 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
|
||||
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());
|
||||
HBox tunerBox = new HBox();
|
||||
tunerBox.setAlignment(Pos.CENTER_LEFT);
|
||||
tunerBox.setSpacing(10);
|
||||
tunerBox.getChildren().addAll(preferredTunerLabel, getPreferredTunerComboBox());
|
||||
|
||||
Label frequencyRotationLabel = new Label("Frequency Rotation Delay (ms)");
|
||||
HBox frequencyBox = new HBox();
|
||||
frequencyBox.setAlignment(Pos.CENTER_LEFT);
|
||||
frequencyBox.setSpacing(10);
|
||||
frequencyBox.getChildren().addAll(frequencyRotationLabel, getFrequencyRotationDelaySpinner());
|
||||
getFrequencyRotationDelaySpinner().disableProperty().bind(Bindings.greaterThan(2, Bindings.size(mFrequencyBoxes)));
|
||||
|
||||
VBox vbox = new VBox();
|
||||
vbox.setSpacing(10);
|
||||
vbox.setPadding(new Insets(0,0,0,20));
|
||||
vbox.getChildren().addAll(tunerBox, frequencyBox);
|
||||
|
||||
hBox.getChildren().add(vbox);
|
||||
|
||||
setAlignment(Pos.TOP_LEFT);
|
||||
getChildren().add(hBox);
|
||||
@@ -164,6 +187,7 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
|
||||
|
||||
sourceConfigMulti.setFrequencies(frequencies);
|
||||
sourceConfigMulti.setPreferredTuner(preferredTuner);
|
||||
sourceConfigMulti.setFrequencyRotationDelay(getFrequencyRotationDelaySpinner().getValue());
|
||||
setSourceConfiguration(sourceConfigMulti);
|
||||
}
|
||||
}
|
||||
@@ -181,6 +205,9 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
|
||||
|
||||
disable(sourceConfiguration == null);
|
||||
|
||||
getFrequencyRotationDelaySpinner().getValueFactory()
|
||||
.setValue(ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_MINIMUM);
|
||||
|
||||
if(sourceConfiguration == null)
|
||||
{
|
||||
setFrequencies(Collections.emptyList());
|
||||
@@ -198,6 +225,7 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
|
||||
SourceConfigTunerMultipleFrequency sourceMulti = (SourceConfigTunerMultipleFrequency)sourceConfiguration;
|
||||
setFrequencies(sourceMulti.getFrequencies());
|
||||
preferredTuner = sourceMulti.getPreferredTuner();
|
||||
getFrequencyRotationDelaySpinner().getValueFactory().setValue(sourceMulti.getFrequencyRotationDelay());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -226,6 +254,31 @@ public class FrequencyEditor extends SourceConfigurationEditor<SourceConfigurati
|
||||
modifiedProperty().set(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel rotation monitor delay value. This dictates how long the decoder will remain on each frequency before
|
||||
* rotating to the next frequency in the list
|
||||
* @return spinner
|
||||
*/
|
||||
private Spinner<Integer> getFrequencyRotationDelaySpinner()
|
||||
{
|
||||
if(mChannelRotationDelaySpinner == null)
|
||||
{
|
||||
mChannelRotationDelaySpinner = new Spinner();
|
||||
mChannelRotationDelaySpinner.setTooltip(
|
||||
new Tooltip("Delay on each frequency before rotating to next when seeking to next active channel frequency"));
|
||||
mChannelRotationDelaySpinner.getStyleClass().add(Spinner.STYLE_CLASS_SPLIT_ARROWS_HORIZONTAL);
|
||||
int min = ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_MINIMUM;
|
||||
int max = ChannelRotationMonitor.CHANNEL_ROTATION_DELAY_MAXIMUM;
|
||||
SpinnerValueFactory<Integer> svf = new SpinnerValueFactory.IntegerSpinnerValueFactory(min, max, min, 50);
|
||||
mChannelRotationDelaySpinner.setValueFactory(svf);
|
||||
mChannelRotationDelaySpinner.getValueFactory().valueProperty()
|
||||
.addListener((observable, oldValue, newValue) -> modifiedProperty().set(true));
|
||||
}
|
||||
|
||||
return mChannelRotationDelaySpinner;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the preferred tuner's combo box to reflect the current set of tuners.
|
||||
*/
|
||||
|
||||
@@ -24,7 +24,6 @@ import io.github.dsheirer.gui.preference.directory.DirectoryPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.duplicate.DuplicateCallPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.playback.PlaybackPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.record.RecordPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.tuner.ChannelMultipleFrequencyPreferenceEditor;
|
||||
import io.github.dsheirer.gui.preference.tuner.TunerPreferenceEditor;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import javafx.scene.Node;
|
||||
@@ -50,8 +49,6 @@ public class PreferenceEditorFactory
|
||||
return new DirectoryPreferenceEditor(userPreferences);
|
||||
case JMBE_LIBRARY:
|
||||
return new JmbeLibraryPreferenceEditor(userPreferences);
|
||||
case SOURCE_CHANNEL_MULTIPLE_FREQUENCY:
|
||||
return new ChannelMultipleFrequencyPreferenceEditor(userPreferences);
|
||||
case SOURCE_TUNER_CHANNELIZER:
|
||||
return new TunerPreferenceEditor(userPreferences);
|
||||
case TALKGROUP_FORMAT:
|
||||
|
||||
@@ -30,7 +30,6 @@ public enum PreferenceEditorType
|
||||
AUDIO_RECORD("Record"),
|
||||
AUDIO_OUTPUT("Output/Tones"),
|
||||
AUDIO_DUPLICATE_CALL_DETECTION("Duplicate Calls"),
|
||||
SOURCE_CHANNEL_MULTIPLE_FREQUENCY("Channel - Multiple Frequency"),
|
||||
SOURCE_TUNER_CHANNELIZER("Tuner Channelizer"),
|
||||
TALKGROUP_FORMAT("Talkgroup & Radio ID"),
|
||||
DEFAULT("Default");
|
||||
|
||||
@@ -190,7 +190,6 @@ public class UserPreferencesEditor extends BorderPane
|
||||
storageItem.setExpanded(true);
|
||||
|
||||
TreeItem<String> sourceItem = new TreeItem<>("Source");
|
||||
sourceItem.getChildren().add(new TreeItem(PreferenceEditorType.SOURCE_CHANNEL_MULTIPLE_FREQUENCY));
|
||||
sourceItem.getChildren().add(new TreeItem(PreferenceEditorType.SOURCE_TUNER_CHANNELIZER));
|
||||
treeRoot.getChildren().add(sourceItem);
|
||||
sourceItem.setExpanded(true);
|
||||
@@ -247,7 +246,7 @@ public class UserPreferencesEditor extends BorderPane
|
||||
|
||||
Menu viewMenu = new Menu("View");
|
||||
MenuItem playlistEditorItem = new MenuItem("Playlist Editor");
|
||||
playlistEditorItem.setOnAction(event -> MyEventBus.getEventBus().post(new ViewPlaylistRequest()));
|
||||
playlistEditorItem.setOnAction(event -> MyEventBus.getGlobalEventBus().post(new ViewPlaylistRequest()));
|
||||
viewMenu.getItems().add(playlistEditorItem);
|
||||
mMenuBar.getMenus().add(viewMenu);
|
||||
}
|
||||
|
||||
@@ -82,13 +82,18 @@ public class JmbeLibraryPreferenceEditor extends VBox
|
||||
mUserPreferences = userPreferences;
|
||||
|
||||
//Register to receive directory preference update notifications so we can update the path labels
|
||||
MyEventBus.getEventBus().register(this);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
|
||||
setPadding(new Insets(10,10,10,10));
|
||||
setSpacing(10);
|
||||
getChildren().addAll(getEditorPane(), getButtonsBox(), getAlertUserWhenMissingCheckBox());
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
}
|
||||
|
||||
private CheckBox getAlertUserWhenMissingCheckBox()
|
||||
{
|
||||
if(mAlertUserWhenMissingCheckBox == null)
|
||||
@@ -214,7 +219,7 @@ public class JmbeLibraryPreferenceEditor extends VBox
|
||||
if(buttonType == ButtonType.YES)
|
||||
{
|
||||
mLog.info("Posting JMBE editor request to event bus");
|
||||
MyEventBus.getEventBus().post(new JmbeEditorRequest(release));
|
||||
MyEventBus.getGlobalEventBus().post(new JmbeEditorRequest(release));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -98,12 +98,17 @@ public class DirectoryPreferenceEditor extends HBox
|
||||
mDirectoryPreference = userPreferences.getDirectoryPreference();
|
||||
|
||||
//Register to receive directory preference update notifications so we can update the path labels
|
||||
MyEventBus.getEventBus().register(this);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
|
||||
HBox.setHgrow(getEditorPane(), Priority.ALWAYS);
|
||||
getChildren().add(getEditorPane());
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
}
|
||||
|
||||
private GridPane getEditorPane()
|
||||
{
|
||||
if(mEditorPane == null)
|
||||
|
||||
@@ -1,123 +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.preference.tuner;
|
||||
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.source.ChannelMultiFrequencyPreference;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Spinner;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
|
||||
/**
|
||||
* Preference settings for channel source configurations containing multiple frequencies
|
||||
*/
|
||||
public class ChannelMultipleFrequencyPreferenceEditor extends HBox
|
||||
{
|
||||
private static final String HELP_TEXT = "Rotation delay determines how long a decoder will wait before rotating " +
|
||||
"to the next frequency in a list for a rotating control channel configuration.";
|
||||
|
||||
private static final Insets MARGINS = new Insets(5,5,5,5);
|
||||
private ChannelMultiFrequencyPreference mChannelMultiFrequencyPreference;
|
||||
private GridPane mEditorPane;
|
||||
private Label mRotationDelayLabel;
|
||||
private Spinner<Integer> mRotationDelaySpinner;
|
||||
private Button mResetButton;
|
||||
private Label mHelpText;
|
||||
|
||||
public ChannelMultipleFrequencyPreferenceEditor(UserPreferences userPreferences)
|
||||
{
|
||||
mChannelMultiFrequencyPreference = userPreferences.getChannelMultiFrequencyPreference();
|
||||
getChildren().add(getEditorPane());
|
||||
}
|
||||
|
||||
private GridPane getEditorPane()
|
||||
{
|
||||
if(mEditorPane == null)
|
||||
{
|
||||
mEditorPane = new GridPane();
|
||||
GridPane.setMargin(getRotationDelaySpinner(), new Insets(5, 5, 5, 5));
|
||||
mEditorPane.add(getRotationDelaySpinner(), 0, 0);
|
||||
GridPane.setMargin(getRotationDelayLabel(), new Insets(5, 5, 5, 5));
|
||||
mEditorPane.add(getRotationDelayLabel(), 1, 0);
|
||||
GridPane.setMargin(getResetButton(), MARGINS);
|
||||
mEditorPane.add(getResetButton(), 2, 0);
|
||||
GridPane.setMargin(getHelpText(), MARGINS);
|
||||
mEditorPane.add(getHelpText(), 0, 1, 3, 1);
|
||||
}
|
||||
|
||||
return mEditorPane;
|
||||
}
|
||||
|
||||
public Spinner<Integer> getRotationDelaySpinner()
|
||||
{
|
||||
if(mRotationDelaySpinner == null)
|
||||
{
|
||||
int currentDelay = (int)(mChannelMultiFrequencyPreference.getRotationDelay() / 1000);
|
||||
mRotationDelaySpinner = new Spinner<>(1, 60, currentDelay, 1);
|
||||
mRotationDelaySpinner.setEditable(true);
|
||||
mRotationDelaySpinner.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
mChannelMultiFrequencyPreference.setRotationDelay(newValue * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
return mRotationDelaySpinner;
|
||||
}
|
||||
|
||||
public Label getRotationDelayLabel()
|
||||
{
|
||||
if(mRotationDelayLabel == null)
|
||||
{
|
||||
mRotationDelayLabel = new Label("Rotation Delay (seconds)");
|
||||
}
|
||||
|
||||
return mRotationDelayLabel;
|
||||
}
|
||||
|
||||
public Button getResetButton()
|
||||
{
|
||||
if(mResetButton == null)
|
||||
{
|
||||
mResetButton = new Button("Reset");
|
||||
mResetButton.setOnAction(event -> {
|
||||
mChannelMultiFrequencyPreference.resetRotationDelay();
|
||||
int currentDelay = (int)(mChannelMultiFrequencyPreference.getRotationDelay() / 1000);
|
||||
getRotationDelaySpinner().getValueFactory().setValue(currentDelay);
|
||||
});
|
||||
}
|
||||
|
||||
return mResetButton;
|
||||
}
|
||||
|
||||
public Label getHelpText()
|
||||
{
|
||||
if(mHelpText == null)
|
||||
{
|
||||
mHelpText = new Label(HELP_TEXT);
|
||||
mHelpText.setWrapText(true);
|
||||
}
|
||||
|
||||
return mHelpText;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user