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

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

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

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

View File

@@ -1,21 +1,23 @@
/*
* ******************************************************************************
* sdrtrunk
* 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.controller.channel;
@@ -39,6 +41,14 @@ import io.github.dsheirer.source.config.SourceConfigRecording;
import io.github.dsheirer.source.config.SourceConfigTuner;
import io.github.dsheirer.source.config.SourceConfiguration;
import io.github.dsheirer.source.tuner.channel.TunerChannel;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.util.Callback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -64,14 +74,14 @@ public class Channel extends Configuration implements Listener<SourceEvent>
private EventLogConfiguration mEventLogConfiguration = new EventLogConfiguration();
private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
private String mAliasListName;
private String mSystem = "System";
private String mSite = "Site";
private String mName = "Channel";
private StringProperty mAliasListName = new SimpleStringProperty();
private StringProperty mSystem = new SimpleStringProperty();
private StringProperty mSite = new SimpleStringProperty();
private StringProperty mName = new SimpleStringProperty();
private boolean mProcessing;
private boolean mAutoStart;
private Integer mAutoStartOrder;
private BooleanProperty mProcessing = new SimpleBooleanProperty();
private BooleanProperty mAutoStart = new SimpleBooleanProperty();
private IntegerProperty mAutoStartOrder = new SimpleIntegerProperty();
private boolean mSelected;
private TunerChannel mTunerChannel = null;
@@ -100,7 +110,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
public Channel(String channelName)
{
this();
mName = channelName;
mName.set(channelName);
}
/**
@@ -111,52 +121,119 @@ public class Channel extends Configuration implements Listener<SourceEvent>
mChannelID = UNIQUE_ID++;
}
/**
* Creates a (new) deep copy of this channel
*/
public Channel copyOf()
{
Channel channel = new Channel(mName);
channel.setSystem(mSystem);
channel.setSite(mSite);
channel.setAliasListName(mAliasListName);
channel.setAutoStart(mAutoStart);
channel.setAutoStartOrder(mAutoStartOrder);
Channel channel = new Channel(mName.get());
channel.setSystem(mSystem.get());
channel.setSite(mSite.get());
channel.setAliasListName(mAliasListName.get());
channel.setAutoStart(mAutoStart.get());
channel.setAutoStartOrder(mAutoStartOrder.get());
AuxDecodeConfiguration aux = new AuxDecodeConfiguration();
AuxDecodeConfiguration auxCopy = new AuxDecodeConfiguration();
for(DecoderType auxType : aux.getAuxDecoders())
if(getAuxDecodeConfiguration() != null)
{
aux.addAuxDecoder(auxType);
for(DecoderType auxType : getAuxDecodeConfiguration().getAuxDecoders())
{
auxCopy.addAuxDecoder(auxType);
}
}
channel.setAuxDecodeConfiguration(aux);
channel.setAuxDecodeConfiguration(auxCopy);
channel.setDecodeConfiguration(DecoderFactory.copy(mDecodeConfiguration));
EventLogConfiguration log = new EventLogConfiguration();
EventLogConfiguration logCopy = new EventLogConfiguration();
for(EventLogType logType : mEventLogConfiguration.getLoggers())
if(mEventLogConfiguration != null)
{
log.addLogger(logType);
for(EventLogType logType : mEventLogConfiguration.getLoggers())
{
logCopy.addLogger(logType);
}
}
channel.setEventLogConfiguration(log);
channel.setEventLogConfiguration(logCopy);
RecordConfiguration record = new RecordConfiguration();
RecordConfiguration recordCopy = new RecordConfiguration();
for(RecorderType recordType : mRecordConfiguration.getRecorders())
if(mRecordConfiguration != null)
{
record.addRecorder(recordType);
for(RecorderType recordType : mRecordConfiguration.getRecorders())
{
recordCopy.addRecorder(recordType);
}
}
channel.setRecordConfiguration(record);
channel.setRecordConfiguration(recordCopy);
channel.setSourceConfiguration(SourceConfigFactory.copy(mSourceConfiguration));
return channel;
}
/**
* Alias list name property
*/
public StringProperty aliasListNameProperty()
{
return mAliasListName;
}
/**
* System property
*/
public StringProperty systemProperty()
{
return mSystem;
}
/**
* Site property
*/
public StringProperty siteProperty()
{
return mSite;
}
/**
* Channel name property
*/
public StringProperty nameProperty()
{
return mName;
}
/**
* Processing property. Indicates if this channel configuration is currently processing.
*/
public BooleanProperty processingProperty()
{
return mProcessing;
}
/**
* Auto-Start property. Indicates if this channel is setup for auto-start
*/
public BooleanProperty autoStartProperty()
{
return mAutoStart;
}
/**
* Auto-start order property. Indicates the order for starting channels that are flagged for auto-start.
*/
public IntegerProperty autoStartOrderProperty()
{
return mAutoStartOrder;
}
/**
* Unique identifier for this channel. Value is transient (not persisted).
*/
@@ -214,17 +291,17 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JacksonXmlProperty(isAttribute = true, localName = "system")
public String getSystem()
{
return mSystem;
return mSystem.get();
}
public void setSystem(String system)
{
mSystem = system;
mSystem.set(system);
}
public boolean hasSystem()
{
return mSystem != null;
return mSystem != null && mSystem.get() != null;
}
/**
@@ -233,17 +310,17 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JacksonXmlProperty(isAttribute = true, localName = "site")
public String getSite()
{
return mSite;
return mSite.get();
}
public void setSite(String site)
{
mSite = site;
mSite.set(site);
}
public boolean hasSite()
{
return mSite != null;
return mSite != null && mSite.get() != null;
}
/**
@@ -252,7 +329,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JacksonXmlProperty(isAttribute = true, localName = "name")
public String getName()
{
return mName;
return mName.get();
}
/**
@@ -260,7 +337,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
*/
public void setName(String name)
{
mName = name;
mName.set(name);
}
/**
@@ -284,7 +361,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JsonIgnore
public boolean isProcessing()
{
return mProcessing;
return mProcessing.get();
}
/**
@@ -296,7 +373,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
*/
void setProcessing(boolean processing)
{
mProcessing = processing;
mProcessing.set(processing);
}
/**
@@ -310,7 +387,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JacksonXmlProperty(isAttribute = true, localName = "enabled")
public boolean getAutoStart()
{
return mAutoStart;
return mAutoStart.get();
}
/**
@@ -319,7 +396,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JsonIgnore
public boolean isAutoStart()
{
return mAutoStart;
return mAutoStart.get();
}
/**
@@ -329,7 +406,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
*/
public void setAutoStart(boolean autoStart)
{
mAutoStart = autoStart;
mAutoStart.set(autoStart);
}
/**
@@ -341,7 +418,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JacksonXmlProperty(isAttribute = true, localName = "order")
public Integer getAutoStartOrder()
{
return mAutoStartOrder;
return mAutoStartOrder.get();
}
/**
@@ -351,7 +428,14 @@ public class Channel extends Configuration implements Listener<SourceEvent>
*/
public void setAutoStartOrder(Integer order)
{
mAutoStartOrder = order;
if(order != null)
{
mAutoStartOrder.set(order);
}
else
{
mAutoStartOrder.setValue(null);
}
}
/**
@@ -362,7 +446,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JsonIgnore
public boolean hasAutoStartOrder()
{
return mAutoStartOrder != null;
return mAutoStartOrder != null && mAutoStartOrder.getValue() != null;
}
/**
@@ -372,7 +456,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
@JacksonXmlProperty(isAttribute = false, localName = "alias_list_name")
public String getAliasListName()
{
return mAliasListName;
return mAliasListName.get();
}
/**
@@ -381,15 +465,7 @@ public class Channel extends Configuration implements Listener<SourceEvent>
*/
public void setAliasListName(String name)
{
mAliasListName = name;
}
/**
* Indicates if this channel has a non-null, non-empty alias list name specified.
*/
public boolean hasAliasList()
{
return mAliasListName != null && !mAliasListName.isEmpty();
mAliasListName.set(name);
}
/**
@@ -558,8 +634,6 @@ public class Channel extends Configuration implements Listener<SourceEvent>
/**
* Current channel frequency correction value (when this channel is processing)
*
* @return
*/
@JsonIgnore
public int getChannelFrequencyCorrection()
@@ -595,4 +669,13 @@ public class Channel extends Configuration implements Listener<SourceEvent>
{
return Objects.hash(getChannelID());
}
/**
* Creates an observable property extractor for use with observable lists to detect changes internal to this object.
*/
public static Callback<Channel,Observable[]> extractor()
{
return (Channel c) -> new Observable[] {c.processingProperty(), c.nameProperty(), c.aliasListNameProperty(),
c.autoStartOrderProperty(), c.autoStartProperty(), c.siteProperty(), c.systemProperty()};
}
}

View File

@@ -1,306 +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.controller.channel;
import com.jidesoft.swing.JideSplitPane;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
import io.github.dsheirer.source.SourceManager;
import net.coderazzi.filters.gui.AutoChoices;
import net.coderazzi.filters.gui.TableFilterHeader;
import net.miginfocom.swing.MigLayout;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ChannelController extends JPanel implements ActionListener, ListSelectionListener
{
private static final long serialVersionUID = 1L;
private static final String TABLE_PREFERENCE_KEY = "channel.controller";
private ChannelModel mChannelModel;
private ChannelProcessingManager mChannelProcessingManager;
private JTable mChannelTable;
private JTableColumnWidthMonitor mColumnWidthMonitor;
private TableFilterHeader mTableFilterHeader;
private ChannelEditor mEditor;
private UserPreferences mUserPreferences;
private static final String NEW_CHANNEL = "New";
private static final String COPY_CHANNEL = "Copy";
private static final String DELETE_CHANNEL = "Delete";
private JButton mNewChannelButton = new JButton(NEW_CHANNEL);
private JButton mCopyChannelButton = new JButton(COPY_CHANNEL);
private JButton mDeleteChannelButton = new JButton(DELETE_CHANNEL);
public ChannelController(ChannelModel channelModel, ChannelProcessingManager channelProcessingManager,
ChannelMapModel channelMapModel, SourceManager sourceManager, AliasModel aliasModel,
UserPreferences userPreferences)
{
mChannelModel = channelModel;
mChannelProcessingManager = channelProcessingManager;
mEditor = new ChannelEditor(channelModel, channelProcessingManager, channelMapModel, sourceManager, aliasModel);
mChannelModel.addListener(mEditor);
mChannelProcessingManager.addChannelEventListener(mEditor);
mUserPreferences = userPreferences;
init();
}
private void init()
{
setLayout(new MigLayout("", "[grow,fill]", "[grow,fill]"));
//System Configuration View and Editor
mChannelTable = new JTable(mChannelModel);
mChannelTable.setDefaultRenderer(String.class, new ChannelTableCellRenderer());
mChannelTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mChannelTable.getSelectionModel().addListSelectionListener(this);
mChannelTable.setAutoCreateRowSorter(true);
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mChannelTable, TABLE_PREFERENCE_KEY);
mTableFilterHeader = new TableFilterHeader(mChannelTable, AutoChoices.ENABLED);
mTableFilterHeader.setFilterOnUpdates(true);
JScrollPane channelScroller = new JScrollPane(mChannelTable);
JPanel buttonsPanel = new JPanel();
buttonsPanel.setLayout(
new MigLayout("insets 0 0 0 0", "[grow,fill][grow,fill][grow,fill]", "[]"));
mNewChannelButton.addActionListener(this);
mNewChannelButton.setToolTipText("Create a new channel configuration");
buttonsPanel.add(mNewChannelButton);
mCopyChannelButton.addActionListener(this);
mCopyChannelButton.setEnabled(false);
mCopyChannelButton.setToolTipText("Create a copy of the currently selected channel configuration");
buttonsPanel.add(mCopyChannelButton);
mDeleteChannelButton.addActionListener(this);
mDeleteChannelButton.setEnabled(false);
mDeleteChannelButton.setToolTipText("Delete the currently selected channel configuration");
buttonsPanel.add(mDeleteChannelButton);
JPanel listAndButtonsPanel = new JPanel();
listAndButtonsPanel.setLayout(
new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill][]"));
listAndButtonsPanel.add(channelScroller, "wrap");
listAndButtonsPanel.add(buttonsPanel);
JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
splitPane.setDividerSize(5);
splitPane.setShowGripper(true);
//Shrink this guy so that he'll eventually be expanded to the scroller preferred size
listAndButtonsPanel.setPreferredSize(new Dimension(10, 10));
//Attempt to get a 60:40 vertical sizing preference
JScrollPane listScroller = new JScrollPane(listAndButtonsPanel);
listScroller.setPreferredSize(new Dimension(800, 58));
JScrollPane editorScroller = new JScrollPane(mEditor);
editorScroller.setPreferredSize(new Dimension(800, 42));
splitPane.add(listScroller);
splitPane.add(editorScroller);
add(splitPane);
}
@Override
public void valueChanged(ListSelectionEvent event)
{
//This limits event firing to only when selection is complete
if(!event.getValueIsAdjusting())
{
int selectedRow = mChannelTable.getSelectedRow();
if(selectedRow != -1)
{
int index = mChannelTable.convertRowIndexToModel(mChannelTable.getSelectedRow());
Channel channel = mChannelModel.getChannelAtIndex(index);
if(channel != null)
{
mEditor.setItem(channel);
mCopyChannelButton.setEnabled(true);
mDeleteChannelButton.setEnabled(true);
}
}
else
{
mEditor.setItem(null);
mCopyChannelButton.setEnabled(false);
mDeleteChannelButton.setEnabled(false);
}
}
}
/**
* Adds the channel to the channel model/table and scrolls to view it
*/
private void addChannel(Channel channel)
{
//HACK: when inserting a row to the model, the JTable gets
//notified and attempts to tell the coderazzi table filter
//adaptive choices filter to refresh before the table filter is
//notified of the row additions, causing an index out of bounds
//exception. We turn off adaptive choices temporarily, add the
//channel, and turn on adaptive choices again.
mTableFilterHeader.setAdaptiveChoices(false);
int index = mChannelModel.addChannel(channel);
mTableFilterHeader.setAdaptiveChoices(true);
if(index >= 0)
{
int translatedIndex = mChannelTable.convertRowIndexToView(index);
if(translatedIndex >= 0)
{
mChannelTable.setRowSelectionInterval(translatedIndex, translatedIndex);
mChannelTable.scrollRectToVisible(
new Rectangle(mChannelTable.getCellRect(translatedIndex, 0, true)));
}
}
}
private Channel getSelectedChannel()
{
int index = mChannelTable.getSelectedRow();
if(index >= 0)
{
return mChannelModel.getChannelAtIndex(
mChannelTable.convertRowIndexToModel(index));
}
return null;
}
/**
* Responds to New, Copy and Delete Channel button invocations
*/
@Override
public void actionPerformed(ActionEvent event)
{
switch(event.getActionCommand())
{
case NEW_CHANNEL:
addChannel(new Channel("New Channel"));
break;
case COPY_CHANNEL:
Channel selected = getSelectedChannel();
if(selected != null)
{
addChannel(selected.copyOf());
}
break;
case DELETE_CHANNEL:
Channel toDelete = getSelectedChannel();
if(toDelete != null)
{
int choice = JOptionPane.showConfirmDialog(ChannelController.this,
"Do you want to delete this channel?", "Delete Channel?",
JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
if(choice == JOptionPane.YES_OPTION)
{
mChannelModel.removeChannel(toDelete);
}
}
break;
default:
break;
}
}
public class ChannelTableCellRenderer extends DefaultTableCellRenderer
{
private static final long serialVersionUID = 1L;
@Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus, int row,
int column)
{
Component component = super.getTableCellRendererComponent(
table, value, isSelected, hasFocus, row, column);
ChannelModel model = (ChannelModel)table.getModel();
int index = table.convertRowIndexToModel(row);
Channel channel = model.getChannelAtIndex(index);
boolean enabled = channel.isProcessing();
if(isSelected)
{
if(enabled)
{
component.setBackground(Color.YELLOW);
}
else
{
component.setBackground(table.getSelectionBackground());
}
}
else
{
if(enabled)
{
component.setBackground(Color.GREEN);
}
else
{
component.setBackground(table.getBackground());
}
}
return component;
}
}
}

View File

@@ -1,262 +0,0 @@
/*
* ******************************************************************************
* sdrtrunk
* 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.
*
* 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.controller.channel;
import com.jidesoft.swing.JideTabbedPane;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
import io.github.dsheirer.gui.editor.Editor;
import io.github.dsheirer.gui.editor.EditorValidationException;
import io.github.dsheirer.module.decode.AuxDecodeConfigurationEditor;
import io.github.dsheirer.module.decode.DecodeConfigurationEditor;
import io.github.dsheirer.module.log.EventLogConfigurationEditor;
import io.github.dsheirer.record.RecordConfigurationEditor;
import io.github.dsheirer.sample.Listener;
import io.github.dsheirer.source.SourceConfigurationEditor;
import io.github.dsheirer.source.SourceManager;
import net.miginfocom.swing.MigLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ChannelEditor extends Editor<Channel> implements ActionListener, Listener<ChannelEvent>
{
private static final long serialVersionUID = 1L;
private static final String ACTION_START = "Start";
private static final String ACTION_STOP = "Stop";
private static final String ACTION_SAVE = "Save";
private static final String ACTION_RESET = "Reset";
private NameConfigurationEditor mNameConfigurationEditor;
private SourceConfigurationEditor mSourceConfigurationEditor;
private DecodeConfigurationEditor mDecodeConfigurationEditor;
private AuxDecodeConfigurationEditor mAuxDecodeConfigurationEditor;
private EventLogConfigurationEditor mEventLogConfigurationEditor;
private RecordConfigurationEditor mRecordConfigurationEditor;
private JButton mProcessingStateButton = new JButton(ACTION_START);
private JLabel mChannelName = new JLabel("Channel:");
private ChannelModel mChannelModel;
private ChannelProcessingManager mChannelProcessingManager;
private ChannelMapModel mChannelMapModel;
private SourceManager mSourceManager;
private boolean mChannelStartRequested = false;
public ChannelEditor(ChannelModel channelModel,
ChannelProcessingManager channelProcessingManager,
ChannelMapModel channelMapModel,
SourceManager sourceManager,
AliasModel aliasModel)
{
mChannelModel = channelModel;
mChannelProcessingManager = channelProcessingManager;
mChannelMapModel = channelMapModel;
mSourceManager = sourceManager;
mNameConfigurationEditor = new NameConfigurationEditor(aliasModel, mChannelModel);
init();
}
private void init()
{
setLayout(new MigLayout("fill,wrap 3", "[grow,fill][grow,fill][grow,fill]", "[grow,fill][]"));
JideTabbedPane tabs = new JideTabbedPane();
tabs.setFont(this.getFont());
tabs.setForeground(Color.BLACK);
tabs.setTabTrailingComponent(mChannelName);
mNameConfigurationEditor.setSaveRequestListener(this);
tabs.addTab("Name/Alias", mNameConfigurationEditor);
mSourceConfigurationEditor = new SourceConfigurationEditor(mSourceManager);
mSourceConfigurationEditor.setSaveRequestListener(this);
tabs.addTab("Source", mSourceConfigurationEditor);
mDecodeConfigurationEditor = new DecodeConfigurationEditor(mChannelMapModel);
mDecodeConfigurationEditor.setSaveRequestListener(this);
tabs.addTab("Decoder", mDecodeConfigurationEditor);
mAuxDecodeConfigurationEditor = new AuxDecodeConfigurationEditor();
mAuxDecodeConfigurationEditor.setSaveRequestListener(this);
tabs.addTab("Aux Decoders", mAuxDecodeConfigurationEditor);
mEventLogConfigurationEditor = new EventLogConfigurationEditor();
mEventLogConfigurationEditor.setSaveRequestListener(this);
tabs.addTab("Logging", mEventLogConfigurationEditor);
mRecordConfigurationEditor = new RecordConfigurationEditor();
mRecordConfigurationEditor.setSaveRequestListener(this);
tabs.addTab("Recording", mRecordConfigurationEditor);
add(tabs, "span");
mProcessingStateButton.addActionListener(this);
mProcessingStateButton.setEnabled(false);
mProcessingStateButton.setToolTipText("Start/Stop the currently selected channel");
add(mProcessingStateButton);
JButton btnSave = new JButton(ACTION_SAVE);
btnSave.setToolTipText("Save changes to the currently selected channel");
btnSave.addActionListener(ChannelEditor.this);
add(btnSave);
JButton btnReset = new JButton(ACTION_RESET);
btnReset.setToolTipText("Reload the currently selected channel");
btnReset.addActionListener(ChannelEditor.this);
add(btnReset);
}
@Override
public void receive(ChannelEvent event)
{
if(hasItem() && getItem() == event.getChannel())
{
switch(event.getEvent())
{
case NOTIFICATION_CONFIGURATION_CHANGE:
case NOTIFICATION_PROCESSING_START:
case NOTIFICATION_PROCESSING_STOP:
setItem(getItem());
mChannelStartRequested = false;
break;
case NOTIFICATION_DELETE:
setItem(null);
break;
case NOTIFICATION_PROCESSING_START_REJECTED:
if(mChannelStartRequested)
{
JOptionPane.showMessageDialog(ChannelEditor.this, "Channel could not be "
+ "started. This is likely because there are no tuner channels "
+ "available", "Couldn't start channel", JOptionPane.INFORMATION_MESSAGE);
mChannelStartRequested = false;
}
break;
default:
break;
}
}
}
@Override
public void actionPerformed(ActionEvent e)
{
String command = e.getActionCommand();
if(command.contentEquals(ACTION_START))
{
if(hasItem())
{
save();
mChannelStartRequested = true;
mChannelProcessingManager.receive(new ChannelEvent(getItem(), ChannelEvent.Event.REQUEST_ENABLE));
}
}
else if(command.contentEquals(ACTION_STOP))
{
if(hasItem())
{
mChannelProcessingManager.receive(new ChannelEvent(getItem(), ChannelEvent.Event.REQUEST_DISABLE));
}
}
else if(command.contentEquals(ACTION_SAVE))
{
save();
}
else if(command.contentEquals(ACTION_RESET))
{
setItem(getItem());
}
}
/**
* Sets the channel configuration in each of the channel editor components
* Note: this method must be invoked on the swing event dispatch thread
*/
public void setItem(final Channel channel)
{
super.setItem(channel);
mNameConfigurationEditor.setItem(channel);
mSourceConfigurationEditor.setItem(channel);
mDecodeConfigurationEditor.setItem(channel);
mAuxDecodeConfigurationEditor.setItem(channel);
mEventLogConfigurationEditor.setItem(channel);
mRecordConfigurationEditor.setItem(channel);
if(channel != null)
{
mChannelName.setText("Channel: " + channel.getName());
mProcessingStateButton.setText(channel.isProcessing() ? ACTION_STOP : ACTION_START);
mProcessingStateButton.setEnabled(true);
mProcessingStateButton.setBackground(channel.isProcessing() ? Color.GREEN : getBackground());
}
else
{
mChannelName.setText("Channel: ");
mProcessingStateButton.setText(ACTION_START);
mProcessingStateButton.setEnabled(false);
mProcessingStateButton.setBackground(getBackground());
}
}
public void save()
{
if(hasItem())
{
mNameConfigurationEditor.save();
mSourceConfigurationEditor.save();
mDecodeConfigurationEditor.save();
mAuxDecodeConfigurationEditor.save();
mEventLogConfigurationEditor.save();
mRecordConfigurationEditor.save();
try
{
mDecodeConfigurationEditor.validate(mSourceConfigurationEditor);
mDecodeConfigurationEditor.validate(mAuxDecodeConfigurationEditor);
}
catch(EditorValidationException e)
{
e.getEditor().requestFocus();
JOptionPane.showMessageDialog(e.getEditor(), e.getReason(),
"Configuration Error", JOptionPane.ERROR_MESSAGE);
return;
}
ChannelEvent update = new ChannelEvent(getItem(), ChannelEvent.Event.NOTIFICATION_CONFIGURATION_CHANGE);
mChannelModel.receive(update);
}
setModified(false);
}
}

View File

@@ -80,6 +80,22 @@ public class ChannelEvent
(mDescription != null ? " Description:" + mDescription : "");
}
/**
* Creates a request enable channel event
*/
public static ChannelEvent requestEnable(Channel channel)
{
return new ChannelEvent(channel, Event.REQUEST_ENABLE);
}
/**
* Creates a new request disable channel event
*/
public static ChannelEvent requestDisable(Channel channel)
{
return new ChannelEvent(channel, Event.REQUEST_DISABLE);
}
/**
* Channel events to describe the specific event
*/

View File

@@ -0,0 +1,36 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 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.controller.channel;
/**
* Exception for channel start/stop errors
*/
public class ChannelException extends Exception
{
private static final long serialVersionUID = 1L;
public ChannelException(String message )
{
this( message, null );
}
public ChannelException(String message, Throwable cause )
{
super( message, cause );
}
}

View File

@@ -1,62 +1,94 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 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.controller.channel;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.controller.channel.Channel.ChannelType;
import io.github.dsheirer.controller.channel.ChannelEvent.Event;
import io.github.dsheirer.module.decode.DecoderType;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Channel Model
*/
public class ChannelModel extends AbstractTableModel implements Listener<ChannelEvent>
public class ChannelModel implements Listener<ChannelEvent>
{
private static final long serialVersionUID = 1L;
public static final int COLUMN_ENABLED = 0;
public static final int COLUMN_SYSTEM = 1;
public static final int COLUMN_SITE = 2;
public static final int COLUMN_NAME = 3;
public static final int COLUMN_ALIAS_LIST = 4;
public static final int COLUMN_SOURCE = 5;
public static final int COLUMN_DECODER = 6;
public static final int COLUMN_AUTO_START = 7;
private static final String[] COLUMN_NAMES = new String[] {"Playing", "System", "Site", "Name", "Alias List",
"Source", "Decoder", "Auto-Start"};
private static final String VALUE_YES = "Yes";
private List<Channel> mChannels = new CopyOnWriteArrayList<>();
private List<Channel> mTrafficChannels = new CopyOnWriteArrayList<>();
private ObservableList<Channel> mChannels = FXCollections.observableArrayList(Channel.extractor());
private ObservableList<Channel> mTrafficChannels = FXCollections.observableArrayList(Channel.extractor());
private Broadcaster<ChannelEvent> mChannelEventBroadcaster = new Broadcaster();
private AliasModel mAliasModel;
public ChannelModel()
public ChannelModel(AliasModel aliasModel)
{
mAliasModel = aliasModel;
//Register a listener to detect channel changes and broadcast change events to cause playlist save requests
ChannelListChangeListener changeListener = new ChannelListChangeListener();
mChannels.addListener(changeListener);
mTrafficChannels.addListener(changeListener);
}
/**
* Observable list of channel configurations managed by this model
*/
public ObservableList<Channel> channelList()
{
return mChannels;
}
/**
* Observable list of traffic channel configurations managed by this model
*/
public ObservableList<Channel> trafficChannelList()
{
return mTrafficChannels;
}
/**
* Removes all channels and traffic channels and fires remove event for each.
*/
public void clear()
{
List<Channel> trafficChannels = new ArrayList<>(mTrafficChannels);
for(Channel trafficChannel: trafficChannels)
{
removeChannel(trafficChannel);
}
List<Channel> channels = new ArrayList<>(mChannels);
for(Channel channel: channels)
{
removeChannel(channel);
}
}
/**
@@ -80,7 +112,7 @@ public class ChannelModel extends AbstractTableModel implements Listener<Channel
/**
* Returns a list of unique system values from across the channel set
*/
public List<String> getSystems()
public List<String> getSystemNames()
{
List<String> systems = new ArrayList<>();
@@ -97,33 +129,6 @@ public class ChannelModel extends AbstractTableModel implements Listener<Channel
return systems;
}
/**
* Returns a list of unique site values for all channels that have a matching
* system value
*/
public List<String> getSites(String system)
{
List<String> sites = new ArrayList<>();
if(system != null)
{
for(Channel channel : mChannels)
{
if(channel.hasSystem() &&
system.equals(channel.getSystem()) &&
channel.hasSite() &&
!sites.contains(channel.getSite()))
{
sites.add(channel.getSite());
}
}
}
Collections.sort(sites);
return sites;
}
/**
* Broadcasts the channel event to all registered listeners
*/
@@ -136,22 +141,9 @@ public class ChannelModel extends AbstractTableModel implements Listener<Channel
{
case NOTIFICATION_CONFIGURATION_CHANGE:
case NOTIFICATION_SELECTION_CHANGE:
index = mChannels.indexOf(event.getChannel());
if(index > 0)
{
fireTableRowsUpdated(index, index);
}
//rebroadcast this event to any listeners
mChannelEventBroadcaster.broadcast(event);
break;
case NOTIFICATION_PROCESSING_START:
case NOTIFICATION_PROCESSING_STOP:
index = mChannels.indexOf(event.getChannel());
if(index > 0)
{
fireTableRowsUpdated(index, index);
}
break;
default:
break;
}
@@ -178,30 +170,21 @@ public class ChannelModel extends AbstractTableModel implements Listener<Channel
/**
* Adds the channel to the model and broadcasts a channel add event
*/
public int addChannel(Channel channel)
public void addChannel(Channel channel)
{
int index = -1;
switch(channel.getChannelType())
{
case STANDARD:
mChannels.add(channel);
index = mChannels.size() - 1;
fireTableRowsInserted(index, index);
//Make sure the alias model has the alias list referred to by the channel
mAliasModel.addAliasList(channel.getAliasListName());
break;
case TRAFFIC:
mTrafficChannels.add(channel);
index = mChannels.size() - 1;
break;
default:
break;
}
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_ADD));
return index;
}
/**
@@ -214,14 +197,7 @@ public class ChannelModel extends AbstractTableModel implements Listener<Channel
switch(channel.getChannelType())
{
case STANDARD:
int index = mChannels.indexOf(channel);
mChannels.remove(channel);
if(index >= 0)
{
fireTableRowsDeleted(index, index);
}
break;
case TRAFFIC:
mTrafficChannels.remove(channel);
@@ -229,8 +205,6 @@ public class ChannelModel extends AbstractTableModel implements Listener<Channel
default:
break;
}
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_DELETE));
}
}
@@ -312,89 +286,59 @@ public class ChannelModel extends AbstractTableModel implements Listener<Channel
mChannelEventBroadcaster.removeListener(listener);
}
//Table Model Interface Methods - standard channels only
@Override
public int getRowCount()
/**
* List of site names from all channels in the model
*/
public List<String> getSiteNames()
{
return mChannels.size();
}
List<String> sites = new ArrayList<>();
@Override
public int getColumnCount()
{
return COLUMN_NAMES.length;
}
@Override
public String getColumnName(int columnIndex)
{
if(columnIndex < COLUMN_NAMES.length)
for(Channel channel: mChannels)
{
return COLUMN_NAMES[columnIndex];
String site = channel.getSite();
if(site != null && !site.isEmpty() && !sites.contains(site))
{
sites.add(site);
}
}
return null;
return sites;
}
@Override
public Class<?> getColumnClass(int columnIndex)
/**
* Observable list change listener for both channels and traffic channels lists
*/
public class ChannelListChangeListener implements ListChangeListener<Channel>
{
return String.class;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
Channel channel = mChannels.get(rowIndex);
switch(columnIndex)
@Override
public void onChanged(Change<? extends Channel> change)
{
case COLUMN_ENABLED:
return channel.isProcessing() ? VALUE_YES : null;
case COLUMN_SYSTEM:
return channel.getSystem();
case COLUMN_SITE:
return channel.getSite();
case COLUMN_NAME:
return channel.getName();
case COLUMN_ALIAS_LIST:
return channel.getAliasListName();
case COLUMN_SOURCE:
return channel.getSourceConfiguration().getDescription();
case COLUMN_DECODER:
return channel.getDecodeConfiguration().getDecoderType().getShortDisplayString();
case COLUMN_AUTO_START:
if(channel.isAutoStart())
while(change.next())
{
if(change.wasAdded())
{
if(channel.hasAutoStartOrder())
for(Channel channel: change.getAddedSubList())
{
return channel.getAutoStartOrder();
}
else
{
return VALUE_YES;
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_ADD));
}
}
break;
else if(change.wasRemoved())
{
for(Channel channel: change.getRemoved())
{
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_DELETE));
}
}
else if(change.wasUpdated())
{
for(int x = change.getFrom(); x < change.getTo(); x++)
{
mChannelEventBroadcaster.broadcast(new ChannelEvent(change.getList().get(x),
Event.NOTIFICATION_CONFIGURATION_CHANGE));
}
}
}
}
return null;
}
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
{
throw new IllegalArgumentException("Not yet implemented");
}
public void createChannel(DecoderType decoderType, long frequency)
{
throw new UnsupportedOperationException("Not yet implemented");
}
}

View File

@@ -118,6 +118,15 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
return mProcessingChains.containsKey(channel) && mProcessingChains.get(channel).isProcessing();
}
/**
* Indicates if any channels are currently processing.
* @return true if channels are processing.
*/
public boolean isProcessing()
{
return !mProcessingChains.isEmpty();
}
/**
* Returns the current processing chain associated with the channel, or
* null if a processing chain is not currently setup for the channel
@@ -164,38 +173,52 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
case REQUEST_ENABLE:
if(!isProcessing(channel))
{
startProcessing(event);
try
{
startProcessing(event);
}
catch(ChannelException ce)
{
mLog.error("Error starting requested channel [" + channel.getName() + "] - " + ce.getMessage());
}
}
break;
case REQUEST_DISABLE:
if(channel.isProcessing())
{
switch(channel.getChannelType())
try
{
case STANDARD:
stopProcessing(channel, true);
break;
case TRAFFIC:
//Don't remove traffic channel processing chains
//until explicitly deleted, so that we can reuse them
stopProcessing(channel, false);
break;
default:
break;
switch(channel.getChannelType())
{
case STANDARD:
stopProcessing(channel, true);
break;
case TRAFFIC:
//Don't remove traffic channel processing chains
//until explicitly deleted, so that we can reuse them
stopProcessing(channel, false);
break;
default:
break;
}
}
catch(ChannelException ce)
{
mLog.error("Error stopping channel [" + channel.getName() + "] - " + ce.getMessage());
}
}
break;
case NOTIFICATION_DELETE:
if(channel.isProcessing())
{
stopProcessing(channel, true);
}
break;
case NOTIFICATION_CONFIGURATION_CHANGE:
if(isProcessing(channel))
{
stopProcessing(channel, true);
startProcessing(event);
try
{
stopProcessing(channel, true);
}
catch(ChannelException ce)
{
mLog.error("Error stopping deleted channel [" + channel.getName() + "] - " + ce.getMessage());
}
}
break;
default:
@@ -203,12 +226,32 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
}
}
/**
* Starts the specified channel.
* @param channel to start
* @throws ChannelException if the channel can't be started
*/
public void start(Channel channel) throws ChannelException
{
startProcessing(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_ENABLE));
}
/**
* Stops the specified channel
* @param channel to stop
*/
public void stop(Channel channel) throws ChannelException
{
stopProcessing(channel, !channel.isTrafficChannel());
}
/**
* Starts a channel/processing chain
*
* @param event that requested the channel start
*/
private void startProcessing(ChannelEvent event)
private void startProcessing(ChannelEvent event) throws ChannelException
{
Channel channel = event.getChannel();
@@ -217,7 +260,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
//If we're already processing, ignore the request
if(processingChain != null && processingChain.isProcessing())
{
return;
throw new ChannelException("Channel is already playing");
}
//Ensure that we can get a source before we construct a new processing chain
@@ -240,7 +283,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
mChannelEventBroadcaster.broadcast(new ChannelEvent(channel,
ChannelEvent.Event.NOTIFICATION_PROCESSING_START_REJECTED, TUNER_UNAVAILABLE_DESCRIPTION));
return;
throw new ChannelException("No Tuner Available");
}
if(processingChain == null)
@@ -277,8 +320,17 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
if(toShutdown != null)
{
mLog.info("Channel source error detected - stopping channel [" + toShutdown.getName() + "]");
stopProcessing(toShutdown, true);
mLog.warn("Channel source error detected - stopping channel [" + toShutdown.getName() + "]");
try
{
stopProcessing(toShutdown, true);
}
catch(ChannelException ce)
{
mLog.error("Error stopping channel [" + channel.getName() + "] with source error - " +
ce.getMessage());
}
}
}
});
@@ -380,7 +432,7 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
* @param channel to stop
* @param remove set to true to remove the associated processing chain.
*/
private void stopProcessing(Channel channel, boolean remove)
private void stopProcessing(Channel channel, boolean remove) throws ChannelException
{
channel.setProcessing(false);
@@ -412,6 +464,10 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
processingChain.dispose();
}
}
else
{
throw new ChannelException("Channel is not currently playing");
}
}
/**
@@ -423,7 +479,14 @@ public class ChannelProcessingManager implements Listener<ChannelEvent>
for(Channel channel : channelsToStop)
{
stopProcessing(channel, true);
try
{
stopProcessing(channel, true);
}
catch(ChannelException ce)
{
mLog.error("Error stopping channel [" + channel.getName() + "] - " + ce.getMessage());
}
}
}

View File

@@ -30,9 +30,10 @@ public class ChannelSelectionManager implements Listener<ChannelEvent>
* Manages channel selection state to ensure that only one channel is ever
* in a selected state.
*/
public ChannelSelectionManager(Listener<ChannelEvent> listener)
public ChannelSelectionManager(ChannelModel channelModel)
{
mChannelEventListener = listener;
mChannelEventListener = channelModel;
channelModel.addListener(this::receive);
}
@Override

View File

@@ -1,344 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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.controller.channel;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.gui.editor.Editor;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collections;
import java.util.List;
public class NameConfigurationEditor extends Editor<Channel> implements DocumentListener
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger(NameConfigurationEditor.class);
private static ComboBoxModel<String> EMPTY_SYSTEM_MODEL = new DefaultComboBoxModel<>();
private static ComboBoxModel<String> EMPTY_SITE_MODEL = new DefaultComboBoxModel<>();
private static ComboBoxModel<String> EMPTY_ALIAS_LIST_MODEL = new DefaultComboBoxModel<>();
private AliasModel mAliasModel;
private ChannelModel mChannelModel;
private JTextField mChannelName;
private JComboBox<String> mSystemNameCombo;
private JComboBox<String> mSiteNameCombo;
private JComboBox<String> mAliasListCombo;
private JCheckBox mAutoStartCheckBox;
private JSpinner mAutoStartOrder;
public NameConfigurationEditor(AliasModel aliasModel, ChannelModel model)
{
mAliasModel = aliasModel;
mChannelModel = model;
init();
}
private void init()
{
setLayout(new MigLayout("fill,wrap 4", "[right][grow,fill][right][grow,fill]", "[][][grow]"));
mChannelName = new JTextField("");
mChannelName.getDocument().addDocumentListener(this);
mChannelName.setEnabled(false);
add(new JLabel("Name:"));
add(mChannelName);
mSystemNameCombo = new JComboBox<>(EMPTY_SYSTEM_MODEL);
mSystemNameCombo.setEnabled(false);
mSystemNameCombo.setEditable(true);
mSystemNameCombo.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
Object selected = mSystemNameCombo.getSelectedItem();
if(selected != null)
{
String system = (String)selected;
List<String> sites = mChannelModel.getSites(system);
if(!sites.contains(getItem().getSite()))
{
sites.add(getItem().getSite());
}
if(sites.isEmpty())
{
mSiteNameCombo.setModel(EMPTY_SITE_MODEL);
}
else
{
mSiteNameCombo.setModel(new DefaultComboBoxModel<String>(
sites.toArray(new String[0])));
;
}
mSiteNameCombo.setSelectedItem(getItem().getSite());
}
setModified(true);
}
});
add(new JLabel("System:"));
add(mSystemNameCombo);
mAliasListCombo = new JComboBox<String>(EMPTY_ALIAS_LIST_MODEL);
mAliasListCombo.setEnabled(false);
mAliasListCombo.setEditable(true);
mAliasListCombo.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(new JLabel("Alias List:"));
add(mAliasListCombo);
mSiteNameCombo = new JComboBox<>(EMPTY_SITE_MODEL);
mSiteNameCombo.setEnabled(false);
mSiteNameCombo.setEditable(true);
mSiteNameCombo.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
setModified(true);
}
});
add(new JLabel("Site:"));
add(mSiteNameCombo);
JPanel autoStartPanel = new JPanel();
mAutoStartCheckBox = new JCheckBox("Auto-Start");
mAutoStartCheckBox.setToolTipText("Automatically start this channel each time the application runs.");
mAutoStartCheckBox.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
mAutoStartOrder.setEnabled(mAutoStartCheckBox.isSelected());
setModified(true);
}
});
autoStartPanel.add(mAutoStartCheckBox);
SpinnerModel model = new SpinnerNumberModel(0,0,500,1);
mAutoStartOrder = new JSpinner(model);
mAutoStartOrder.setEnabled(false);
mAutoStartOrder.setPreferredSize(new Dimension(60, mAutoStartOrder.getPreferredSize().height));
mAutoStartOrder.setToolTipText("Auto-start order: 1 (high) <> 500 (low). 0 = no ordering");
mAutoStartOrder.addChangeListener(new ChangeListener()
{
@Override
public void stateChanged(ChangeEvent e)
{
setModified(true);
}
});
autoStartPanel.add(new JLabel("Order:"));
autoStartPanel.add(mAutoStartOrder);
add(new JLabel()); //Empty space holder
add(autoStartPanel);
}
@Override
public void save()
{
if(hasItem() && isModified())
{
getItem().setName(mChannelName.getText());
Object system = mSystemNameCombo.getSelectedItem();
getItem().setSystem((system == null ? null : (String)system));
Object site = mSiteNameCombo.getSelectedItem();
getItem().setSite((site == null ? null : (String)site));
Object aliasList = mAliasListCombo.getSelectedItem();
getItem().setAliasListName(aliasList == null ? null : (String)aliasList);
getItem().setAutoStart(mAutoStartCheckBox.isSelected());
int order = ((Number)mAutoStartOrder.getValue()).intValue();
getItem().setAutoStartOrder((order > 0 ? order : null));
}
setModified(false);
}
private void setControlsEnabled(boolean enabled)
{
if(mChannelName.isEnabled() != enabled)
{
mChannelName.setEnabled(enabled);
}
if(mAliasListCombo.isEnabled() != enabled)
{
mAliasListCombo.setEnabled(enabled);
}
if(mSystemNameCombo.isEnabled() != enabled)
{
mSystemNameCombo.setEnabled(enabled);
}
if(mSiteNameCombo.isEnabled() != enabled)
{
mSiteNameCombo.setEnabled(enabled);
}
if(mAutoStartCheckBox.isEnabled() != enabled)
{
mAutoStartCheckBox.setEnabled(enabled);
}
if(mAutoStartOrder.isEnabled() != enabled)
{
mAutoStartOrder.setEnabled(enabled);
}
}
@Override
public void setItem(Channel channel)
{
super.setItem(channel);
if(hasItem())
{
setControlsEnabled(true);
mChannelName.setText(channel.getName());
List<String> systems = mChannelModel.getSystems();
if(systems.isEmpty())
{
mSystemNameCombo.setModel(EMPTY_SYSTEM_MODEL);
}
else
{
mSystemNameCombo.setModel(new DefaultComboBoxModel<String>(
systems.toArray(new String[0])));
;
}
mSystemNameCombo.setSelectedItem(channel.getSystem());
List<String> sites = mChannelModel.getSites(channel.getSystem());
if(sites.isEmpty())
{
mSiteNameCombo.setModel(EMPTY_SITE_MODEL);
}
else
{
mSiteNameCombo.setModel(new DefaultComboBoxModel<String>(
sites.toArray(new String[0])));
;
}
if(channel.getSite() != null)
{
mSiteNameCombo.setSelectedItem(channel.getSite());
}
List<String> lists = mAliasModel.getListNames();
Collections.sort(lists);
mAliasListCombo.setModel(new DefaultComboBoxModel<String>(
lists.toArray(new String[0])));
if(channel.getAliasListName() != null)
{
mAliasListCombo.setSelectedItem(channel.getAliasListName());
}
if(channel.isAutoStart())
{
mAutoStartCheckBox.setSelected(channel.isAutoStart());
mAutoStartOrder.setEnabled(true);
}
else
{
mAutoStartCheckBox.setSelected(false);
mAutoStartOrder.setEnabled(false);
}
mAutoStartOrder.setValue(channel.hasAutoStartOrder() ? channel.getAutoStartOrder() : 0);
}
else
{
setControlsEnabled(false);
mChannelName.setText(null);
}
setModified(false);
}
@Override
public void insertUpdate(DocumentEvent e)
{
setModified(true);
}
@Override
public void changedUpdate(DocumentEvent e)
{
setModified(true);
}
@Override
public void removeUpdate(DocumentEvent e)
{
setModified(true);
}
}

View File

@@ -1,34 +1,45 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 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.controller.channel.map;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.util.Callback;
import java.util.ArrayList;
import java.util.List;
public class ChannelMap
{
private String mName;
private boolean mInvalid = false;
private List<ChannelRange> mRanges = new ArrayList<ChannelRange>();
private StringProperty mName = new SimpleStringProperty();
private BooleanProperty mValidProperty = new SimpleBooleanProperty();
private ObservableList<ChannelRange> mRanges = FXCollections.observableArrayList(ChannelRange.extractor());
public ChannelMap()
{
@@ -37,12 +48,29 @@ public class ChannelMap
public ChannelMap(String name)
{
mName = name;
mName.set(name);
mRanges.addListener((ListChangeListener<ChannelRange>)c -> validate());
}
public StringProperty nameProperty()
{
return mName;
}
public BooleanProperty validProperty()
{
return mValidProperty;
}
@JsonIgnore
public ObservableList<ChannelRange> getItems()
{
return mRanges;
}
public ChannelMap copyOf()
{
ChannelMap map = new ChannelMap(new String(mName));
ChannelMap map = new ChannelMap(new String(mName.get()));
for(ChannelRange range : mRanges)
{
@@ -53,14 +81,14 @@ public class ChannelMap
}
@JsonIgnore
public boolean isInvalid()
public boolean isValid()
{
return mInvalid;
return mValidProperty.get();
}
public String toString()
{
return mName + (mInvalid ? " - Error" : "");
return mName.get();
}
@JacksonXmlProperty(isAttribute = false, localName = "range")
@@ -71,20 +99,18 @@ public class ChannelMap
public void setRanges(List<ChannelRange> ranges)
{
mRanges = ranges;
validate();
mRanges.setAll(ranges);
}
@JacksonXmlProperty(isAttribute = true, localName = "name")
public String getName()
{
return mName;
return mName.get();
}
public void setName(String name)
{
mName = name;
mName.set(name);
}
public long getFrequency(int channelNumber)
@@ -105,20 +131,27 @@ public class ChannelMap
*/
private void validate()
{
mInvalid = false;
mValidProperty.set(true);
for(ChannelRange channelRange: mRanges)
{
channelRange.setOverlapping(false);
}
for(int x = 0; x < mRanges.size(); x++)
{
if(!mRanges.get(x).isValid())
{
mInvalid = true;
mValidProperty.set(false);
}
for(int y = x + 1; y < mRanges.size(); y++)
{
if(mRanges.get(x).overlaps(mRanges.get(y)))
{
mInvalid = true;
mRanges.get(x).setOverlapping(true);
mRanges.get(y).setOverlapping(true);
mValidProperty.set(false);
}
}
}
@@ -128,15 +161,19 @@ public class ChannelMap
public void addRange(ChannelRange range)
{
mRanges.add(range);
validate();
}
public void removeRange(ChannelRange range)
{
mRanges.remove(range);
validate();
}
/**
* Creates an observable property extractor for use with observable lists to detect changes internal to this object.
*/
public static Callback<ChannelMap,Observable[]> extractor()
{
return (ChannelMap c) -> new Observable[] {c.nameProperty(), c.validProperty(), c.getItems()};
}
}

View File

@@ -1,322 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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.controller.channel.map;
import io.github.dsheirer.controller.channel.map.ChannelMapEvent.Event;
import io.github.dsheirer.controller.channel.map.ChannelRangeModel.ChannelRangeEventListener;
import io.github.dsheirer.gui.editor.DocumentListenerEditor;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableCellEditor;
import javax.swing.text.InternationalFormatter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ChannelMapEditor extends DocumentListenerEditor<ChannelMap>
implements ListSelectionListener, ChannelRangeEventListener
{
private static final long serialVersionUID = 1L;
private JLabel mNameLabel;
private JTextField mNameText;
private ChannelRangeModel mRangeModel;
private JTable mRangeTable;
private JButton mNewButton;
private JButton mDeleteButton;
private JButton mSaveButton;
private JLabel mRangeDescription;
private ChannelMapModel mChannelMapModel;
public ChannelMapEditor( ChannelMapModel channelMapModel )
{
mChannelMapModel = channelMapModel;
init();
}
public ChannelMap getChannelMap()
{
if( hasItem() )
{
return (ChannelMap)getItem();
}
return null;
}
@Override
public void setItem( ChannelMap item )
{
super.setItem( item );
if( hasItem() )
{
ChannelMap map = getChannelMap();
mNameText.setText( map.getName() );
mRangeModel.clear();
mRangeModel.addRanges( map.getRanges() );
mNewButton.setEnabled( true );
}
else
{
mNameText.setText( "" );
mRangeModel.clear();
mNewButton.setEnabled( false );
}
setModified( false );
}
@Override
public void setModified( boolean modified )
{
super.setModified( modified );
mSaveButton.setEnabled( modified );
}
private void init()
{
setLayout( new MigLayout( "fill,wrap 3",
"[grow,fill][grow,fill][grow,fill]", "[][grow][][]" ) );
mNameLabel = new JLabel( "Channel Map:" );
add( mNameLabel );
mNameText = new JTextField();
mNameText.getDocument().addDocumentListener( this );
add( mNameText, "span" );
mRangeModel = new ChannelRangeModel();
mRangeModel.setListener( this );
mRangeTable = new JTable( mRangeModel );
mRangeTable.setPreferredScrollableViewportSize( new Dimension( 300, 150 ) );
mRangeTable.setFillsViewportHeight( true );
mRangeTable.setAutoCreateRowSorter( true );
for( int x = 0; x < mRangeTable.getColumnCount(); x++ )
{
mRangeTable.getColumnModel().getColumn( x )
.setCellEditor( new RangeTableEditor() );
}
mRangeTable.getSelectionModel().addListSelectionListener( new ListSelectionListener()
{
@Override
public void valueChanged( ListSelectionEvent event )
{
if( !event.getValueIsAdjusting() )
{
int index = event.getFirstIndex();
mDeleteButton.setEnabled( index >= 0 );
if( index >= 0 )
{
int modelIndex = mRangeTable.convertRowIndexToModel( index );
if( modelIndex >= 0 )
{
ChannelRange selected = mRangeModel.getChannelRange( modelIndex );
if( selected != null )
{
mRangeDescription.setText( selected.getDescription() );
}
else
{
mRangeDescription.setText( "" );
}
}
}
}
}
} );
JScrollPane scrollPane = new JScrollPane( mRangeTable );
add( scrollPane, "span,grow,push");
mRangeDescription = new JLabel( " " );
add( mRangeDescription, "span" );
mSaveButton = new JButton( "Save" );
mSaveButton.setEnabled( false );
mSaveButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
save();
}
} );
add( mSaveButton );
mNewButton = new JButton( "New Range" );
mNewButton.setEnabled( false );
mNewButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
mRangeModel.addRange( new ChannelRange() );
}
} );
add( mNewButton );
mDeleteButton = new JButton( "DeleteRange" );
mDeleteButton.setEnabled( false );
mDeleteButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
int selectedRow = mRangeTable.getSelectedRow();
if( selectedRow >= 0 )
{
int modelRow = mRangeTable.convertRowIndexToModel( selectedRow );
ChannelRange selectedRange = mRangeModel.getChannelRange( modelRow );
if( selectedRange != null )
{
int choice = JOptionPane.showConfirmDialog( mRangeTable,
"Do you want to delete this channel range?",
"Delete Channel Range?", JOptionPane.YES_NO_OPTION );
if( choice == JOptionPane.YES_OPTION )
{
mRangeModel.removeRange( selectedRange );
mDeleteButton.setEnabled( false );
}
}
}
}
} );
add( mDeleteButton, "wrap" );
}
@Override
public void save()
{
if( isModified() && hasItem() )
{
ChannelMap channelMap = getChannelMap();
channelMap.setName( mNameText.getText() );
channelMap.setRanges( mRangeModel.getChannelRanges() );
mChannelMapModel.broadcast( new ChannelMapEvent( getChannelMap(), Event.CHANGE ) );
setModified( false );
}
}
@Override
public void valueChanged( ListSelectionEvent e )
{
if( !e.getValueIsAdjusting() )
{
if( e.getSource() instanceof JList )
{
JList<?> list = (JList<?>)e.getSource();
if( list.getSelectedValue() instanceof ChannelMap )
{
setItem( (ChannelMap)list.getSelectedValue() );
}
else
{
setItem( null );
}
}
}
}
@Override
public void channelRangesChanged()
{
setModified( true );
//Update description
int index = mRangeTable.getSelectedRow();
if( index >= 0 )
{
ChannelRange selected = mRangeModel.getChannelRange(
mRangeTable.convertRowIndexToModel( index ) );
if( selected != null )
{
mRangeDescription.setText( selected.getDescription() );
}
else
{
mRangeDescription.setText( "" );
}
}
}
public class RangeTableEditor extends AbstractCellEditor
implements TableCellEditor, ActionListener
{
private static final long serialVersionUID = 1L;
JFormattedTextField mEditor;
public RangeTableEditor()
{
InternationalFormatter nf = new InternationalFormatter();
nf.setMinimum( 1 );
nf.setAllowsInvalid( true );
mEditor = new JFormattedTextField( nf );
}
@Override
public Object getCellEditorValue()
{
return mEditor.getText();
}
@Override
public void actionPerformed( ActionEvent e )
{
}
@Override
public Component getTableCellEditorComponent( JTable table,
Object value,
boolean isSelected,
int row,
int column )
{
mEditor.setValue( value );
return mEditor;
}
}
}

View File

@@ -1,128 +0,0 @@
package io.github.dsheirer.controller.channel.map;
import com.jidesoft.swing.JideSplitPane;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ChannelMapManager extends JPanel
{
private static final long serialVersionUID = 1L;
private ChannelMapModel mChannelMapModel;
private JList<ChannelMap> mChannelMapList;
private ChannelMapEditor mChannelMapEditor;
private JButton mDeleteMapButton;
private JButton mCloneMapButton;
public ChannelMapManager( ChannelMapModel channelMapModel )
{
mChannelMapModel = channelMapModel;
mChannelMapList = new JList<>( mChannelMapModel );
mChannelMapEditor = new ChannelMapEditor( mChannelMapModel );
init();
}
private void init()
{
setLayout( new MigLayout( "insets 1 1 1 1", "[fill,grow]", "[fill,grow]" ) );
JPanel listPanel = new JPanel();
listPanel.setLayout( new MigLayout( "",
"[fill,grow][fill,grow][fill,grow]", "[fill,grow][]" ) );
mChannelMapList.addListSelectionListener( mChannelMapEditor );
mChannelMapList.addListSelectionListener( new ListSelectionListener()
{
@Override
public void valueChanged( ListSelectionEvent e )
{
if( mChannelMapList.getSelectedValue() != null )
{
mCloneMapButton.setEnabled( true );
mDeleteMapButton.setEnabled( true );
}
else
{
mCloneMapButton.setEnabled( false );
mDeleteMapButton.setEnabled( false );
}
}
} );
mChannelMapList.setLayoutOrientation( JList.VERTICAL );
mChannelMapList.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
JScrollPane listScroller = new JScrollPane( mChannelMapList );
listPanel.add( listScroller, "span" );
JButton newMapButton = new JButton( "New" );
newMapButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
ChannelMap channelMap = new ChannelMap( "New Channel Map" );
mChannelMapModel.addChannelMap( channelMap );
mChannelMapList.setSelectedValue( channelMap, true );
}
} );
listPanel.add( newMapButton );
mCloneMapButton = new JButton( "Clone" );
mCloneMapButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
ChannelMap selected = mChannelMapList.getSelectedValue();
if( selected != null )
{
ChannelMap copy = selected.copyOf();
mChannelMapModel.addChannelMap( copy );
mChannelMapList.setSelectedValue( copy, true );
}
}
} );
listPanel.add( mCloneMapButton );
mDeleteMapButton = new JButton( "Delete" );
mDeleteMapButton.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
ChannelMap channelMap = mChannelMapList.getSelectedValue();
if( channelMap != null )
{
int choice = JOptionPane.showConfirmDialog( mChannelMapList,
"Do you want to delete this channel map?",
"Delete Channel Map?", JOptionPane.YES_NO_OPTION );
if( choice == JOptionPane.YES_OPTION )
{
mChannelMapModel.removeChannelMap( channelMap );
}
}
}
} );
listPanel.add( mDeleteMapButton );
JScrollPane editorScroller = new JScrollPane( mChannelMapEditor );
JideSplitPane splitPane = new JideSplitPane();
splitPane.setOrientation( JideSplitPane.HORIZONTAL_SPLIT );
splitPane.add( listPanel );
splitPane.add( editorScroller );
add( splitPane );
}
}

View File

@@ -1,60 +0,0 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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.controller.channel.map;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import java.awt.*;
public class ChannelMapManagerFrame extends JFrame
{
private static final long serialVersionUID = 1L;
public ChannelMapManagerFrame( ChannelMapModel channelMapModel )
{
init( new ChannelMapManager( channelMapModel ) );
}
private void init( ChannelMapManager channelMapManager )
{
setTitle( "Channel Map Manager" );
setSize( 800, 400 );
setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
setLayout( new MigLayout( "", "[grow,fill]", "[grow,fill]" ) );
add( channelMapManager );
setLocationRelativeTo( null );
}
public static void main( String[] args )
{
ChannelMapModel channelMapModel = new ChannelMapModel();
final ChannelMapManagerFrame frame =
new ChannelMapManagerFrame( channelMapModel );
EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
frame.setVisible( true );
}
} );
}
}

View File

@@ -1,14 +1,37 @@
/*
*
* * ******************************************************************************
* * 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.controller.channel.map;
import io.github.dsheirer.controller.channel.map.ChannelMapEvent.Event;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import javax.swing.AbstractListModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ChannelMapModel extends AbstractListModel<ChannelMap>
@@ -16,19 +39,32 @@ public class ChannelMapModel extends AbstractListModel<ChannelMap>
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger(ChannelMapModel.class);
private List<ChannelMap> mChannelMaps = new ArrayList<>();
private ObservableList<ChannelMap> mChannelMaps = FXCollections.observableArrayList(ChannelMap.extractor());
private Broadcaster<ChannelMapEvent> mEventBroadcaster = new Broadcaster<>();
public ChannelMapModel()
{
}
/**
* Removes all channel maps from this model and broadcasts a remove/delete event for each.
*/
public void clear()
{
List<ChannelMap> channelMaps = new ArrayList<>(mChannelMaps);
for(ChannelMap channelMap: channelMaps)
{
removeChannelMap(channelMap);
}
}
/**
* Returns an unmodifiable list of channel maps currently in the model
*/
public List<ChannelMap> getChannelMaps()
public ObservableList<ChannelMap> getChannelMaps()
{
return Collections.unmodifiableList(mChannelMaps);
return mChannelMaps;
}
/**
@@ -112,7 +148,6 @@ public class ChannelMapModel extends AbstractListModel<ChannelMap>
broadcast(new ChannelMapEvent(channelMap, Event.ADD));
}
}
/**

View File

@@ -1,25 +1,36 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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.controller.channel.map;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.util.Callback;
import java.text.DecimalFormat;
@@ -28,28 +39,64 @@ public class ChannelRange
{
private static final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat("#.0000");
private int mFirst = 1;
private int mLast = 1023;
private int mBase = 450000000;
private int mSize = 12500;
private boolean mOverlapping = false;
private IntegerProperty mFirstChannel = new SimpleIntegerProperty(1);
private IntegerProperty mLastChannel = new SimpleIntegerProperty(1023);
private IntegerProperty mBaseFrequency = new SimpleIntegerProperty(450000000);
private IntegerProperty mStepSize = new SimpleIntegerProperty(12500);
private BooleanProperty mValid = new SimpleBooleanProperty();
private BooleanProperty mOverlapping = new SimpleBooleanProperty();
public ChannelRange()
{
//Bind valid to first channel is less than last channel
mValid.bind(Bindings.lessThan(mFirstChannel, mLastChannel));
}
public ChannelRange(int first, int last, int base, int size)
public ChannelRange(int first, int last, int base, int sizeProperty)
{
mFirst = first;
mLast = last;
mBase = base;
mSize = size;
this();
mFirstChannel.set(first);
mLastChannel.set(last);
mBaseFrequency.set(base);
mStepSize.set(sizeProperty);
}
public ChannelRange copyOf()
{
return new ChannelRange(mFirst, mLast, mBase, mSize);
return new ChannelRange(mFirstChannel.get(), mLastChannel.get(), mBaseFrequency.get(), mStepSize.get());
}
/**
* Indicates if this channel range overlaps with another channel range
*/
public BooleanProperty overlappingProperty()
{
return mOverlapping;
}
public BooleanProperty validProperty()
{
return mValid;
}
public IntegerProperty firstChannelProperty()
{
return mFirstChannel;
}
public IntegerProperty lastChannelProperty()
{
return mLastChannel;
}
public IntegerProperty baseFrequencyProperty()
{
return mBaseFrequency;
}
public IntegerProperty stepSizeProperty()
{
return mStepSize;
}
@JsonIgnore
@@ -60,15 +107,15 @@ public class ChannelRange
if(isValid())
{
sb.append("First: ");
sb.append(mFirst).append("=");
sb.append(mFirstChannel.get()).append("=");
long frequency = getFrequency(mFirst);
long frequency = getFrequency(mFirstChannel.get());
sb.append(FREQUENCY_FORMATTER.format((double)frequency / 1E6D));
sb.append(" Last: ");
sb.append(mLast).append("=");
sb.append(mLastChannel.get()).append("=");
frequency = getFrequency(mLast);
frequency = getFrequency(mLastChannel.get());
sb.append(FREQUENCY_FORMATTER.format((double)frequency / 1E6D));
sb.append(" MHz");
}
@@ -93,38 +140,38 @@ public class ChannelRange
@JsonIgnore
public boolean isValid()
{
return mFirst < mLast;
return mValid.get();
}
public boolean overlaps(ChannelRange other)
{
return (mFirst <= other.mFirst && other.mFirst <= mLast) ||
(mFirst <= other.mLast && other.mLast <= mLast) ||
(mFirst <= other.mFirst && other.mLast <= mLast) ||
(other.mFirst <= mFirst && mLast <= other.mLast);
return (mFirstChannel.get() <= other.mFirstChannel.get() && other.mFirstChannel.get() <= mLastChannel.get()) ||
(mFirstChannel.get() <= other.mLastChannel.get() && other.mLastChannel.get() <= mLastChannel.get()) ||
(mFirstChannel.get() <= other.mFirstChannel.get() && other.mLastChannel.get() <= mLastChannel.get()) ||
(other.mFirstChannel.get() <= mFirstChannel.get() && mLastChannel.get() <= other.mLastChannel.get());
}
@JsonIgnore
public boolean isOverlapping()
{
return mOverlapping;
return mOverlapping.get();
}
public void setOverlapping(boolean overlapping)
{
mOverlapping = overlapping;
mOverlapping.set(overlapping);
}
public boolean hasChannel(int channel)
{
return mFirst <= channel && channel <= mLast;
return mFirstChannel.get() <= channel && channel <= mLastChannel.get();
}
public long getFrequency(int channel)
{
if(hasChannel(channel))
{
return mBase + ((channel - mFirst) * mSize);
return mBaseFrequency.get() + ((channel - mFirstChannel.get()) * mStepSize.get());
}
else
{
@@ -135,54 +182,63 @@ public class ChannelRange
@JacksonXmlProperty(isAttribute = true, localName = "first")
public int getFirstChannelNumber()
{
return mFirst;
return mFirstChannel.get();
}
public void setFirstChannelNumber(int first)
{
mFirst = first;
mFirstChannel.set(first);;
}
@JacksonXmlProperty(isAttribute = true, localName = "last")
public void setLastChannelNumber(int last)
{
mLast = last;
mLastChannel.set(last);;
}
public int getLastChannelNumber()
{
return mLast;
return mLastChannel.get();
}
/**
* Sets the base frequency
*
* @param base frequency in hertz
* @param baseFrequency frequency in hertz
*/
@JacksonXmlProperty(isAttribute = true, localName = "base")
public void setBase(int base)
public void setBaseFrequency(int baseFrequency)
{
mBase = base;
this.mBaseFrequency.set(baseFrequency);
}
public int getBase()
public int getBaseFrequency()
{
return mBase;
return mBaseFrequency.get();
}
/**
* Sets the channel size
*
* @param size in hertz
* @param stepSize in hertz
*/
@JacksonXmlProperty(isAttribute = true, localName = "size")
public void setSize(int size)
public void setStepSize(int stepSize)
{
mSize = size;
this.mStepSize.set(stepSize);
}
public int getSize()
public int getStepSize()
{
return mSize;
return mStepSize.get();
}
/**
* Creates an observable property extractor for use with obserable lists to detect changes internal to this object.
*/
public static Callback<ChannelRange,Observable[]> extractor()
{
return (ChannelRange c) -> new Observable[] {c.firstChannelProperty(), c.lastChannelProperty(),
c.baseFrequencyProperty(), c.stepSizeProperty(), c.validProperty(), c.overlappingProperty()};
}
}

View File

@@ -1,20 +1,24 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 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/>
******************************************************************************/
/*
*
* * ******************************************************************************
* * 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.controller.channel.map;
import javax.swing.table.AbstractTableModel;
@@ -176,9 +180,9 @@ public class ChannelRangeModel extends AbstractTableModel
case STOP:
return range.getLastChannelNumber();
case BASE:
return range.getBase();
return range.getBaseFrequency();
case SIZE:
return range.getSize();
return range.getStepSize();
case ERROR:
StringBuilder sb = new StringBuilder();
@@ -266,10 +270,10 @@ public class ChannelRangeModel extends AbstractTableModel
range.setLastChannelNumber( parsedValue );
break;
case BASE:
range.setBase( parsedValue );
range.setBaseFrequency( parsedValue );
break;
case SIZE:
range.setSize( parsedValue );
range.setStepSize( parsedValue );
break;
}