/******************************************************************************* * 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 * ******************************************************************************/ package io.github.dsheirer.controller.channel; 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.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 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 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(); } } } }