#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

@@ -19,71 +19,53 @@
package io.github.dsheirer.controller;
import com.jidesoft.swing.JideTabbedPane;
import io.github.dsheirer.alias.AliasController;
import io.github.dsheirer.alias.AliasModel;
import io.github.dsheirer.audio.broadcast.BroadcastModel;
import io.github.dsheirer.audio.broadcast.BroadcastPanel;
import io.github.dsheirer.audio.playback.AudioPanel;
import io.github.dsheirer.audio.playback.AudioPlaybackManager;
import io.github.dsheirer.channel.metadata.NowPlayingPanel;
import io.github.dsheirer.controller.channel.ChannelController;
import io.github.dsheirer.controller.channel.ChannelModel;
import io.github.dsheirer.controller.channel.ChannelProcessingManager;
import io.github.dsheirer.controller.channel.map.ChannelMapModel;
import io.github.dsheirer.eventbus.MyEventBus;
import io.github.dsheirer.gui.playlist.ViewPlaylistRequest;
import io.github.dsheirer.icon.IconManager;
import io.github.dsheirer.map.MapPanel;
import io.github.dsheirer.map.MapService;
import io.github.dsheirer.playlist.PlaylistManager;
import io.github.dsheirer.preference.UserPreferences;
import io.github.dsheirer.settings.SettingsManager;
import io.github.dsheirer.source.SourceManager;
import io.github.dsheirer.source.tuner.TunerModel;
import io.github.dsheirer.source.tuner.TunerViewPanel;
import jiconfont.icons.font_awesome.FontAwesome;
import jiconfont.swing.IconFontSwing;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
public class ControllerPanel extends JPanel
{
private final static Logger mLog = LoggerFactory.getLogger(ControllerPanel.class);
private static final long serialVersionUID = 1L;
private int mSettingsTabIndex = -1;
private AliasController mAliasController;
private AudioPanel mAudioPanel;
private BroadcastPanel mBroadcastPanel;
private ChannelController mChannelController;
private NowPlayingPanel mNowPlayingPanel;
private ChannelModel mChannelModel;
private MapPanel mMapPanel;
private TunerViewPanel mTunerManagerPanel;
private BroadcastModel mBroadcastModel;
private JideTabbedPane mTabbedPane;
public ControllerPanel(AudioPlaybackManager audioPlaybackManager, AliasModel aliasModel, BroadcastModel broadcastModel,
ChannelModel channelModel, ChannelMapModel channelMapModel, ChannelProcessingManager channelProcessingManager,
public ControllerPanel(PlaylistManager playlistManager, AudioPlaybackManager audioPlaybackManager,
IconManager iconManager, MapService mapService, SettingsManager settingsManager,
SourceManager sourceManager, TunerModel tunerModel, UserPreferences userPreferences)
SourceManager sourceManager, UserPreferences userPreferences)
{
mBroadcastModel = broadcastModel;
mChannelModel = channelModel;
mAudioPanel = new AudioPanel(iconManager, userPreferences, settingsManager, sourceManager, audioPlaybackManager,
aliasModel);
mNowPlayingPanel = new NowPlayingPanel(channelModel, channelProcessingManager, iconManager,
aliasModel, userPreferences);
mMapPanel = new MapPanel(mapService, aliasModel, iconManager, settingsManager);
mBroadcastPanel = new BroadcastPanel(broadcastModel, aliasModel, iconManager, userPreferences);
mChannelController = new ChannelController(channelModel, channelProcessingManager, channelMapModel,
sourceManager, aliasModel, userPreferences);
mAliasController = new AliasController(aliasModel, broadcastModel, iconManager, userPreferences);
mTunerManagerPanel = new TunerViewPanel(tunerModel, userPreferences);
playlistManager.getAliasModel());
mNowPlayingPanel = new NowPlayingPanel(playlistManager, iconManager, userPreferences);
mMapPanel = new MapPanel(mapService, playlistManager.getAliasModel(), iconManager, settingsManager);
mTunerManagerPanel = new TunerViewPanel(sourceManager.getTunerModel(), userPreferences);
init();
}
@@ -94,20 +76,34 @@ public class ControllerPanel extends JPanel
add(mAudioPanel, "wrap");
//Tabbed View - configuration, calls, messages, map
mTabbedPane = new JideTabbedPane();
mTabbedPane = new JideTabbedPane()
{
@Override
public void setSelectedIndex(int index)
{
if(index == mSettingsTabIndex)
{
MyEventBus.getEventBus().post(new ViewPlaylistRequest());
}
else
{
super.setSelectedIndex(index);
}
}
};
mTabbedPane.setFont(this.getFont());
mTabbedPane.setForeground(Color.BLACK);
mTabbedPane.addTab("Now Playing", mNowPlayingPanel);
mTabbedPane.addTab("Aliases", mAliasController);
mTabbedPane.addTab("Channels", mChannelController);
mTabbedPane.addTab("Map", mMapPanel);
mTabbedPane.addTab("Streaming", mBroadcastPanel);
mTabbedPane.addTab("Tuners", mTunerManagerPanel);
Icon gearIcon = IconFontSwing.buildIcon(FontAwesome.COG, 20, Color.DARK_GRAY);
mTabbedPane.addTab("Playlist Editor", gearIcon, new JLabel("Show Playlist Manager"));
mSettingsTabIndex = mTabbedPane.getTabCount() - 1;
//Set preferred size to influence the split between these panels
mTabbedPane.setPreferredSize(new Dimension(880, 500));
add(mTabbedPane,"wrap");
add(mTabbedPane, "wrap");
}
}

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;
}