328 Channel Auto-Start capability. Adds auto-start and auto-start

ordering to each channel configuration to provide the user with greater
control over initial application startup configuration.

Adds new dialog window that displays on startup that allows the user to
invoke/cancel auto-start, or simply allow the timer to expire whereby
the channels will be auto-started.

Updates channel table model to include an auto-start column and relabels
the 'Enabled' column to read 'Playing'.

Updates channel editor to change the 'Enable/Disable' button to read
'Start/Stop'.

Changes the 'enabled' property of the channel configuration to indicate
the current 'processing' status - isProcessing().

Channel's XML configuration re-purposes the 'enabled' property to indicate
auto-start and adds a new (optional) auto-start 'order' attribute.
This commit is contained in:
Denny
2018-02-10 08:09:43 -05:00
parent 594a77a9d0
commit 030fc1617c
14 changed files with 926 additions and 272 deletions

View File

@@ -0,0 +1,286 @@
/*******************************************************************************
* sdr-trunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* 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.module.decode.p25.DecodeConfigP25Phase1;
import io.github.dsheirer.util.ThreadPool;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class ChannelAutoStartFrame extends JFrame
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelAutoStartFrame.class);
private static final int COUNTDOWN_SECONDS = 10;
private ChannelEventListener mChannelEventListener;
private List<Channel> mChannels;
private JLabel mCountdownLabel;
private JButton mStartButton;
private JButton mCancelButton;
private JTable mChannelTable;
private AtomicBoolean mChannelsStarted = new AtomicBoolean();
private ScheduledFuture<?> mTimerFuture;
/**
* Creates and displays a channel auto-start gui for presenting the user with a list of channels that
* will be automatically started once the countdown timer reaches zero, or the user chooses to start
* now or cancel.
*
* @param listener to receive channel start/enable request(s)
* @param channels to auto-start
*/
public ChannelAutoStartFrame(ChannelEventListener listener, List<Channel> channels)
{
mChannelEventListener = listener;
mChannels = channels;
init();
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
setVisible(true);
startTimer();
}
});
}
private void init()
{
setTitle("Auto-Start Channels");
setSize(new Dimension(400,300));
setLocationRelativeTo(null);
addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent event)
{
mLog.info("Channel auto-start canceled by user - window closed");
stopTimer();
super.windowClosing(event);
}
});
JPanel panel = new JPanel();
panel.setLayout(new MigLayout("","[grow,fill][grow,fill]",
"[][][grow,fill][]"));
panel.add(new JLabel("The following channels will be automatically"), "span");
mCountdownLabel = new JLabel(getCountdownText(COUNTDOWN_SECONDS));
panel.add(mCountdownLabel, "span");
panel.add(new JScrollPane(getChannelTable()), "span");
mStartButton = new JButton("Start Now");
mStartButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
mLog.info("Starting [" + mChannels.size() + "] channels now - user invoked");
startChannels();
stopTimer();
}
});
panel.add(mStartButton);
mCancelButton = new JButton("Cancel");
mCancelButton.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e)
{
mLog.info("Channel auto-start canceled by user");
stopTimer();
}
});
panel.add(mCancelButton);
setContentPane(panel);
}
private String getCountdownText(int value)
{
return "started in: " + value + " seconds.";
}
/**
* Updates the countdown text label on the swing event thread
* @param value for the countdown
*/
private void updateCountdownText(final int value)
{
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
mCountdownLabel.setText(getCountdownText(value));
}
});
}
/**
* Creates (once) a table of auto-start channels
*/
private JTable getChannelTable()
{
if(mChannelTable == null)
{
AutoStartChannelModel model = new AutoStartChannelModel(mChannels);
mChannelTable = new JTable(model);
}
return mChannelTable;
}
/**
* Sends an enable (ie start) request for each of the auto start channels.
*
* This method is thread-safe and will only be executed once.
*
* Although the redundant channel-start request would be ignored, this once-only will prevent the chance
* that both the timer and the user would attempt to start the channels at the same time.
*/
private void startChannels()
{
if(mChannelsStarted.compareAndSet(false, true))
{
if(mChannelEventListener != null)
{
for(Channel channel: mChannels)
{
mChannelEventListener.channelChanged(new ChannelEvent(channel, ChannelEvent.Event.REQUEST_ENABLE));
}
}
}
}
/**
* Starts the countdown timer to fire once a second.
*/
private void startTimer()
{
if(mTimerFuture == null)
{
mTimerFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new CountdownTimer(), 0, 1, TimeUnit.SECONDS);
}
}
/**
* Stops the countdown timer and disposes this frame
*/
private void stopTimer()
{
if(mTimerFuture != null)
{
mTimerFuture.cancel(true);
mTimerFuture = null;
}
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
dispose();
}
});
}
/**
* Updates the countdown text and auto-starts the channels once the timer falls below zero.
*/
public class CountdownTimer implements Runnable
{
private int mCount = COUNTDOWN_SECONDS;
@Override
public void run()
{
if(mCount >= 0)
{
updateCountdownText(mCount);
mCount--;
}
else
{
mLog.info("Starting [" + mChannels.size() + "] now - timer invoked");
startChannels();
stopTimer();
}
}
}
public static void main(String[] args)
{
List<Channel> channels = new ArrayList<>();
Channel channel1 = new Channel();
channel1.setSystem("A System");
channel1.setSite("My Site");
channel1.setName("Channel 1 Name");
channel1.setDecodeConfiguration(new DecodeConfigP25Phase1());
channels.add(channel1);
Channel channel2 = new Channel();
channel2.setSystem("B System");
channel2.setSite("My Site2");
channel2.setName("Channel 2 Name");
channel2.setDecodeConfiguration(new DecodeConfigP25Phase1());
channel2.setAutoStartOrder(2);
channels.add(channel2);
Channel channel3 = new Channel();
channel3.setSystem("C System");
channel3.setSite("My Site3");
channel3.setName("Channel 3 Name");
channel3.setDecodeConfiguration(new DecodeConfigP25Phase1());
channel3.setAutoStartOrder(1);
channels.add(channel3);
ChannelEventListener listener = new ChannelEventListener()
{
@Override
public void channelChanged(ChannelEvent event)
{
mLog.debug("Channel Event: " + event);
}
};
ChannelAutoStartFrame frame = new ChannelAutoStartFrame(listener, channels);
}
}