();
+
+ public ControllerTreeCellRenderer()
+ {
+ }
+
+ @Override
+ public Component getTreeCellRendererComponent( JTree tree,
+ Object value, boolean selected, boolean expanded, boolean isLeaf,
+ int row, boolean focused )
+ {
+ Component c = super.getTreeCellRendererComponent( tree, value, selected,
+ expanded, isLeaf, row, focused );
+
+ if( value instanceof BaseNode )
+ {
+ ImageIcon icon = getIcon( (BaseNode)value );
+
+ if( icon != null )
+ {
+ if( icon.getIconHeight() > 16 )
+ {
+
+ }
+ else
+ {
+ setIcon( icon );
+ }
+ }
+ }
+
+ return c;
+ }
+
+ private ImageIcon getIcon( BaseNode node )
+ {
+ ImageIcon icon = null;
+
+ String filepath = node.getIconPath();
+
+ if( filepath != null )
+ {
+ if( mIcons.containsKey( filepath ) )
+ {
+ icon = mIcons.get( filepath );
+ }
+ else
+ {
+ icon = new ImageIcon( filepath );
+
+ if( icon.getIconHeight() > MAX_ICON_HEIGHT )
+ {
+ Image image = icon.getImage();
+
+ double scale = (double)icon.getIconHeight() / (double)MAX_ICON_HEIGHT;
+
+ int scaledWidth = (int)( (double)icon.getIconWidth() / scale );
+
+ Image scaledImage = image.getScaledInstance( scaledWidth,
+ MAX_ICON_HEIGHT, java.awt.Image.SCALE_SMOOTH );
+
+ mIcons.put( filepath, new ImageIcon( scaledImage ) );
+ }
+ else
+ {
+ mIcons.put( filepath, icon );
+ }
+
+ }
+ }
+
+ return icon;
+ }
+}
diff --git a/src/controller/Editor.java b/src/controller/Editor.java
new file mode 100644
index 00000000..b938eb87
--- /dev/null
+++ b/src/controller/Editor.java
@@ -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
+ ******************************************************************************/
+package controller;
+
+import javax.swing.JPanel;
+
+import net.miginfocom.swing.MigLayout;
+
+public abstract class Editor extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+
+ public Editor()
+ {
+ setLayout( new MigLayout() );
+ }
+
+ public abstract void save();
+
+ public abstract void reset();
+}
diff --git a/src/controller/EmptyEditor.java b/src/controller/EmptyEditor.java
new file mode 100644
index 00000000..83f21de3
--- /dev/null
+++ b/src/controller/EmptyEditor.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+public class EmptyEditor extends JPanel
+{
+ private static final long serialVersionUID = 1L;
+
+ public EmptyEditor()
+ {
+ setLayout( new BorderLayout() );
+ setPreferredSize( new Dimension( 200, (int)getPreferredSize().getHeight() ) );
+
+ String text = ""
+ + "SDR Trunk
"
+ + "
"
+ + "Use the tree structure to the left to configure SDRTrunk"
+ + " to use multiple SDR tuners and sound card sources to"
+ + " decode multiple trunked and digital radio channels.
"
+ + "
"
+ + "Each tree branch can be accessed by clicking on the branch"
+ + " and a configuration editor will appear here.
"
+ + "
"
+ + "Many of the branches contain context sensitive menus that "
+ + " you can access by right-clicking on the node.
"
+ + "
"
+ + "To begin, right-click on the 'Systems' branch under 'Playlist'"
+ + "and create a new 'System'. Right-click the 'System' node to create "
+ + "a new 'Site'. Right-click on the 'Site' node to create a new Channel.
"
+ + "
"
+ + "In the new channel, select a channel source and decoder "
+ + "and select event logging and recording options. Check 'Enabled'"
+ + " and hit the 'Save' button to begin decoding.
"
+ + "";
+
+ add( new JLabel( text ), BorderLayout.NORTH );
+ }
+}
diff --git a/src/controller/ResourceManager.java b/src/controller/ResourceManager.java
new file mode 100644
index 00000000..3075dddb
--- /dev/null
+++ b/src/controller/ResourceManager.java
@@ -0,0 +1,159 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller;
+
+import map.MapService;
+import playlist.PlaylistManager;
+import record.RecorderManager;
+import settings.SettingsManager;
+import source.SourceManager;
+import source.tuner.TunerManager;
+import controller.channel.ChannelManager;
+import eventlog.EventLogManager;
+
+/**
+ * Manager for system wide resources
+ */
+public class ResourceManager
+{
+ private ChannelManager mChannelManager;
+ private EventLogManager mEventLogManager;
+ private PlaylistManager mPlaylistManager;
+ private RecorderManager mRecorderManager;
+ private SettingsManager mSettingsManager;
+ private SourceManager mSourceManager;
+ private TunerManager mTunerManager;
+ private ConfigurationControllerModel mController;
+ private MapService mMapService;
+ private ThreadPoolManager mThreadPoolManager;
+
+ public ResourceManager()
+ {
+ this( new SettingsManager(),
+ new PlaylistManager(),
+ new EventLogManager(),
+ new RecorderManager() );
+ }
+
+ public ResourceManager( SettingsManager settingsManager,
+ PlaylistManager playlistManager,
+ EventLogManager eventLogManager,
+ RecorderManager recorderManager )
+ {
+ mSettingsManager = settingsManager;
+ mPlaylistManager = playlistManager;
+ mEventLogManager = eventLogManager;
+ mRecorderManager = recorderManager;
+
+ /**
+ * ChannelManager requires a reference to the ResourceManager so that
+ * channel processing chains can access system wide resources
+ */
+ mChannelManager = new ChannelManager( this );
+ mSourceManager = new SourceManager( this );
+ mTunerManager = new TunerManager( this );
+ mController = new ConfigurationControllerModel( this );
+ mMapService = new MapService( this );
+ mThreadPoolManager = new ThreadPoolManager();
+ }
+
+ public ConfigurationControllerModel getController()
+ {
+ return mController;
+ }
+
+ public ThreadPoolManager getThreadPoolManager()
+ {
+ return mThreadPoolManager;
+ }
+
+ public void setController( ConfigurationControllerModel controller )
+ {
+ mController = controller;
+ }
+
+ public TunerManager getTunerManager()
+ {
+ return mTunerManager;
+ }
+
+ public MapService getMapService()
+ {
+ return mMapService;
+ }
+
+ public SettingsManager getSettingsManager()
+ {
+ return mSettingsManager;
+ }
+
+ public void setSettingsManager( SettingsManager manager )
+ {
+ mSettingsManager = manager;
+ }
+
+ public PlaylistManager getPlaylistManager()
+ {
+ return mPlaylistManager;
+ }
+
+ public void setPlaylistManager( PlaylistManager aliasManager )
+ {
+ mPlaylistManager = aliasManager;
+ }
+
+ public EventLogManager getEventLogManager()
+ {
+ return mEventLogManager;
+ }
+
+ public void setEventLogManager( EventLogManager eventLogManager )
+ {
+ mEventLogManager = eventLogManager;
+ }
+
+ public RecorderManager getRecorderManager()
+ {
+ return mRecorderManager;
+ }
+
+ public void setRecorderManager( RecorderManager recorderManager )
+ {
+ mRecorderManager = recorderManager;
+ }
+
+ public SourceManager getSourceManager()
+ {
+ return mSourceManager;
+ }
+
+ public void setSourceManager( SourceManager sourceManager )
+ {
+ mSourceManager = sourceManager;
+ }
+
+ public ChannelManager getChannelManager()
+ {
+ return mChannelManager;
+ }
+
+ public void setChannelManager( ChannelManager channelManager )
+ {
+ mChannelManager = channelManager;
+ }
+}
diff --git a/src/controller/ThreadPoolManager.java b/src/controller/ThreadPoolManager.java
new file mode 100644
index 00000000..158a5b19
--- /dev/null
+++ b/src/controller/ThreadPoolManager.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller;
+
+import java.util.HashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+public class ThreadPoolManager
+{
+ private int THREAD_POOL_SIZE = 10;
+ private int MAX_TASK_COUNT = 24;
+
+ private HashMap,ThreadType> mTasks =
+ new HashMap,ThreadType>();
+
+ private ScheduledExecutorService mExecutor;
+
+ public ThreadPoolManager()
+ {
+ }
+
+ public ScheduledFuture> schedule( ThreadType type,
+ Runnable command,
+ long period,
+ TimeUnit unit )
+ throws RejectedExecutionException
+ {
+ if( mExecutor == null )
+ {
+ mExecutor = Executors.newScheduledThreadPool( THREAD_POOL_SIZE );
+ }
+
+ if( mTasks.size() <= MAX_TASK_COUNT )
+ {
+ ScheduledFuture> task =
+ mExecutor.scheduleAtFixedRate( command, 0, period, unit );
+
+ mTasks.put( task, type );
+
+ return task;
+ }
+ else
+ {
+ throw new RejectedExecutionException( "Cannot schedule task - at "
+ + "max task count [" + mTasks.size() + "]" );
+ }
+
+ }
+
+ public void cancel( ScheduledFuture> task )
+ {
+ task.cancel( true );
+
+ ThreadType type = mTasks.remove( task );
+ }
+
+ public int getTaskCount( ThreadType type )
+ {
+ int count = 0;
+
+ for( ThreadType current: mTasks.values() )
+ {
+ if( current == type )
+ {
+ count++;
+ }
+ }
+
+ return count;
+ }
+
+ public enum ThreadType
+ {
+ SOURCE_SAMPLE_PROCESSING,
+ DECIMATION,
+ DECODER;
+ }
+}
diff --git a/src/controller/activity/ActivitySummaryFrame.java b/src/controller/activity/ActivitySummaryFrame.java
new file mode 100644
index 00000000..c4d7bbaf
--- /dev/null
+++ b/src/controller/activity/ActivitySummaryFrame.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.activity;
+
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+import net.miginfocom.swing.MigLayout;
+
+public class ActivitySummaryFrame extends JFrame
+{
+ private static final long serialVersionUID = 1L;
+
+ public ActivitySummaryFrame( String summary )
+ {
+ this( summary, null );
+ }
+
+ public ActivitySummaryFrame( String summary, Component displayOver )
+ {
+ setTitle( "Activity Summary" );
+ setLocationRelativeTo( displayOver );
+ setSize( 400, 400 );
+ setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
+ setLayout( new MigLayout( "", "[grow,fill]", "[grow,fill][]" ) );
+
+ JTextArea summaryText = new JTextArea( summary );
+
+ JScrollPane scroller = new JScrollPane( summaryText );
+ scroller.setViewportView( summaryText );
+
+ add( scroller, "wrap" );
+
+ JButton close = new JButton( "Close" );
+ close.addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ dispose();
+ }
+ } );
+
+ add( close );
+
+ setVisible( true );
+ }
+}
diff --git a/src/controller/activity/ActivitySummaryProvider.java b/src/controller/activity/ActivitySummaryProvider.java
new file mode 100644
index 00000000..91e84253
--- /dev/null
+++ b/src/controller/activity/ActivitySummaryProvider.java
@@ -0,0 +1,26 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.activity;
+
+import message.Message;
+import sample.Listener;
+
+public interface ActivitySummaryProvider extends Listener
+{
+ public String getSummary();
+}
diff --git a/src/controller/activity/CallEvent.java b/src/controller/activity/CallEvent.java
new file mode 100644
index 00000000..9bbc7bcf
--- /dev/null
+++ b/src/controller/activity/CallEvent.java
@@ -0,0 +1,226 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.activity;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import alias.Alias;
+import alias.AliasList;
+import decode.DecoderType;
+
+public abstract class CallEvent
+{
+ protected long mEventTime = System.currentTimeMillis();
+
+ private SimpleDateFormat mSDF = new SimpleDateFormat( "yyyyMMdd','HHmmss" );
+
+ protected DecoderType mDecoderType;
+ protected CallEventType mCallEventType;
+ protected AliasList mAliasList;
+ protected String mFromID;
+ protected String mToID;
+ protected String mDetails;
+
+ public CallEvent( DecoderType decoder,
+ CallEventType callEventType,
+ AliasList aliasList,
+ String fromID,
+ String toID,
+ String details )
+ {
+ mDecoderType = decoder;
+ mCallEventType = callEventType;
+ mAliasList = aliasList;
+ mFromID = fromID;
+ mToID = toID;
+ mDetails = details;
+ }
+
+ public DecoderType getDecoderType()
+ {
+ return mDecoderType;
+ }
+
+ public CallEventType getCallEventType()
+ {
+ return mCallEventType;
+ }
+
+ public AliasList getAliasList()
+ {
+ return mAliasList;
+ }
+
+ public void setAliasList( AliasList aliasList )
+ {
+ mAliasList = aliasList;
+ }
+
+ public boolean hasAliasList()
+ {
+ return mAliasList != null;
+ }
+
+ public long getEventTime()
+ {
+ return mEventTime;
+ }
+
+ public String getFromID()
+ {
+ return mFromID;
+ }
+
+ public void setFromID( String id )
+ {
+ mFromID = id;
+ }
+
+ public String getToID()
+ {
+ return mToID;
+ }
+
+ public void setToID( String id )
+ {
+ mToID = id;
+ }
+
+ public String getDetails()
+ {
+ return mDetails;
+ }
+
+ public void setDetails( String details )
+ {
+ mDetails = details;
+ }
+
+ public abstract Alias getFromIDAlias();
+
+ public abstract Alias getToIDAlias();
+
+ public abstract int getChannel();
+
+ public abstract int getFrequency();
+
+ public static String getCSVHeader()
+ {
+ return "DATE,TIME,DECODER,EVENT,FROM,FROM_ALIAS,TO,TO_ALIAS,"
+ + "CHANNEL,FREQUENCY,DETAILS";
+ }
+
+ public String toCSV()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append( "'" );
+ sb.append( mSDF.format( new Date( getEventTime() ) ) );
+ sb.append( "','" );
+ sb.append( getDecoderType().toString() );
+ sb.append( "','" );
+ sb.append( getCallEventType().toString() );
+ sb.append( "'," );
+ if( getFromID() != null )
+ {
+ sb.append( "'" );
+ sb.append( getFromID() );
+ sb.append( "'" );
+ }
+ sb.append( "," );
+
+ Alias fromAlias = getFromIDAlias();
+
+ if( fromAlias != null )
+ {
+ sb.append( "'" );
+ sb.append( fromAlias.getName() );
+ sb.append( "'" );
+ }
+ sb.append( "," );
+
+ if( getToID() != null )
+ {
+ sb.append( "'" );
+ sb.append( getToID() );
+ sb.append( "'" );
+ }
+ sb.append( "," );
+
+ Alias toAlias = getToIDAlias();
+
+ if( toAlias != null )
+ {
+ sb.append( "'" );
+ sb.append( toAlias.getName() );
+ sb.append( "'" );
+ }
+ sb.append( ",'" );
+ sb.append( getChannel() );
+ sb.append( "','" );
+ sb.append( getFrequency() );
+ sb.append( "'," );
+ if( getDetails() != null )
+ {
+ sb.append( "'" );
+ sb.append( getDetails() );
+ sb.append( "'" );
+ }
+
+ return sb.toString();
+ }
+
+ public enum CallEventType
+ {
+ ACKNOWLEDGE( "Acknowledge" ),
+ CALL_DETECT( "Call Detect" ),
+ CALL_START( "Call Start" ),
+ CALL_START_UNIQUE_ID( "Call Start" ),
+ CALL_START_NO_TUNER( "Call - No Tuner" ),
+ CALL_END( "Call End" ),
+ CALL_TIMEOUT( "Call Timeout" ),
+ EMERGENCY( "EMERGENCY" ),
+ GPS( "GPS" ),
+ ID_ANI( "ANI" ),
+ ID_UNIQUE( "Unique ID" ),
+ PAGE( "Page" ),
+ REGISTER( "Register" ),
+ REGISTER_ESN( "ESN" ),
+ SDM( "SDM" ),
+ STATUS( "Status" ),
+ UNKNOWN( "Unknown" );
+
+ private String mLabel;
+
+ private CallEventType( String label )
+ {
+ mLabel = label;
+ }
+
+ public String getLabel()
+ {
+ return mLabel;
+ }
+
+ public String toString()
+ {
+ return mLabel;
+ }
+ }
+}
diff --git a/src/controller/activity/CallEventAliasCellRenderer.java b/src/controller/activity/CallEventAliasCellRenderer.java
new file mode 100644
index 00000000..88c35bd4
--- /dev/null
+++ b/src/controller/activity/CallEventAliasCellRenderer.java
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.activity;
+
+import javax.swing.ImageIcon;
+import javax.swing.table.DefaultTableCellRenderer;
+
+import settings.SettingsManager;
+import alias.Alias;
+
+/**
+ * Custom renderer for JTable cells that contain Alias objects. Renders the
+ * alias name, and if there is an icon name, attempts to get that icon from
+ * the settings manager and render it.
+ */
+public class CallEventAliasCellRenderer extends DefaultTableCellRenderer
+{
+ private SettingsManager mSettingsManager;
+
+ public CallEventAliasCellRenderer( SettingsManager settingsManager )
+ {
+ super();
+ mSettingsManager = settingsManager;
+ }
+
+ public void setValue( Object obj )
+ {
+ if( obj != null && obj instanceof Alias )
+ {
+ Alias alias = (Alias)obj;
+
+ setText( alias.getName() );
+
+ ImageIcon icon = getIcon( alias, 12 );
+
+ if( icon != null )
+ {
+ setIcon( icon );
+ }
+ }
+ else
+ {
+ setText( " " );
+ setIcon( null );
+ }
+ }
+
+ private ImageIcon getIcon( Alias alias, int height )
+ {
+ if( mSettingsManager != null && alias != null )
+ {
+ return mSettingsManager.getImageIcon( alias.getIconName(), height );
+ }
+
+ return null;
+ }
+}
diff --git a/src/controller/activity/CallEventModel.java b/src/controller/activity/CallEventModel.java
new file mode 100644
index 00000000..0618e2a4
--- /dev/null
+++ b/src/controller/activity/CallEventModel.java
@@ -0,0 +1,176 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.activity;
+
+import java.awt.EventQueue;
+import java.text.SimpleDateFormat;
+import java.util.LinkedList;
+
+import javax.swing.table.AbstractTableModel;
+
+import log.Log;
+import sample.Broadcaster;
+import sample.Listener;
+
+public class CallEventModel extends AbstractTableModel
+{
+ private static final long serialVersionUID = 1L;
+
+ private static final int DEFAULT_ICON_HEIGHT = 14;
+
+ private static final int sTIME = 0;
+ private static final int sEVENT = 1;
+ private static final int sFROM_ID = 2;
+ private static final int sFROM_ALIAS = 3;
+ private static final int sTO_ID = 4;
+ private static final int sTO_ALIAS =5;
+ private static final int sCHANNEL = 6;
+ private static final int sFREQUENCY = 7;
+ private static final int sDETAILS = 8;
+
+ protected int mMaxMessages = 500;
+
+ protected LinkedList mEvents = new LinkedList();
+
+ protected String[] mHeaders = new String[] { "Time",
+ "Event",
+ "From",
+ "From Alias",
+ "To",
+ "To Alias",
+ "Channel",
+ "Frequency",
+ "Details" };
+
+ private SimpleDateFormat mSDFTime = new SimpleDateFormat( "HH:mm:ss" );
+
+ private Broadcaster mBroadcaster = new Broadcaster();
+ private boolean mNewMessagesFirst = true;
+
+ public CallEventModel()
+ {
+ }
+
+ public void dispose()
+ {
+ mEvents.clear();
+ mBroadcaster.dispose();
+ }
+
+ public void flush()
+ {
+ /* Flush all events to listeners ( normally the call event logger ) */
+ for( CallEvent event: mEvents )
+ {
+ mBroadcaster.receive( event );
+ }
+ }
+
+ public int getMaxMessageCount()
+ {
+ return mMaxMessages;
+ }
+
+ public void setMaxMessageCount( int count )
+ {
+ mMaxMessages = count;
+ }
+
+ public void add( final CallEvent event )
+ {
+ EventQueue.invokeLater( new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ mEvents.addFirst( event );
+
+ CallEventModel.this.fireTableRowsInserted( 0, 0 );
+
+ prune();
+ }
+ } );
+ }
+
+ private void prune()
+ {
+ while( mEvents.size() > mMaxMessages )
+ {
+ CallEvent event = mEvents.removeLast();
+
+ mBroadcaster.receive( event );
+
+ super.fireTableRowsDeleted( mEvents.size(), mEvents.size() );
+ }
+ }
+
+ @Override
+ public int getRowCount()
+ {
+ return mEvents.size();
+ }
+
+ @Override
+ public int getColumnCount()
+ {
+ return mHeaders.length;
+ }
+
+ public String getColumnName( int column )
+ {
+ return mHeaders[ column ];
+ }
+
+ @Override
+ public Object getValueAt( int rowIndex, int columnIndex )
+ {
+ switch( columnIndex )
+ {
+ case sTIME:
+ return mSDFTime.format( mEvents.get( rowIndex ).getEventTime() );
+ case sEVENT:
+ return mEvents.get( rowIndex ).getCallEventType();
+ case sFROM_ID:
+ return mEvents.get( rowIndex ).getFromID();
+ case sFROM_ALIAS:
+ return mEvents.get( rowIndex ).getFromIDAlias();
+ case sTO_ID:
+ return mEvents.get( rowIndex ).getToID();
+ case sTO_ALIAS:
+ return mEvents.get( rowIndex ).getToIDAlias();
+ case sCHANNEL:
+ return mEvents.get( rowIndex ).getChannel();
+ case sFREQUENCY:
+ return mEvents.get( rowIndex ).getFrequency();
+ case sDETAILS:
+ return mEvents.get( rowIndex ).getDetails();
+ }
+
+ return null;
+ }
+
+ public void addListener( Listener listener )
+ {
+ mBroadcaster.addListener( listener );
+ }
+
+ public void removeListener( Listener listener )
+ {
+ mBroadcaster.removeListener( listener );
+ }
+}
diff --git a/src/controller/activity/CallEventPanel.java b/src/controller/activity/CallEventPanel.java
new file mode 100644
index 00000000..62846d98
--- /dev/null
+++ b/src/controller/activity/CallEventPanel.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.activity;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+
+import net.miginfocom.swing.MigLayout;
+import controller.channel.Channel;
+import controller.channel.Channel.ChannelEvent;
+import controller.channel.ChannelListener;
+
+public class CallEventPanel extends JPanel implements ChannelListener
+{
+ private static final long serialVersionUID = 1L;
+
+ private JScrollPane mEmptyScroller;
+ private Channel mDisplayedChannel;
+
+ public CallEventPanel()
+ {
+ setLayout( new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]") );
+
+ JTable table = new JTable( new CallEventModel() );
+ table.setAutoCreateRowSorter( true );
+ table.setAutoResizeMode( JTable.AUTO_RESIZE_LAST_COLUMN );
+ mEmptyScroller = new JScrollPane( table );
+
+ add( mEmptyScroller );
+ }
+
+ @Override
+ public void occurred( Channel channel, ChannelEvent event )
+ {
+ if( event == ChannelEvent.CHANGE_SELECTED && channel.getSelected() )
+ {
+ if( mDisplayedChannel == null ||
+ ( mDisplayedChannel != null && mDisplayedChannel != channel ) )
+ {
+ removeAll();
+
+ JScrollPane scroller = new JScrollPane(
+ channel.getProcessingChain().getChannelState()
+ .getCallEventTable() );
+
+ add( scroller );
+
+ mDisplayedChannel = channel;
+
+ revalidate();
+ repaint();
+ }
+ }
+ else if( event == ChannelEvent.PROCESSING_STOPPED ||
+ event == ChannelEvent.CHANNEL_DELETED )
+ {
+ if( mDisplayedChannel != null && mDisplayedChannel == channel )
+ {
+ mDisplayedChannel = null;
+ removeAll();
+ add( mEmptyScroller );
+
+ revalidate();
+ repaint();
+ }
+ }
+ }
+}
diff --git a/src/controller/activity/MessageActivityModel.java b/src/controller/activity/MessageActivityModel.java
new file mode 100644
index 00000000..cfb5ea54
--- /dev/null
+++ b/src/controller/activity/MessageActivityModel.java
@@ -0,0 +1,155 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.activity;
+
+import java.awt.EventQueue;
+import java.text.SimpleDateFormat;
+import java.util.LinkedList;
+
+import javax.swing.table.AbstractTableModel;
+
+import message.Message;
+import sample.Listener;
+
+public class MessageActivityModel extends AbstractTableModel
+ implements Listener
+{
+ private static final long serialVersionUID = 1L;
+ private static final int sTIME = 0;
+ private static final int sPROTOCOL = 1;
+ private static final int sERROR_STATUS = 2;
+ private static final int sMESSAGE = 3;
+ private static final int sMESSAGE_BITS = 4;
+
+ protected int mMaxMessages = 500;
+ protected LinkedList mMessages = new LinkedList();
+
+ protected int[] mColumnWidths = { 110, 110, 110, -1, -1 };
+
+ protected String[] mHeaders = new String[] { "Time",
+ "Protocol",
+ "Error Check",
+ "Message",
+ "Binary" };
+
+ private SimpleDateFormat mSDFTime = new SimpleDateFormat( "HH:mm:ss" );
+
+ private boolean mNewMessagesFirst = true;
+
+ public MessageActivityModel()
+ {
+ }
+
+ public void dispose()
+ {
+ mMessages.clear();
+ }
+
+ public int[] getColumnWidths()
+ {
+ return mColumnWidths;
+ }
+
+ public void setColumnWidths( int[] widths )
+ {
+ if( widths.length != 5 )
+ {
+ throw new IllegalArgumentException( "MessageActivityModel - "
+ + "column widths array should have 5 elements" );
+ }
+ else
+ {
+ mColumnWidths = widths;
+ }
+ }
+
+ public int getMaxMessageCount()
+ {
+ return mMaxMessages;
+ }
+
+ public void setMaxMessageCount( int count )
+ {
+ mMaxMessages = count;
+ }
+
+ public void receive( final Message message )
+ {
+ EventQueue.invokeLater( new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ mMessages.addFirst( message );
+
+ MessageActivityModel.this.fireTableRowsInserted( 0, 0 );
+
+ prune();
+ }
+ } );
+ }
+
+ private void prune()
+ {
+ while( mMessages.size() > mMaxMessages )
+ {
+ mMessages.removeLast();
+
+ super.fireTableRowsDeleted( mMessages.size() - 1, mMessages.size() - 1 );
+ }
+ }
+
+ @Override
+ public int getRowCount()
+ {
+ return mMessages.size();
+ }
+
+ @Override
+ public int getColumnCount()
+ {
+ return mHeaders.length;
+ }
+
+ public String getColumnName( int column )
+ {
+ return mHeaders[ column ];
+ }
+
+ @Override
+ public Object getValueAt( int rowIndex, int columnIndex )
+ {
+ Message message = mMessages.get( rowIndex );
+
+ switch( columnIndex )
+ {
+ case sTIME:
+ return mSDFTime.format( message.getDateReceived() );
+ case sPROTOCOL:
+ return message.getProtocol();
+ case sERROR_STATUS:
+ return message.getErrorStatus();
+ case sMESSAGE:
+ return message.getMessage();
+ case sMESSAGE_BITS:
+ return message.getBinaryMessage();
+ }
+
+ return null;
+ }
+}
diff --git a/src/controller/activity/MessageActivityPanel.java b/src/controller/activity/MessageActivityPanel.java
new file mode 100644
index 00000000..5783a19d
--- /dev/null
+++ b/src/controller/activity/MessageActivityPanel.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.activity;
+
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+
+import net.miginfocom.swing.MigLayout;
+import controller.channel.Channel;
+import controller.channel.Channel.ChannelEvent;
+import controller.channel.ChannelListener;
+
+public class MessageActivityPanel extends JPanel implements ChannelListener
+{
+ private static final long serialVersionUID = 1L;
+
+ private JScrollPane mEmptyScrollPane;
+ private Channel mDisplayedChannel;
+
+ public MessageActivityPanel()
+ {
+ setLayout( new MigLayout("insets 0 0 0 0 ", "[grow,fill]", "[grow,fill]") );
+
+ mEmptyScrollPane =
+ new JScrollPane( new JTable( new MessageActivityModel() ) );
+
+ add( mEmptyScrollPane );
+ }
+
+ @Override
+ public void occurred( Channel channel, ChannelEvent event )
+ {
+ if( event == ChannelEvent.CHANGE_SELECTED && channel.getSelected() )
+ {
+ if( mDisplayedChannel == null ||
+ ( mDisplayedChannel != null && mDisplayedChannel != channel ) )
+ {
+ removeAll();
+
+ JScrollPane scroll =
+ new JScrollPane( channel.getProcessingChain()
+ .getChannelState().getMessageActivityTable() );
+
+ add( scroll );
+
+ mDisplayedChannel = channel;
+
+ revalidate();
+ repaint();
+ }
+ }
+ else if( event == ChannelEvent.PROCESSING_STOPPED ||
+ event == ChannelEvent.CHANNEL_DELETED )
+ {
+ if( mDisplayedChannel != null && mDisplayedChannel == channel )
+ {
+ mDisplayedChannel = null;
+ removeAll();
+ add( mEmptyScrollPane );
+
+ revalidate();
+ repaint();
+ }
+ }
+ }
+}
diff --git a/src/controller/activity/MessageDetailsProvider.java b/src/controller/activity/MessageDetailsProvider.java
new file mode 100644
index 00000000..97672505
--- /dev/null
+++ b/src/controller/activity/MessageDetailsProvider.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.activity;
+
+import alias.Alias;
+
+/**
+ * Getter methods to support the Message Activity Table/Model
+ * @author denny
+ *
+ */
+public interface MessageDetailsProvider
+{
+ /**
+ * Indicates if the message is valid and has passed crc/integrity checks
+ */
+ public abstract boolean isValid();
+
+ /**
+ * Status of the CRC check of the message
+ */
+ public abstract String getErrorStatus();
+
+ /**
+ * Parsed Message
+ * @return
+ */
+ public abstract String getMessage();
+
+ /**
+ * Raw ( 0 & 1 ) message bits
+ */
+ public abstract String getBinaryMessage();
+
+
+ /**
+ * Decoded protocol
+ */
+ public abstract String getProtocol();
+
+ /**
+ * Event - call, data, idle, etc.
+ */
+ public abstract String getEventType();
+
+ /**
+ * Formatted from identifier
+ */
+ public abstract String getFromID();
+
+ /**
+ * From identifier alias (from AliasManager)
+ */
+ public abstract Alias getFromIDAlias();
+
+ /**
+ * Formatted to identifier
+ */
+ public abstract String getToID();
+
+ /**
+ * To identifier alias (from AliasManager)
+ * @return
+ */
+ public abstract Alias getToIDAlias();
+
+}
diff --git a/src/controller/channel/AbstractChannelEditor.java b/src/controller/channel/AbstractChannelEditor.java
new file mode 100644
index 00000000..41c3b4aa
--- /dev/null
+++ b/src/controller/channel/AbstractChannelEditor.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+import controller.Editor;
+
+public abstract class AbstractChannelEditor extends Editor
+{
+ private static final long serialVersionUID = 1L;
+
+ protected ChannelNode mChannelNode;
+
+ public AbstractChannelEditor( ChannelNode channel )
+ {
+ super();
+ mChannelNode = channel;
+ }
+
+ public ChannelNode getChannelNode()
+ {
+ return mChannelNode;
+ }
+
+ public abstract void save();
+
+ public abstract void reset();
+}
diff --git a/src/controller/channel/Channel.java b/src/controller/channel/Channel.java
new file mode 100644
index 00000000..ee964ea9
--- /dev/null
+++ b/src/controller/channel/Channel.java
@@ -0,0 +1,902 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JSeparator;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlTransient;
+
+import log.Log;
+import message.Message;
+import record.config.RecordConfiguration;
+import sample.Listener;
+import source.SourceType;
+import source.config.SourceConfigFactory;
+import source.config.SourceConfigTuner;
+import source.config.SourceConfiguration;
+import source.tuner.TunerChannel;
+import source.tuner.TunerChannel.Type;
+import controller.ResourceManager;
+import controller.activity.ActivitySummaryFrame;
+import controller.config.Configuration;
+import controller.site.Site;
+import controller.state.ChannelState;
+import controller.state.ChannelState.State;
+import controller.system.System;
+import decode.config.AuxDecodeConfiguration;
+import decode.config.DecodeConfigFactory;
+import decode.config.DecodeConfiguration;
+import eventlog.config.EventLogConfiguration;
+
+@XmlSeeAlso( { Configuration.class } )
+@XmlRootElement( name = "channel" )
+public class Channel extends Configuration
+{
+ public enum ChannelType
+ {
+ STANDARD,
+ TRAFFIC
+ };
+
+ private static final boolean ENABLED = true;
+ private static final boolean DISABLED = false;
+ private static final boolean BROADCAST_CHANGE = true;
+
+ private CopyOnWriteArrayList mChannelListeners =
+ new CopyOnWriteArrayList();
+
+ private CopyOnWriteArrayList> mMessageListeners =
+ new CopyOnWriteArrayList>();
+
+ private HashMap mTrafficChannels =
+ new HashMap();
+
+ private DecodeConfiguration mDecodeConfiguration =
+ DecodeConfigFactory.getDefaultDecodeConfiguration();
+ private AuxDecodeConfiguration mAuxDecodeConfiguration =
+ new AuxDecodeConfiguration();
+ private SourceConfiguration mSourceConfiguration =
+ SourceConfigFactory.getDefaultSourceConfiguration();
+ private EventLogConfiguration mEventLogConfiguration =
+ new EventLogConfiguration();
+ private RecordConfiguration mRecordConfiguration = new RecordConfiguration();
+
+ private ResourceManager mResourceManager;
+ private String mAliasListName;
+ private System mSystem;
+ private Site mSite;
+ private String mName;
+ private boolean mEnabled;
+ private boolean mSelected;
+ private ChannelType mChannelType;
+ private ProcessingChain mProcessingChain;
+
+ /**
+ * Constructs a new standard channel with a default name of "New Channel"
+ */
+ public Channel()
+ {
+ this( "New Channel", ChannelType.STANDARD );
+ }
+
+ /**
+ * Constructs a new standard channel with the specified channel name
+ */
+ public Channel( String channelName )
+ {
+ this( channelName, ChannelType.STANDARD );
+ }
+
+ /**
+ * Constructs a new channel with the specified channel name and type.
+ *
+ * @param channelName
+ * @param channelType
+ */
+ public Channel( String channelName, ChannelType channelType )
+ {
+ mName = channelName;
+ mChannelType = channelType;
+ }
+
+ public ChannelType getChannelType()
+ {
+ return mChannelType;
+ }
+
+ /**
+ * Sets the resource manager responsible for this channel and registers
+ * the channel manager as a listener to this channel. Fires channel add
+ * channel event to the newly added channel manager channel listener.
+ */
+ public void setResourceManager( ResourceManager resourceManager )
+ {
+ mResourceManager = resourceManager;
+
+ /* Add system-wide channel listeners onto this channel */
+ addListeners( mResourceManager.getChannelManager().getChannelListeners() );
+
+ /* If we're enabled, fire enable changed event to get processing started */
+ if( mEnabled )
+ {
+ enableChanged();
+ }
+ }
+
+ @XmlTransient
+ public ResourceManager getResourceManager()
+ {
+ return mResourceManager;
+ }
+
+ /**
+ * Returns the processing chain for this channel. If the processing chain
+ * is not currently constructed and running, this method will return null.
+ */
+ @XmlTransient
+ public ProcessingChain getProcessingChain()
+ {
+ return mProcessingChain;
+ }
+
+ /**
+ * Indicates if this channel has a processing chain constructed and it
+ * is processing.
+ */
+ public boolean isProcessing()
+ {
+ return mProcessingChain != null && mProcessingChain.isProcessing();
+ }
+
+ /**
+ * Indicates if this channel has been selected for audio output
+ */
+ @XmlTransient
+ public boolean getSelected()
+ {
+ return mSelected;
+ }
+
+ public void setSelected( boolean selected )
+ {
+ mSelected = selected;
+
+ if( mProcessingChain != null && mProcessingChain.isProcessing() )
+ {
+ mProcessingChain.getAudioOutput().setAudioPlaybackEnabled( selected );
+ }
+
+ fireChannelEvent( ChannelEvent.CHANGE_SELECTED );
+ }
+
+ public JMenu getContextMenu()
+ {
+ JMenu menu = new JMenu( mSite + "-" + mName );
+
+ if( mEnabled )
+ {
+ JMenuItem disable = new JMenuItem( "Disable" );
+ disable.addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ setEnabled( DISABLED, BROADCAST_CHANGE );
+ getResourceManager().getPlaylistManager().save();
+ }
+ } );
+
+ menu.add( disable );
+
+ menu.add( new JSeparator() );
+
+ JMenuItem actySummaryItem =
+ new JMenuItem( "Activity Summary" );
+
+ actySummaryItem.addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ if( mResourceManager != null )
+ {
+ ProcessingChain chain = getProcessingChain();
+
+ if( chain != null )
+ {
+ ChannelState state = chain.getChannelState();
+
+ if( state != null )
+ {
+ String summary = state.getActivitySummary();
+
+ new ActivitySummaryFrame( summary,
+ mResourceManager.getController().getTree() );
+ }
+ }
+ }
+ }
+ } );
+
+ menu.add( actySummaryItem );
+ }
+ else
+ {
+ JMenuItem enable = new JMenuItem( "Enable" );
+ enable.addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ setEnabled( ENABLED, BROADCAST_CHANGE );
+ getResourceManager().getPlaylistManager().save(); }
+ } );
+
+ menu.add( enable );
+ }
+
+ menu.add( new JSeparator() );
+
+ JMenuItem deleteItem = new JMenuItem( "Delete" );
+ deleteItem.addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ dispose();
+ getResourceManager().getPlaylistManager().save(); }
+ } );
+
+ menu.add( deleteItem );
+
+
+ return menu;
+ }
+
+ /**
+ * Orderly shutdown method when this channel is going to be deleted.
+ */
+ public void dispose()
+ {
+ setEnabled( DISABLED, BROADCAST_CHANGE );
+
+ /* Broadcast channel deleted event */
+ fireChannelEvent( ChannelEvent.CHANNEL_DELETED );
+ }
+
+ /**
+ * Returns a full channel name containing the system, site and channel name.
+ */
+ public String getChannelDisplayName()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ if( mSystem != null )
+ {
+ sb.append( mSystem.getName() );
+ }
+ else
+ {
+ sb.append( "-" );
+ }
+
+ sb.append( "/" );
+
+ if( mSite != null )
+ {
+ sb.append( mSite.getName() );
+ }
+ else
+ {
+ sb.append( "-" );
+ }
+
+ sb.append( "/" );
+
+ sb.append( mName );
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the tuner channel requirement for this channel when the source
+ * configuration specifies a tuner and frequency as the source for this
+ * channel.
+ *
+ * If the source configuration is anything else, this method returns null.
+ */
+ public TunerChannel getTunerChannel()
+ {
+ TunerChannel retVal = null;
+
+ if( mSourceConfiguration.getSourceType() == SourceType.TUNER )
+ {
+ SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration;
+
+ retVal = new TunerChannel( Type.LOCKED, config.getFrequency(),
+ mDecodeConfiguration.getDecoderType().getChannelBandwidth() );
+ }
+
+ return retVal;
+ }
+
+ /**
+ * Returns the owning system for this channel.
+ */
+ public System getSystem()
+ {
+ return mSystem;
+ }
+
+ /**
+ * Sets the owning system for this channel and optionally broadcasts a
+ * system change channel event.
+ */
+ public void setSystem( System system, boolean fireChangeEvent )
+ {
+ mSystem = system;
+
+ if( fireChangeEvent )
+ {
+ fireChannelEvent( ChannelEvent.CHANGE_SYSTEM );
+ }
+ }
+
+ /**
+ * Returns the owning site for this channel.
+ */
+ public Site getSite()
+ {
+ return mSite;
+ }
+
+ /**
+ * Sets the owning site for this channel and optionally broadcasts a site
+ * change channel event.
+ */
+ public void setSite( Site site, boolean fireChangeEvent )
+ {
+ mSite = site;
+
+ if( fireChangeEvent )
+ {
+ fireChannelEvent( ChannelEvent.CHANGE_SITE );
+ }
+ }
+
+ /**
+ * Default display string for this channel -- the channel name.
+ */
+ public String toString()
+ {
+ return mName;
+ }
+
+ /**
+ * Indicates if this channel is enabled for processing.
+ */
+ @XmlAttribute
+ public boolean getEnabled()
+ {
+ return mEnabled;
+ }
+
+ /**
+ * Enables/disables this channel for processing. Automatically starts or
+ * stops channel processing in response to this change.
+ */
+ public void setEnabled( boolean enabled )
+ {
+ mEnabled = enabled;
+
+ enableChanged();
+ }
+
+ /**
+ * Enables/disables this channel for processing. Automatically starts or
+ * stops channel processing in response to this change. Optionally
+ * broadcasts a enabled change channel event.
+ */
+ public void setEnabled( boolean enabled, boolean fireChannelEvent )
+ {
+ setEnabled( enabled );
+
+ if( fireChannelEvent )
+ {
+ fireChannelEvent( ChannelEvent.CHANGE_ENABLED );
+ }
+ }
+
+ /**
+ * Returns the alias list that is used by this channel for looking up alias
+ * values for the various identifiers produced by the decoder.
+ */
+ @XmlElement( name = "alias_list_name" )
+ public String getAliasListName()
+ {
+ return mAliasListName;
+ }
+
+ /**
+ * Sets the alias list to be used for looking up the alias values for the
+ * various identifiers produced by the decoder
+ */
+ public void setAliasListName( String name )
+ {
+ mAliasListName = name;
+ }
+
+ /**
+ * Sets the alias list to be used for looking up the alias values for the
+ * various identifiers produced by the decoder. This method is the same
+ * as setAliasName(name) and optionally broadcasts an alias list change
+ * channel event.
+ */
+ public void setAliasListName( String name, boolean fireChangeEvent )
+ {
+ mAliasListName = name;
+
+ if( fireChangeEvent )
+ {
+ fireChannelEvent( ChannelEvent.CHANGE_ALIAS_LIST );
+ }
+ }
+
+ /**
+ * Gets the decoder configuration used by this channel
+ */
+ @XmlElement( name = "decode_configuration" )
+ public DecodeConfiguration getDecodeConfiguration()
+ {
+ return mDecodeConfiguration;
+ }
+
+ /**
+ * Sets the decoder configuration used by this channel
+ */
+ public void setDecodeConfiguration( DecodeConfiguration config )
+ {
+ mDecodeConfiguration = config;
+
+ if( mProcessingChain != null && mProcessingChain.isRunning() )
+ {
+ mProcessingChain.updateDecoder();
+ }
+ }
+
+ /**
+ * Gets the aux decoder configuration used by this channel
+ */
+ @XmlElement( name = "aux_decode_configuration" )
+ public AuxDecodeConfiguration getAuxDecodeConfiguration()
+ {
+ return mAuxDecodeConfiguration;
+ }
+
+ /**
+ * Sets the decoder configuration used by this channel
+ */
+ public void setAuxDecodeConfiguration( AuxDecodeConfiguration config )
+ {
+ mAuxDecodeConfiguration = config;
+
+ if( mProcessingChain != null && mProcessingChain.isRunning() )
+ {
+ mProcessingChain.updateDecoder();
+ }
+ }
+
+ /**
+ * Sets the decoder configuration used by this channel and optionally
+ * broadcasts a decoder change channel event.
+ */
+ public void setDecodeConfiguration( DecodeConfiguration config,
+ boolean fireChangeEvent )
+ {
+ setDecodeConfiguration( config );
+
+ if( fireChangeEvent )
+ {
+ fireChannelEvent( ChannelEvent.CHANGE_DECODER );
+ }
+ }
+
+ /**
+ * Returns the source configuration for this channel.
+ */
+ @XmlElement( name = "source_configuration" )
+ public SourceConfiguration getSourceConfiguration()
+ {
+ return mSourceConfiguration;
+ }
+
+ /**
+ * Sets the source configuration for this channel.
+ */
+ public void setSourceConfiguration( SourceConfiguration config )
+ {
+ mSourceConfiguration = config;
+
+ if( mProcessingChain != null && mProcessingChain.isRunning() )
+ {
+ mProcessingChain.updateSource();
+ }
+ }
+
+ /**
+ * Sets the source configuration for this channel and optionally broadcasts
+ * a source change channel event.
+ */
+ public void setSourceConfiguration( SourceConfiguration config,
+ boolean fireChangeEvent )
+ {
+ setSourceConfiguration( config );
+
+ if( fireChangeEvent )
+ {
+ fireChannelEvent( ChannelEvent.CHANGE_SOURCE );
+ }
+ }
+
+ /**
+ * Returns the event logger configuration for this channel.
+ */
+ @XmlElement( name = "event_log_configuration" )
+ public EventLogConfiguration getEventLogConfiguration()
+ {
+ return mEventLogConfiguration;
+ }
+
+ /**
+ * Sets the event logger configuration for this channel.
+ */
+ public void setEventLogConfiguration( EventLogConfiguration config )
+ {
+ mEventLogConfiguration = config;
+
+ if( mProcessingChain != null && mProcessingChain.isRunning() )
+ {
+ mProcessingChain.updateEventLogging();
+ }
+ }
+
+ /**
+ * Sets the event logger configuration for this channel and optionally
+ * broadcasts an event logger change channel event.
+ */
+ public void setEventLogConfiguration( EventLogConfiguration config,
+ boolean fireChangeEvent )
+ {
+ setEventLogConfiguration( config );
+
+ if( fireChangeEvent )
+ {
+ fireChannelEvent( ChannelEvent.CHANGE_EVENT_LOGGER );
+ }
+ }
+
+ /**
+ * Returns the recorder configuration for this channel.
+ */
+ @XmlElement( name = "record_configuration" )
+ public RecordConfiguration getRecordConfiguration()
+ {
+ return mRecordConfiguration;
+ }
+
+ /**
+ * Sets the recorder configuration for this channel.
+ */
+ public void setRecordConfiguration( RecordConfiguration config )
+ {
+ mRecordConfiguration = config;
+
+ if( mProcessingChain != null && mProcessingChain.isRunning() )
+ {
+ mProcessingChain.updateRecording();
+ }
+ }
+
+ /**
+ * Sets the recorder configuration for this channel and optionally broadcasts
+ * a recorder configuration change channel event.
+ */
+ public void setRecordConfiguration( RecordConfiguration config,
+ boolean fireChangeEvent )
+ {
+ setRecordConfiguration( config );
+
+ if( fireChangeEvent )
+ {
+ fireChannelEvent( ChannelEvent.CHANGE_RECORDER );
+ }
+ }
+
+ /**
+ * Returns the name of this channel.
+ */
+ @XmlAttribute
+ public String getName()
+ {
+ return mName;
+ }
+
+ /**
+ * Sets the name of this channel.
+ */
+ public void setName( String name )
+ {
+ mName = name;
+ }
+
+ /**
+ * Sets the name of this channel and optionally broadcasts a name change
+ * channel event.
+ */
+ public void setName( String name, boolean fireChangeEvent )
+ {
+ mName = name;
+
+ if( fireChangeEvent )
+ {
+ fireChannelEvent( ChannelEvent.CHANGE_NAME );
+ }
+ }
+
+ /**
+ * Responds to changes in the enabled setting by starting the processing
+ * change if the channel is now enabled, or stopping the procesing chain
+ * if it was already started.
+ */
+ private void enableChanged()
+ {
+ if( mEnabled )
+ {
+ start();
+ }
+ else
+ {
+ stop();
+ }
+ }
+
+ /**
+ * Starts the channel processing chain.
+ */
+ private void start()
+ {
+ if( mEnabled && mProcessingChain == null && mResourceManager != null )
+ {
+ mProcessingChain = new ProcessingChain( this, mResourceManager );
+
+ /* Add system-wide message listeners */
+ mProcessingChain.addListeners(
+ mResourceManager.getChannelManager().getMessageListeners() );
+
+ /* Add individual message listeners */
+ mProcessingChain.addListeners( mMessageListeners );
+
+ mProcessingChain.start();
+
+ fireChannelEvent( ChannelEvent.PROCESSING_STARTED );
+ }
+
+ /* If this is a traffic channel, override the call fade timeout before
+ * we set the state to call, so that it will auto-expire after the
+ * fade timeout and be auto deleted */
+ if( mChannelType == ChannelType.TRAFFIC )
+ {
+ if( mProcessingChain.isProcessing() )
+ {
+ mProcessingChain.getChannelState().setCallFadeTimeout( 40000 );
+ mProcessingChain.getChannelState().setState( State.CALL );
+ }
+ else
+ {
+ mProcessingChain.getChannelState().setCallFadeTimeout( 5000 );
+ /* Channel state should already be no_tuner */
+ }
+
+ }
+ }
+
+ /**
+ * Stops the channel processing chain
+ */
+ private void stop()
+ {
+ /* Stop and remove any traffic channels */
+ for( Channel traffic: mTrafficChannels.values() )
+ {
+ traffic.stop();
+ traffic.dispose();
+ }
+
+ mTrafficChannels.clear();
+
+ if( !mEnabled && mProcessingChain != null )
+ {
+ mProcessingChain.stop();
+
+ mProcessingChain.dispose();
+
+ mProcessingChain = null;
+
+ mSelected = false;
+ }
+
+ fireChannelEvent( ChannelEvent.PROCESSING_STOPPED );
+ }
+
+ /**
+ * Broadcasts a channel change event to all registered listeners
+ */
+ public void fireChannelEvent( ChannelEvent event )
+ {
+ for( ChannelListener listener: mChannelListeners )
+ {
+ listener.occurred( this, event );
+ }
+ }
+
+ /**
+ * Adds a channel listener to receive all channel events from this channel
+ * and automatically sends the listener a channel add event.
+ */
+ public void addListener( ChannelListener listener )
+ {
+ if( !mChannelListeners.contains( listener ) )
+ {
+ mChannelListeners.add( listener );
+
+ listener.occurred( this, ChannelEvent.CHANNEL_ADDED );
+ }
+ else
+ {
+ Log.error( "Channel - attempt to add already existing channel listener [" + listener.getClass() + "]" );
+ }
+ }
+
+ /**
+ * Adds a list of channel listeners to receive all channel events from this
+ * channel and automatically sends each one a channel add event.
+ */
+ public void addListeners( List listeners )
+ {
+ for( ChannelListener listener: listeners )
+ {
+ addListener( listener );
+ }
+ }
+
+ /**
+ * Removes a channel listener from receiving channel events from this channel
+ */
+ public void removeListener( ChannelListener listener )
+ {
+ mChannelListeners.remove( listener );
+ }
+
+ public void addListener( Listener listener )
+ {
+ mMessageListeners.add( listener );
+
+ if( isProcessing() )
+ {
+ mProcessingChain.addListener( listener );
+ }
+ }
+
+ public void removeListener( Listener listener )
+ {
+ mMessageListeners.remove( listener );
+
+ if( isProcessing() )
+ {
+ mProcessingChain.removeListener( listener );
+ }
+ }
+
+ /**
+ * Indicates if any part of this channel is contained within the
+ * minimum and maximum frequency values.
+ */
+ public boolean isWithin( int minimum, int maximum )
+ {
+ return getTunerChannel() != null &&
+ getTunerChannel().isWithin( minimum, maximum );
+ }
+
+ public void addTrafficChannel( int channelNumber, Channel channel )
+ {
+ mTrafficChannels.put( channelNumber, channel );
+ }
+
+ public boolean hasTrafficChannel( Channel channel )
+ {
+ return mTrafficChannels.containsValue( channel );
+ }
+
+ public void removeTrafficChannel( Channel channel )
+ {
+ int keyToRemove = -1;
+
+ if( mTrafficChannels.values().contains( channel ) )
+ {
+ for( Integer channelNumber: mTrafficChannels.keySet() )
+ {
+ if( mTrafficChannels.get( channelNumber ).equals( channel ) )
+ {
+ keyToRemove = channelNumber;
+ }
+ }
+ }
+
+ if( keyToRemove != -1 )
+ {
+ mTrafficChannels.remove( keyToRemove );
+ }
+ }
+
+ public Collection getTrafficChannels()
+ {
+ return Collections.unmodifiableCollection( mTrafficChannels.values() );
+ }
+
+ public boolean hasTrafficChannel( int channelNumber )
+ {
+ return mTrafficChannels.containsKey( channelNumber );
+ }
+
+ /**
+ * Channel Events - used to specify channel events and changes to channel
+ * configurations and settings.
+ */
+ public enum ChannelEvent
+ {
+ CHANGE_ALIAS_LIST,
+ CHANGE_DECODER,
+ CHANGE_ENABLED,
+ CHANGE_EVENT_LOGGER,
+ CHANGE_NAME,
+ CHANGE_SITE,
+ CHANGE_RECORDER,
+ CHANGE_SELECTED,
+ CHANGE_SOURCE,
+ CHANGE_SYSTEM,
+
+ CHANNEL_ADDED,
+ CHANNEL_DELETED,
+
+ PROCESSING_STARTED,
+ PROCESSING_STOPPED,
+ }
+}
diff --git a/src/controller/channel/ChannelEditor.java b/src/controller/channel/ChannelEditor.java
new file mode 100644
index 00000000..ef64415a
--- /dev/null
+++ b/src/controller/channel/ChannelEditor.java
@@ -0,0 +1,235 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+
+import java.awt.Color;
+import java.awt.ComponentOrientation;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JTextField;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+
+import net.miginfocom.swing.MigLayout;
+import record.RecordComponentEditor;
+import source.SourceComponentEditor;
+import alias.AliasList;
+
+import com.jidesoft.swing.JideTabbedPane;
+
+import controller.Editor;
+import decode.AuxDecodeComponentEditor;
+import decode.DecodeComponentEditor;
+import eventlog.EventLogComponentEditor;
+
+public class ChannelEditor extends Editor implements ActionListener
+{
+ private static final long serialVersionUID = 1L;
+ private DefaultTreeModel mTreeModel;
+ private ChannelNode mChannelNode;
+
+ private DecodeComponentEditor mDecodeEditor;
+ private AuxDecodeComponentEditor mAuxDecodeEditor;
+ private EventLogComponentEditor mEventLogEditor;
+ private RecordComponentEditor mRecordEditor;
+ private SourceComponentEditor mSourceEditor;
+
+ private JTextField mChannelName;
+ private JCheckBox mChannelEnabled = new JCheckBox();
+ private JComboBox mComboAliasLists;
+
+ public ChannelEditor( ChannelNode channel )
+ {
+ super();
+
+ mChannelNode = channel;
+
+ initGUI();
+ }
+
+ private void initGUI()
+ {
+ setLayout( new MigLayout( "fill,wrap 2", "[right,grow][grow]", "[][][][][grow][]" ) );
+
+ add( new JLabel( "Channel Configuration" ), "span, align center" );
+
+ add( new JLabel( "Enabled:" ) );
+
+ mChannelEnabled.setSelected(
+ mChannelNode.getChannel().getEnabled() );
+ mChannelEnabled.setComponentOrientation( ComponentOrientation.RIGHT_TO_LEFT );
+ add( mChannelEnabled );
+
+ add( new JLabel( "Name:" ) );
+
+ mChannelName = new JTextField( mChannelNode.getChannel().getName() );
+ add( mChannelName, "growx,push" );
+
+ /**
+ * ComboBox: Alias Lists
+ */
+ add( new JLabel( "Aliases:" ) );
+
+ mComboAliasLists = new JComboBox();
+
+ List lists = mChannelNode.getModel().getResourceManager()
+ .getPlaylistManager().getPlayist().getAliasDirectory().getAliasList();
+
+ Collections.sort( lists );
+
+ mComboAliasLists.setModel( new DefaultComboBoxModel(
+ lists.toArray( new AliasList[ lists.size() ] ) ) );
+
+ String aliasListName =
+ mChannelNode.getChannel().getAliasListName();
+
+ if( aliasListName != null )
+ {
+ AliasList selected = mChannelNode.getModel().getResourceManager()
+ .getPlaylistManager().getPlayist().getAliasDirectory()
+ .getAliasList( aliasListName );
+
+ mComboAliasLists.setSelectedItem( selected );
+ }
+
+ add( mComboAliasLists, "growx,push" );
+
+ JideTabbedPane tabs = new JideTabbedPane();
+ tabs.setFont( this.getFont() );
+ tabs.setForeground( Color.BLACK );
+
+ mSourceEditor = new SourceComponentEditor( mChannelNode );
+ tabs.addTab( "Source", mSourceEditor );
+
+ mDecodeEditor = new DecodeComponentEditor( mChannelNode );
+ tabs.addTab( "Decoder", mDecodeEditor );
+
+ mAuxDecodeEditor = new AuxDecodeComponentEditor( mChannelNode );
+ tabs.addTab( "Aux", mAuxDecodeEditor );
+
+ mEventLogEditor = new EventLogComponentEditor( mChannelNode );
+ tabs.addTab( "Event Log", mEventLogEditor );
+
+ mRecordEditor = new RecordComponentEditor( mChannelNode );
+ tabs.addTab( "Record", mRecordEditor );
+
+ add( tabs, "span,grow,push" );
+
+ JButton btnSave = new JButton( "Save" );
+ btnSave.addActionListener( ChannelEditor.this );
+ add( btnSave, "growx,push" );
+
+ JButton btnReset = new JButton( "Reset" );
+ btnReset.addActionListener( ChannelEditor.this );
+ add( btnReset, "growx,push" );
+ }
+
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ String command = e.getActionCommand();
+
+ if( command.contentEquals( "Save" ) )
+ {
+ save();
+ }
+ else if( command.contentEquals( "Reset" ) )
+ {
+ reset();
+ }
+
+ if( mTreeModel != null )
+ {
+ mTreeModel.nodeChanged( mChannelNode );
+ }
+ }
+
+ @Override
+ public void save()
+ {
+ boolean expanded = mChannelNode.getModel().getTree()
+ .isExpanded( new TreePath( mChannelNode ) );
+
+ mSourceEditor.save();
+ mDecodeEditor.save();
+ mAuxDecodeEditor.save();
+ mEventLogEditor.save();
+ mRecordEditor.save();
+
+ mChannelNode.getChannel().setName( mChannelName.getText(), false );
+ mChannelNode.getChannel().setEnabled( mChannelEnabled.isSelected(), false );
+
+ AliasList selected = mComboAliasLists.getItemAt(
+ mComboAliasLists.getSelectedIndex() );
+ if( selected != null )
+ {
+ mChannelNode.getChannel().setAliasListName( selected.getName(), false );
+ }
+
+
+ mChannelNode.save();
+ mChannelNode.show();
+
+ if( expanded )
+ {
+ mChannelNode.getModel().getTree().expandPath( new TreePath( mChannelNode ) );
+ }
+ }
+
+ @Override
+ public void reset()
+ {
+ mChannelName.setText(
+ mChannelNode.getChannel().getName() );
+ mChannelName.requestFocus();
+ mChannelName.requestFocusInWindow();
+
+ mChannelEnabled.setSelected(
+ mChannelNode.getChannel().getEnabled() );
+ mChannelEnabled.requestFocus();
+ mChannelEnabled.requestFocusInWindow();
+
+ String aliasListName =
+ mChannelNode.getChannel().getAliasListName();
+
+ if( aliasListName != null )
+ {
+ AliasList selected = mChannelNode.getModel().getResourceManager()
+ .getPlaylistManager().getPlayist().getAliasDirectory()
+ .getAliasList( aliasListName );
+
+ mComboAliasLists.setSelectedItem( selected );
+ }
+
+
+ mSourceEditor.reset();
+ mDecodeEditor.reset();
+ mAuxDecodeEditor.reset();
+ mEventLogEditor.reset();
+ mRecordEditor.reset();
+ }
+}
diff --git a/src/controller/channel/ChannelListener.java b/src/controller/channel/ChannelListener.java
new file mode 100644
index 00000000..c42324bd
--- /dev/null
+++ b/src/controller/channel/ChannelListener.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+import controller.channel.Channel.ChannelEvent;
+
+/**
+ * Interface for receiving channel events. Each listener will receive
+ * notification of all channel events.
+ */
+public interface ChannelListener
+{
+ /**
+ * Notifies the listener that a channel has changed and what has changed
+ */
+ public void occurred( Channel channel, ChannelEvent event );
+}
diff --git a/src/controller/channel/ChannelManager.java b/src/controller/channel/ChannelManager.java
new file mode 100644
index 00000000..2ca807fc
--- /dev/null
+++ b/src/controller/channel/ChannelManager.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+import java.util.ArrayList;
+
+import message.Message;
+import sample.Listener;
+import controller.ResourceManager;
+import controller.channel.Channel.ChannelEvent;
+
+public class ChannelManager implements ChannelListener
+{
+ private ArrayList mChannels = new ArrayList();
+
+ private ArrayList mChannelListeners =
+ new ArrayList();
+ private ArrayList> mMessageListeners =
+ new ArrayList>();
+
+ public ChannelManager( ResourceManager resourceManager )
+ {
+ mChannelListeners.add( this );
+ }
+
+ /**
+ * Notification that a channel has been added or deleted, so that we can
+ * keep track of all channels and manage system-wide listeners for each
+ * of those channels
+ */
+ @SuppressWarnings( "incomplete-switch" )
+ @Override
+ public void occurred( Channel channel, ChannelEvent component )
+ {
+ switch( component )
+ {
+ case CHANNEL_ADDED:
+ if( !mChannels.contains( channel ) )
+ {
+ mChannels.add( channel );
+ }
+ break;
+ case CHANNEL_DELETED:
+ mChannels.remove( channel );
+ break;
+ }
+ }
+
+ /**
+ * Adds a channel listener that will be added to all channels to receive
+ * any channel change events. This listener will automatically receive
+ * a channel add event as it is added to each of the existing channels.
+ */
+ public void addListener( ChannelListener listener )
+ {
+ mChannelListeners.add( listener );
+
+ for( Channel channel: mChannels )
+ {
+ channel.addListener( listener );
+ }
+ }
+
+ /**
+ * Removes a channel listener from being automatically added to all channels
+ * to receive channel change events.
+ */
+ public void removeListener( ChannelListener listener )
+ {
+ mChannelListeners.remove( listener );
+
+ for( Channel channel: mChannels )
+ {
+ channel.removeListener( listener );
+ }
+ }
+
+ /**
+ * Returns the list of channel change listeners that will be automatically
+ * added to all channels to receive channel change events
+ */
+ public ArrayList getChannelListeners()
+ {
+ return mChannelListeners;
+ }
+
+ /**
+ * Returns the list of channel change listeners that will be automatically
+ * added to all channels to receive channel change events
+ */
+ public ArrayList> getMessageListeners()
+ {
+ return mMessageListeners;
+ }
+
+ /**
+ * Adds a message listener that will be added to all channels to receive
+ * any messages.
+ */
+ public void addListener( Listener listener )
+ {
+ mMessageListeners.add( listener );
+ }
+
+ /**
+ * Removes a message listener.
+ */
+ public void removeListener( Listener listener )
+ {
+ mMessageListeners.remove( listener );
+ }
+}
diff --git a/src/controller/channel/ChannelMap.java b/src/controller/channel/ChannelMap.java
new file mode 100644
index 00000000..92bb2ce7
--- /dev/null
+++ b/src/controller/channel/ChannelMap.java
@@ -0,0 +1,120 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+import java.util.ArrayList;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+
+@XmlSeeAlso( { ChannelRange.class } )
+public class ChannelMap
+{
+ private String mName;
+
+ private ArrayList mRanges = new ArrayList();
+
+ public ChannelMap()
+ {
+ this( "New Channel Map" );
+ }
+
+ public ChannelMap( String name )
+ {
+ mName = name;
+ }
+
+ public String toString()
+ {
+ return mName;
+ }
+ /**
+ * Creates a copy of this channel map
+ */
+ public ChannelMap copyOf()
+ {
+ ChannelMap map = new ChannelMap( mName );
+
+ for( ChannelRange range: mRanges )
+ {
+ map.addRange( range.copyOf() );
+ }
+
+ return map;
+ }
+
+ public int getInvalidRange()
+ {
+ for( int x = 0; x < mRanges.size(); x++ )
+ {
+ if( !mRanges.get( x ).isValid() )
+ {
+ return x;
+ }
+ }
+
+ return -1;
+ }
+
+ public ArrayList getRanges()
+ {
+ return mRanges;
+ }
+
+ @XmlElement( name = "range" )
+ public void setRanges( ArrayList ranges )
+ {
+ mRanges = ranges;
+ }
+
+ @XmlAttribute
+ public String getName()
+ {
+ return mName;
+ }
+
+ public void setName( String name )
+ {
+ mName = name;
+ }
+
+ public int getFrequency( int channelNumber )
+ {
+ for( ChannelRange range: mRanges )
+ {
+ if( range.hasChannel( channelNumber ) )
+ {
+ return range.getFrequency( channelNumber );
+ }
+ }
+
+ return 0;
+ }
+
+ public void addRange( ChannelRange range )
+ {
+ mRanges.add( range );
+ }
+
+ public void deleteRange( ChannelRange range )
+ {
+ mRanges.remove( range );
+ }
+
+}
diff --git a/src/controller/channel/ChannelMapEditor.java b/src/controller/channel/ChannelMapEditor.java
new file mode 100644
index 00000000..4f79d4da
--- /dev/null
+++ b/src/controller/channel/ChannelMapEditor.java
@@ -0,0 +1,223 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+
+import javax.swing.AbstractCellEditor;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JFormattedTextField;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.table.TableCellEditor;
+import javax.swing.text.InternationalFormatter;
+
+import log.Log;
+import net.miginfocom.swing.MigLayout;
+
+public class ChannelMapEditor extends JPanel implements ActionListener
+{
+ private static final long serialVersionUID = 1L;
+ private ChannelMapNode mChannelMapNode;
+
+ private JLabel mNameLabel;
+ private JTextField mNameText;
+ private ChannelMapModel mRangeModel;
+ private JTable mRangeTable;
+ private JButton mDelete;
+ private ChannelMap mChannelMapCopy;
+
+ public ChannelMapEditor( ChannelMapNode ChannelMap )
+ {
+ mChannelMapNode = ChannelMap;
+ mChannelMapCopy = mChannelMapNode.getChannelMap().copyOf();
+ initGUI();
+ }
+
+ private void initGUI()
+ {
+ setLayout( new MigLayout( "fill,wrap 2", "[right][grow]", "[][][grow][][]" ) );
+
+ add( new JLabel( "Channel Map" ), "span, align center" );
+
+ mNameLabel = new JLabel( "Name:" );
+ add( mNameLabel );
+
+ mNameText = new JTextField( mChannelMapCopy.getName() );
+ mNameText.addFocusListener( new FocusListener()
+ {
+ @Override public void focusGained( FocusEvent arg0 ) {}
+
+ @Override
+ public void focusLost( FocusEvent arg0 )
+ {
+ Log.info( "text field action event fired, new value:" + mNameText.getText() );
+ mChannelMapCopy.setName( mNameText.getText() );
+ }
+ } );
+ add( mNameText, "growx, push" );
+
+ mRangeModel = new ChannelMapModel( mChannelMapCopy );
+
+ 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() );
+ }
+
+ JScrollPane scrollPane = new JScrollPane( mRangeTable );
+ add( scrollPane, "span,grow,push");
+
+ JButton btnNew = new JButton( "New Range" );
+ btnNew.addActionListener( ChannelMapEditor.this );
+ add( btnNew );
+
+ mDelete = new JButton( "Delete" );
+ mDelete.addActionListener( ChannelMapEditor.this );
+ add( mDelete, "wrap" );
+
+ JButton btnSave = new JButton( "Save" );
+ btnSave.addActionListener( ChannelMapEditor.this );
+ add( btnSave, "growx" );
+
+ JButton btnReset = new JButton( "Reset " );
+ btnReset.addActionListener( ChannelMapEditor.this );
+ add( btnReset, "wrap" );
+ }
+
+ private boolean checkForValidRanges()
+ {
+ int invalidRow = mChannelMapCopy.getInvalidRange();
+
+ if( invalidRow != -1 && invalidRow <= mRangeTable.getRowCount() )
+ {
+ mRangeTable.setRowSelectionInterval( invalidRow, invalidRow );
+
+ JOptionPane.showMessageDialog( this,
+ "Starting channel number must be less than ending channel number",
+ "Channel number range error",
+ JOptionPane.WARNING_MESSAGE );
+
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ String command = e.getActionCommand();
+
+ switch( command )
+ {
+ case "New Range":
+ mRangeModel.addRange( new ChannelRange() );
+ break;
+ case "Save":
+ if( checkForValidRanges() )
+ {
+ mChannelMapNode.getChannelMap()
+ .setName( mChannelMapCopy.getName() );
+ mChannelMapNode.getChannelMap()
+ .setRanges( mChannelMapCopy.getRanges() );
+
+ mChannelMapNode.save();
+
+ mChannelMapNode.refresh();
+
+ mChannelMapNode.show();
+ }
+ case "Reset":
+ mChannelMapCopy = mChannelMapNode.getChannelMap();
+ mRangeModel.setChannelMap( mChannelMapCopy );
+ mNameText.setText( mChannelMapCopy.getName() );
+ break;
+ case "Delete":
+ int row = mRangeTable.getSelectedRow();
+
+ if( row < 0 )
+ {
+ JOptionPane.showMessageDialog( this,
+ "Please select a channel range to delete",
+ "Select a range",
+ JOptionPane.WARNING_MESSAGE );
+ }
+ else
+ {
+ mRangeModel.removeRange(
+ mRangeTable.convertRowIndexToModel( row ) );
+ }
+ break;
+ }
+ }
+
+ public class RangeTableEditor extends AbstractCellEditor
+ implements TableCellEditor, ActionListener
+ {
+ JFormattedTextField mEditor;
+
+ public RangeTableEditor()
+ {
+ InternationalFormatter nf = new InternationalFormatter();
+ nf.setMinimum( 1 );
+ nf.setAllowsInvalid( false );
+
+ mEditor = new JFormattedTextField( nf );
+ }
+
+ @Override
+ public Object getCellEditorValue()
+ {
+ Log.info( "getCellEditorValue - I've been invoked!" );
+ return mEditor.getText();
+ }
+
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ Log.info( "Action Performed:" + e.getActionCommand() );
+ }
+
+ @Override
+ public Component getTableCellEditorComponent( JTable table,
+ Object value,
+ boolean isSelected,
+ int row,
+ int column )
+ {
+ mEditor.setValue( value );
+
+ return mEditor;
+ }
+ }
+}
diff --git a/src/controller/channel/ChannelMapList.java b/src/controller/channel/ChannelMapList.java
new file mode 100644
index 00000000..38ba11ee
--- /dev/null
+++ b/src/controller/channel/ChannelMapList.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+import java.util.ArrayList;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+
+@XmlSeeAlso( ChannelMap.class )
+public class ChannelMapList
+{
+ private ArrayList mChannelMap = new ArrayList();
+
+ public ChannelMapList()
+ {
+ /* Empty constructor for JAXB */
+ }
+
+ @XmlElement( name = "channel_map" )
+ public ArrayList getChannelMap()
+ {
+ return mChannelMap;
+ }
+
+ public void setChannelMap( ArrayList ChannelMaps )
+ {
+ mChannelMap = ChannelMaps;
+ }
+
+ public void addChannelMap( ChannelMap ChannelMap )
+ {
+ mChannelMap.add( ChannelMap );
+ }
+
+ public void removeChannelMap( ChannelMap ChannelMap )
+ {
+ mChannelMap.remove( ChannelMap );
+ }
+
+ public void clearChannelMaps()
+ {
+ mChannelMap.clear();
+ }
+
+ /**
+ * Gets the named channel map, or returns an empty map
+ */
+ public ChannelMap getChannelMap( String name )
+ {
+ for( ChannelMap map: mChannelMap )
+ {
+ if( map.getName().equalsIgnoreCase( name ) )
+ {
+ return map;
+ }
+ }
+
+ return new ChannelMap();
+ }
+}
diff --git a/src/controller/channel/ChannelMapListNode.java b/src/controller/channel/ChannelMapListNode.java
new file mode 100644
index 00000000..158b21d4
--- /dev/null
+++ b/src/controller/channel/ChannelMapListNode.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+
+import controller.ConfigurableNode;
+
+public class ChannelMapListNode extends ConfigurableNode
+{
+ private static final long serialVersionUID = 1L;
+
+ public ChannelMapListNode( ChannelMapList list )
+ {
+ super( list );
+ }
+
+ public ChannelMapList getChannelMapList()
+ {
+ return (ChannelMapList)getUserObject();
+ }
+
+ public void init()
+ {
+ for( ChannelMap channelMap: getChannelMapList().getChannelMap() )
+ {
+ ChannelMapNode node = new ChannelMapNode( channelMap );
+
+ getModel().addNode( node, ChannelMapListNode.this, getChildCount() );
+
+ node.init();
+ }
+
+ sort();
+ }
+
+ public String toString()
+ {
+ return "Channel Maps [" + this.getChildCount() + "]";
+ }
+
+ public JPopupMenu getContextMenu()
+ {
+ JPopupMenu retVal = new JPopupMenu();
+
+ JMenuItem addSystemItem = new JMenuItem( "Add Channel Map" );
+ addSystemItem.addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ ChannelMap channelMap = new ChannelMap();
+
+ getChannelMapList().addChannelMap( channelMap );
+
+ getModel().addNode( new ChannelMapNode( channelMap ),
+ ChannelMapListNode.this,
+ ChannelMapListNode.this.getChildCount() );
+
+ save();
+ }
+ } );
+
+ retVal.add( addSystemItem );
+
+ return retVal;
+ }
+}
diff --git a/src/controller/channel/ChannelMapModel.java b/src/controller/channel/ChannelMapModel.java
new file mode 100644
index 00000000..bcb3fadb
--- /dev/null
+++ b/src/controller/channel/ChannelMapModel.java
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+import javax.swing.table.AbstractTableModel;
+
+import log.Log;
+
+public class ChannelMapModel extends AbstractTableModel
+{
+ private static final long serialVersionUID = 1L;
+ private static final int sSTART = 0;
+ private static final int sSTOP = 1;
+ private static final int sBASE = 2;
+ private static final int sSIZE = 3;
+
+ protected int[] mColumnWidths = { 110, 110, 110, -1 };
+
+ protected String[] mHeaders = new String[] { "Begin",
+ "End",
+ "Base",
+ "Size" };
+
+ private ChannelMap mChannelMap;
+
+ public ChannelMapModel( ChannelMap map )
+ {
+ mChannelMap = map;
+ }
+
+ public ChannelMap getChannelMap()
+ {
+ return mChannelMap;
+ }
+
+ public void setChannelMap( ChannelMap map )
+ {
+ mChannelMap = map;
+
+ fireTableDataChanged();
+ }
+
+ public void addRange( ChannelRange range )
+ {
+ mChannelMap.addRange( range );
+
+ int index = mChannelMap.getRanges().size() - 1;
+
+ fireTableRowsInserted( index, index );
+ }
+
+ public void removeRange( int rangeIndex )
+ {
+ if( rangeIndex < mChannelMap.getRanges().size() )
+ {
+ mChannelMap.getRanges().remove( rangeIndex );
+ }
+
+ fireTableRowsDeleted( rangeIndex, rangeIndex );
+ }
+
+ public int[] getColumnWidths()
+ {
+ return mColumnWidths;
+ }
+
+ public void setColumnWidths( int[] widths )
+ {
+ if( widths.length != 4 )
+ {
+ throw new IllegalArgumentException( "ChannelMapRangeModel - "
+ + "column widths array should have 5 elements" );
+ }
+ else
+ {
+ mColumnWidths = widths;
+ }
+ }
+
+ @Override
+ public int getRowCount()
+ {
+ return mChannelMap.getRanges().size();
+ }
+
+ @Override
+ public int getColumnCount()
+ {
+ return mHeaders.length;
+ }
+
+ public String getColumnName( int column )
+ {
+ return mHeaders[ column ];
+ }
+
+ @Override
+ public Object getValueAt( int rowIndex, int columnIndex )
+ {
+ ChannelRange range = mChannelMap.getRanges().get( rowIndex );
+
+ switch( columnIndex )
+ {
+ case sSTART:
+ return range.getFirstChannelNumber();
+ case sSTOP:
+ return range.getLastChannelNumber();
+ case sBASE:
+ return range.getBase();
+ case sSIZE:
+ return range.getSize();
+ }
+
+ return null;
+ }
+
+ public boolean isCellEditable( int row, int column )
+ {
+ return true;
+ }
+
+ public void setValueAt( Object value, int row, int column )
+ {
+ try
+ {
+ int parsedValue = Integer.parseInt( (String)value );
+
+ ChannelRange range = mChannelMap.getRanges().get( row );
+
+ switch( column )
+ {
+ case sSTART:
+ range.setFirstChannelNumber( parsedValue );
+ break;
+ case sSTOP:
+ range.setLastChannelNumber( parsedValue );
+ break;
+ case sBASE:
+ range.setBase( parsedValue );
+ break;
+ case sSIZE:
+ range.setSize( parsedValue );
+ break;
+ }
+ }
+ catch( Exception e )
+ {
+ Log.info( "ChannelMapModel - couldn't parse integer value "
+ + "for [" + value + "] - " + e.getLocalizedMessage() );
+ }
+ }
+}
diff --git a/src/controller/channel/ChannelMapNode.java b/src/controller/channel/ChannelMapNode.java
new file mode 100644
index 00000000..d9b68419
--- /dev/null
+++ b/src/controller/channel/ChannelMapNode.java
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+
+import controller.ConfigurableNode;
+
+public class ChannelMapNode extends ConfigurableNode
+{
+ private static final long serialVersionUID = 1L;
+
+ public ChannelMapNode( ChannelMap ChannelMap )
+ {
+ super( ChannelMap );
+ }
+
+ public void init()
+ {
+ //For consistency, but not used
+ }
+
+ @Override
+ public String getIconPath()
+ {
+ return "images/ChannelMap.png";
+ }
+
+ @Override
+ public JPanel getEditor()
+ {
+ return new ChannelMapEditor( this );
+ }
+
+ public ChannelMap getChannelMap()
+ {
+ return (ChannelMap)getUserObject();
+ }
+
+ public String toString()
+ {
+ return getChannelMap().getName();
+ }
+
+ public void delete()
+ {
+ ChannelMapListNode parent = (ChannelMapListNode)getParent();
+
+ parent.getChannelMapList().removeChannelMap( getChannelMap() );
+
+ save();
+
+ getModel().removeNodeFromParent( ChannelMapNode.this );
+
+ /* Show the first child, if one exists so that we don't have a
+ * collapsed list */
+ if( parent.getChildCount() > 0 )
+ {
+ ((ConfigurableNode)parent.getFirstChild()).show();
+ }
+ }
+
+ public JPopupMenu getContextMenu()
+ {
+ JPopupMenu retVal = new JPopupMenu();
+
+ JMenuItem deleteItem = new JMenuItem( "Delete" );
+ deleteItem.addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ delete();
+ }
+ } );
+
+ retVal.add( deleteItem );
+
+ return retVal;
+ }
+}
diff --git a/src/controller/channel/ChannelNode.java b/src/controller/channel/ChannelNode.java
new file mode 100644
index 00000000..0dc6cce9
--- /dev/null
+++ b/src/controller/channel/ChannelNode.java
@@ -0,0 +1,168 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+import java.text.DecimalFormat;
+
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+
+import log.Log;
+import source.tuner.TunerChannel;
+import controller.ConfigurableNode;
+import controller.channel.Channel.ChannelEvent;
+import controller.site.SiteNode;
+import controller.system.SystemNode;
+
+/**
+ * A channel has all of the pieces needed to wire together a source, decoder,
+ * event logger and recorder and be started and stopped.
+ */
+public class ChannelNode extends ConfigurableNode implements ChannelListener
+{
+ private static final long serialVersionUID = 1L;
+
+ private static DecimalFormat sFORMAT = new DecimalFormat( "0.0000" );
+
+ public ChannelNode( Channel channel )
+ {
+ super( channel );
+ }
+
+ public void init()
+ {
+ /* Set the owning system and site names for the channel */
+ SiteNode siteNode = (SiteNode)getParent();
+
+ if( siteNode != null )
+ {
+ getChannel().setSite( siteNode.getSite(), false );
+
+ SystemNode systemNode = (SystemNode)siteNode.getParent();
+
+ if( systemNode != null )
+ {
+ getChannel().setSystem( systemNode.getSystem(), false );
+ }
+ }
+
+ /* Add this node as listener to receive changes from underlying channel */
+ getChannel().addListener( this );
+
+ /* Add the resource manager to the channel so that the channel
+ * can provide channel change events to all system resources */
+ getChannel().setResourceManager( getModel().getResourceManager() );
+ }
+
+ /**
+ * Listener method to receive channel changes from the underlying chnannel
+ */
+ @Override
+ public void occurred( Channel channel, ChannelEvent event )
+ {
+ switch( event )
+ {
+ /* Refresh the node for each of these events */
+ case CHANGE_DECODER:
+ case CHANGE_ENABLED:
+ case CHANGE_NAME:
+ case CHANGE_SELECTED:
+ case CHANGE_SITE:
+ case CHANGE_SYSTEM:
+ case PROCESSING_STARTED:
+ case PROCESSING_STOPPED:
+ this.refresh();
+ break;
+ /* We're being deleted, so cleanup */
+ case CHANNEL_DELETED:
+ delete();
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public String getIconPath()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ Channel channel = getChannel();
+
+ sb.append( channel.getDecodeConfiguration().getDecoderType().getIconPrefix() );
+
+ if( channel.getEnabled() )
+ {
+ sb.append( channel.isProcessing() ? "_green" : "_red" );
+ }
+
+ sb.append( ".png" );
+
+ return sb.toString();
+ }
+
+ @Override
+ public JPanel getEditor()
+ {
+ return new ChannelEditor( this );
+ }
+
+ public Channel getChannel()
+ {
+ return (Channel)this.getUserObject();
+ }
+
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append( getChannel().getName() );
+
+ TunerChannel channel = getChannel().getTunerChannel();
+
+ if( channel != null )
+ {
+ sb.append( " (" );
+ sb.append( sFORMAT.format( (double)channel.getFrequency() / 1000000.0d ) + " MHz" );
+ sb.append( ")" );
+
+ }
+
+ return sb.toString();
+ }
+
+ public JPopupMenu getContextMenu()
+ {
+ JPopupMenu popupMenu = new JPopupMenu( "Channel Menu" );
+ popupMenu.add( getChannel().getContextMenu() );
+
+ return popupMenu;
+ }
+
+ public void delete()
+ {
+ Log.info( "Channel Node - deleting ..." );
+ ((SiteNode)getParent()).getSite().removeChannel( getChannel() );
+
+ Log.info( "Channel Node - saving after channel delete" );
+ save();
+
+ Log.info( "Channel Node - removing from site node" );
+ getModel().removeNodeFromParent( ChannelNode.this );
+ }
+}
diff --git a/src/controller/channel/ChannelRange.java b/src/controller/channel/ChannelRange.java
new file mode 100644
index 00000000..e641f8a0
--- /dev/null
+++ b/src/controller/channel/ChannelRange.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement
+@XmlType( name = "range" )
+public class ChannelRange
+{
+ private int mFirst = 1;
+ private int mLast = 1023;
+ private int mBase = 450000000;
+ private int mSize = 12500;
+
+ /* JAXB requirement - do not remove */
+ public ChannelRange() {}
+
+ public ChannelRange( int first, int last, int base, int size )
+ {
+ mFirst = first;
+ mLast = last;
+ mBase = base;
+ mSize = size;
+ }
+
+ public ChannelRange copyOf()
+ {
+ return new ChannelRange( mFirst, mLast, mBase, mSize );
+ }
+
+ public boolean isValid()
+ {
+ return mFirst < mLast;
+ }
+
+ public boolean hasChannel( int channel )
+ {
+ return mFirst <= channel && channel <= mLast;
+ }
+
+ public int getFrequency( int channel )
+ {
+ if( hasChannel( channel ) )
+ {
+ return mBase + ( ( channel - mFirst ) * mSize );
+ }
+ else
+ {
+ return 0;
+ }
+ }
+
+ @XmlAttribute( name = "first" )
+ public int getFirstChannelNumber()
+ {
+ return mFirst;
+ }
+
+ public void setFirstChannelNumber( int first )
+ {
+ mFirst = first;
+ }
+
+ @XmlAttribute( name = "last" )
+ public void setLastChannelNumber( int last )
+ {
+ mLast = last;
+ }
+
+ public int getLastChannelNumber()
+ {
+ return mLast;
+ }
+
+ /**
+ * Sets the base frequency
+ * @param base frequency in hertz
+ */
+ @XmlAttribute( name = "base" )
+ public void setBase( int base )
+ {
+ mBase = base;
+ }
+
+ public int getBase()
+ {
+ return mBase;
+ }
+
+ /**
+ * Sets the channel size
+ * @param size in hertz
+ */
+ @XmlAttribute( name = "size" )
+ public void setSize( int size )
+ {
+ mSize = size;
+ }
+
+ public int getSize()
+ {
+ return mSize;
+ }
+}
diff --git a/src/controller/channel/ChannelSelectionListener.java b/src/controller/channel/ChannelSelectionListener.java
new file mode 100644
index 00000000..9719ea6f
--- /dev/null
+++ b/src/controller/channel/ChannelSelectionListener.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+public interface ChannelSelectionListener
+{
+ public void channelSelected( ProcessingChain channel );
+}
diff --git a/src/controller/channel/ProcessingChain.java b/src/controller/channel/ProcessingChain.java
new file mode 100644
index 00000000..5c2f019c
--- /dev/null
+++ b/src/controller/channel/ProcessingChain.java
@@ -0,0 +1,721 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.channel;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.LinkedTransferQueue;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import log.Log;
+import message.Message;
+import record.Recorder;
+import record.wave.ComplexWaveRecorder;
+import record.wave.FloatWaveRecorder;
+import sample.Broadcaster;
+import sample.Listener;
+import sample.Provider;
+import sample.complex.ComplexSample;
+import source.ComplexSource;
+import source.FloatArraySource;
+import source.FloatSource;
+import source.Source;
+import source.Source.SampleType;
+import source.config.SourceConfigMixer;
+import source.tuner.Tuner;
+import source.tuner.TunerChannelSource;
+import alias.AliasList;
+import audio.AudioOutput;
+import audio.AudioTypeListener;
+import audio.SquelchListener;
+import controller.ResourceManager;
+import controller.ThreadPoolManager.ThreadType;
+import controller.activity.CallEvent;
+import controller.channel.Channel.ChannelType;
+import controller.state.ChannelState;
+import controller.state.ChannelState.State;
+import decode.Decoder;
+import decode.DecoderFactory;
+import eventlog.EventLogType;
+import eventlog.EventLogger;
+
+/**
+ * A channel has all of the pieces needed to wire together a source, decoder,
+ * event logger and recorder and be started and stopped.
+ */
+public class ProcessingChain implements Listener
+{
+ protected AtomicBoolean mRunning = new AtomicBoolean();
+ protected Channel mChannel;
+ protected Source mSource;
+ protected Decoder mDecoder;
+ protected List mRecorders = new ArrayList();
+ protected ArrayList mEventLoggers = new ArrayList();
+ protected AudioOutput mAudioOutput;
+ protected ChannelState mChannelState;
+ protected ResourceManager mResourceManager;
+ private ScheduledFuture> mProcessorTask;
+ private AliasList mAliasList;
+
+ private ComplexListener mComplexListener = new ComplexListener();
+ private LinkedTransferQueue> mComplexBuffer =
+ new LinkedTransferQueue>();
+
+ private FloatListener mFloatListener = new FloatListener();
+ private FloatArrayListener mFloatArrayListener = new FloatArrayListener();
+ private LinkedTransferQueue mFloatBuffer =
+ new LinkedTransferQueue();
+
+ protected Broadcaster mBroadcaster = new Broadcaster();
+
+ public ProcessingChain( Channel channel, ResourceManager resourceManager )
+ {
+ mChannel = channel;
+ mResourceManager = resourceManager;
+
+ if( mResourceManager != null )
+ {
+ mAliasList = mResourceManager.getPlaylistManager()
+ .getPlayist().getAliasDirectory().getAliasList(
+ mChannel.getAliasListName() );
+ }
+ }
+
+ public Listener> getComplexListener()
+ {
+ return mComplexListener;
+ }
+
+ public Listener getFloatListener()
+ {
+ return mFloatListener;
+ }
+
+ public Listener getFloatArrayListener()
+ {
+ return mFloatArrayListener;
+ }
+
+ public String toString()
+ {
+ return mChannel.getName();
+ }
+
+ public AliasList getAliasList()
+ {
+ return mAliasList;
+ }
+
+ public void dispose()
+ {
+ if( mProcessorTask != null )
+ {
+ mResourceManager.getThreadPoolManager().cancel( mProcessorTask );
+ }
+
+ if( mChannelState != null )
+ {
+ mChannelState.dispose();
+ }
+
+ mChannelState = null;
+
+ if( mDecoder != null )
+ {
+ mDecoder.dispose();
+ }
+
+ mBroadcaster.dispose();
+ mEventLoggers.clear();
+ mRecorders.clear();
+
+
+ if( mAudioOutput != null )
+ {
+ mAudioOutput.dispose();
+ }
+
+ mResourceManager = null;
+ }
+
+ public ResourceManager getResourceManager()
+ {
+ return mResourceManager;
+ }
+
+ public void start()
+ {
+ if( !mRunning.get() && mChannel.getEnabled() )
+ {
+ updateSource();
+
+ updateDecoder();
+
+ updateEventLogging();
+
+ updateRecording();
+
+ mRunning.set( true );
+ }
+ else
+ {
+ Log.info( getLogPrefix() + "start() invoked, but processing chain is "
+ + "already started" );
+ }
+ }
+
+ /**
+ * Performs closeout operations to prepare for channel deletion
+ */
+ public void stop()
+ {
+ if( mRunning.get() )
+ {
+ updateSource();
+
+ updateDecoder();
+
+ updateRecording();
+
+ /* Flush call events to the logger */
+ if( getChannel().getChannelType() == ChannelType.STANDARD )
+ {
+ getChannelState().getCallEventModel().flush();
+ }
+
+ updateEventLogging();
+
+ if( mAudioOutput != null )
+ {
+ mAudioOutput.setAudioPlaybackEnabled( false );
+ }
+
+ mRunning.set( false );
+ }
+ else
+ {
+ Log.info( getLogPrefix() + "stop() invoked, but processing chain was "
+ + "already stopped" );
+ }
+ }
+
+ private String getLogPrefix()
+ {
+ return "Channel [" + mChannel.getName() + "] - ";
+ }
+
+ public void updateSource()
+ {
+ /* Cleanup existing source */
+ if( mSource != null )
+ {
+ if( mSource instanceof ComplexSource )
+ {
+ ((ComplexSource)mSource).removeListener( this.getComplexListener() );
+ }
+ else if( mSource instanceof FloatSource )
+ {
+ ((FloatSource)mSource).removeListener( this.getFloatListener() );
+ }
+ else if( mSource instanceof FloatArraySource )
+ {
+ ((FloatArraySource)mSource)
+ .removeListener( this.getFloatArrayListener() );
+ }
+
+ if( mProcessorTask != null )
+ {
+ mResourceManager.getThreadPoolManager().cancel( mProcessorTask );
+ }
+
+ mSource.dispose();
+
+ mSource = null;
+ }
+
+ /* Get a new source if needed and start the sample processor thread */
+ if( mChannel.getEnabled() && mResourceManager != null )
+ {
+ mSource = mResourceManager.getSourceManager()
+ .getSource( ProcessingChain.this );
+
+ if( mSource != null )
+ {
+ if( mSource instanceof ComplexSource )
+ {
+ try
+ {
+ mProcessorTask =
+ mResourceManager.getThreadPoolManager()
+ .schedule( ThreadType.DECODER,
+ new ComplexProcessor(),
+ 50, TimeUnit.MILLISECONDS );
+
+ ((ComplexSource)mSource)
+ .setListener( this.getComplexListener() );
+ }
+ catch( RejectedExecutionException ree )
+ {
+ Log.error( getLogPrefix() + "ProcessingChain - "
+ + "error scheduling complex sample processing "
+ + "thread - " + ree.getLocalizedMessage() );
+
+ mSource.dispose();
+ }
+ }
+ else if( mSource instanceof FloatSource )
+ {
+ try
+ {
+ mProcessorTask =
+ mResourceManager.getThreadPoolManager()
+ .schedule( ThreadType.DECODER,
+ new FloatProcessor(),
+ 50, TimeUnit.MILLISECONDS );
+
+ ((FloatSource)mSource)
+ .setListener( this.getFloatListener() );
+ }
+ catch( RejectedExecutionException ree )
+ {
+ Log.error( getLogPrefix() + "ProcessingChain - "
+ + "error scheduling float sample processing "
+ + "thread - " + ree.getLocalizedMessage() );
+
+ mSource.dispose();
+ }
+ }
+ else if( mSource instanceof FloatArraySource )
+ {
+ try
+ {
+ mProcessorTask =
+ mResourceManager.getThreadPoolManager()
+ .schedule( ThreadType.DECODER,
+ new FloatProcessor(),
+ 50, TimeUnit.MILLISECONDS );
+
+ ((FloatArraySource)mSource)
+ .setListener( this.getFloatArrayListener() );
+ }
+ catch( RejectedExecutionException ree )
+ {
+ Log.error( getLogPrefix() + "ProcessingChain - "
+ + "error scheduling float sample processing "
+ + "thread - " + ree.getLocalizedMessage() );
+
+ mSource.dispose();
+ }
+ }
+ }
+ }
+ }
+
+ public void updateDecoder()
+ {
+ if( mChannel.getEnabled() )
+ {
+ AliasList aliasList = null;
+
+ if( mResourceManager != null )
+ {
+ aliasList = mResourceManager.getPlaylistManager()
+ .getPlayist().getAliasDirectory().getAliasList(
+ mChannel.getAliasListName() );
+ }
+
+ /* Setup the channel state */
+ mChannelState = DecoderFactory.getChannelStateNew( this, aliasList );
+
+ if( mSource == null )
+ {
+ mChannelState.setState( State.NO_TUNER );
+ }
+ else
+ {
+ mDecoder = DecoderFactory.
+ getDecoder( this, mSource.getSampleType(), aliasList );
+
+ if( mDecoder != null )
+ {
+ /* Register to receive decoded messages and auxiliary messages */
+ mDecoder.addMessageListener( ProcessingChain.this );
+
+ /* Setup the audio output processing thread */
+ String threadName = "SDRTrunk Audio - " +
+ mChannel.getSite().getName() +
+ "-" + mChannel.getName();
+
+ mAudioOutput = new AudioOutput( threadName );
+ addFloatListener( mAudioOutput );
+ }
+ }
+
+ if( mAudioOutput != null )
+ {
+ /* Register audio output to be controlled by the channel state */
+ mChannelState.addListener( (SquelchListener)mAudioOutput );
+ mChannelState.setListener( (AudioTypeListener)mAudioOutput );
+ }
+ }
+ }
+
+ /**
+ * Starts, stops or changes event logging
+ */
+ public void updateEventLogging()
+ {
+ /* Shutdown and remove existing event loggers */
+ if( mEventLoggers != null )
+ {
+ for( EventLogger logger: mEventLoggers )
+ {
+ logger.stop();
+ }
+
+ mEventLoggers.clear();
+ }
+
+ /* Construct event loggers and add as message listeners */
+ if( mChannel.getEnabled() &&
+ mResourceManager != null )
+ {
+ //Get the event loggers
+ ArrayList types = getChannel()
+ .getEventLogConfiguration().getLoggers();
+
+ for( EventLogType type: types )
+ {
+ EventLogger logger;
+
+ if( type == EventLogType.CALL_EVENT )
+ {
+ logger = mResourceManager.getEventLogManager()
+ .getLogger( this, type );
+
+ getChannelState().getCallEventModel()
+ .addListener( (Listener)logger );
+ }
+ else
+ {
+ logger = mResourceManager.getEventLogManager()
+ .getLogger( this, type );
+
+ addListener( (Listener)logger );
+ }
+
+ logger.start();
+
+ mEventLoggers.add( logger );
+ }
+ }
+ }
+
+ /**
+ * Starts, stops or changes data recording
+ */
+ public void updateRecording()
+ {
+ /* Remove any existing recorders */
+ if( mRecorders != null )
+ {
+ for( Recorder recorder: mRecorders )
+ {
+ if( recorder != null )
+ {
+ try
+ {
+ recorder.stop();
+ }
+ catch ( IOException ioe )
+ {
+ Log.error( getLogPrefix() + "Couldn't stop recorder [" +
+ recorder.getFileName() + "] " +
+ ioe.getLocalizedMessage() );
+ }
+ }
+ }
+ }
+
+ /* Start any new recorders */
+ if( mChannel.getEnabled() &&
+ mResourceManager != null &&
+ mDecoder != null )
+ {
+ mRecorders = mResourceManager.getRecorderManager()
+ .getRecorder( this );
+
+ /* Iterate any returned recorders and start/add them */
+ for( Recorder recorder: mRecorders )
+ {
+ try
+ {
+ recorder.start();
+
+ if( recorder instanceof FloatWaveRecorder )
+ {
+ Log.info( getLogPrefix() + "- started audio recording [" +
+ recorder.getFileName() + "]" );
+
+ mDecoder.addFloatListener( (FloatWaveRecorder)recorder );
+ }
+ else if( recorder instanceof ComplexWaveRecorder )
+ {
+ Log.info( getLogPrefix() + "- started baseband recording [" +
+ recorder.getFileName() + "]" );
+
+ mDecoder.addComplexListener( (ComplexWaveRecorder )recorder );
+ }
+
+ }
+ catch ( IOException e )
+ {
+ Log.error( getLogPrefix() + "Unable to start recorder [" +
+ recorder.getFileName() + "]" );
+ }
+ }
+ }
+ }
+
+ public Channel getChannel()
+ {
+ return mChannel;
+ }
+
+ public AudioOutput getAudioOutput()
+ {
+ return mAudioOutput;
+ }
+
+ public Source getSource()
+ {
+ return mSource;
+ }
+
+ /**
+ * Returns the tuner that is sourcing this channel, or null if it's being
+ * sourced from some other source
+ */
+ public Tuner getTuner()
+ {
+ Tuner retVal = null;
+
+ if( mSource instanceof TunerChannelSource )
+ {
+ retVal = ((TunerChannelSource)mSource).getTuner();
+ }
+
+ return retVal;
+ }
+
+ public Decoder getDecoder()
+ {
+ return mDecoder;
+ }
+
+ public ChannelState getChannelState()
+ {
+ return mChannelState;
+ }
+
+ public boolean isRunning()
+ {
+ return mRunning.get();
+ }
+
+ public boolean isProcessing()
+ {
+ return isRunning() && mSource != null;
+ }
+
+ private source.mixer.MixerChannel getMixerChannel()
+ {
+ source.mixer.MixerChannel retVal = null;
+
+ if( mChannel.getSourceConfiguration() instanceof SourceConfigMixer )
+ {
+ SourceConfigMixer mixer = (SourceConfigMixer)mChannel.getSourceConfiguration();
+
+ retVal = mixer.getChannel();
+ }
+
+ return retVal;
+ }
+
+ @Override
+ public void receive( Message message )
+ {
+ if( message.isValid() )
+ {
+ mBroadcaster.receive( message );
+ }
+
+ }
+
+ public void addFloatListener( Listener listener )
+ {
+ if( mDecoder != null )
+ {
+ mDecoder.addFloatListener( listener );
+ }
+ }
+
+ public void removeFloatListener( Listener listener )
+ {
+ if( mDecoder != null )
+ {
+ mDecoder.removeFloatListener( listener );
+ }
+ }
+
+ public void addComplexListener( Listener listener )
+ {
+ if( mDecoder != null )
+ {
+ mDecoder.addComplexListener( listener );
+ }
+ else
+ {
+ Log.error( getLogPrefix() + "attempt to add a complex sample "
+ + "listener, but the decoder is null" );
+ }
+ }
+
+ public void removeComplexListener( Listener listener )
+ {
+ if( mDecoder != null )
+ {
+ mDecoder.removeComplexListener( listener );
+ }
+ else
+ {
+ Log.error( getLogPrefix() + "attempt to remove a complex sample "
+ + "listener, but the decoder is null" );
+ }
+ }
+
+ public void addListener( Listener listener )
+ {
+ mBroadcaster.addListener( listener );
+ }
+
+ public void addListeners( List> listeners )
+ {
+ for( Listener listener: listeners )
+ {
+ addListener( listener );
+ }
+ }
+
+ public void removeListener( Listener listener )
+ {
+ mBroadcaster.removeListener( listener );
+ }
+
+ /**
+ * Runnable float sample processing task. Schedule this task to run 20
+ * times a second when we're receiving complex samples from our source.
+ * Schedule execution via the thread pool manager, obtainable from the
+ * resource manager,
+ */
+ private class FloatProcessor implements Runnable
+ {
+ @Override
+ public void run()
+ {
+ List samples = new ArrayList();
+
+ mFloatBuffer.drainTo( samples, 48000 );
+
+ for( Float sample: samples )
+ {
+ if( mDecoder != null )
+ {
+ mDecoder.getFloatReceiver().receive( sample );
+ }
+ }
+
+ samples.clear();
+ }
+ }
+
+ /**
+ * Runnable complex sample processing task. Schedule this task to run 20
+ * times a second when we're receiving complex samples from our source.
+ * Schedule execution via the thread pool manager, obtainable from the
+ * resource manager,
+ */
+ private class ComplexProcessor implements Runnable
+ {
+ @Override
+ public void run()
+ {
+ List> sampleSets =
+ new ArrayList>();
+
+ mComplexBuffer.drainTo( sampleSets, 16 );
+
+ for( List samples: sampleSets )
+ {
+ for( ComplexSample sample: samples )
+ {
+ if( sample != null && mDecoder != null )
+ {
+ mDecoder.getComplexReceiver().receive( sample );
+ }
+ }
+
+ samples.clear();
+ }
+
+ sampleSets.clear();
+ }
+ }
+
+ public class ComplexListener implements Listener>
+ {
+ @Override
+ public void receive( List samples )
+ {
+ mComplexBuffer.add( samples );
+ }
+ }
+
+ public class FloatListener implements Listener
+ {
+ @Override
+ public void receive( Float sample )
+ {
+ mFloatBuffer.add( sample );
+ }
+ }
+
+ public class FloatArrayListener implements Listener
+ {
+ @Override
+ public void receive( Float[] samples )
+ {
+ mFloatBuffer.addAll( Arrays.asList( samples ) );
+ }
+ }
+}
diff --git a/src/controller/config/Configuration.java b/src/controller/config/Configuration.java
new file mode 100644
index 00000000..ff979c4a
--- /dev/null
+++ b/src/controller/config/Configuration.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.config;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+
+import record.config.RecordConfiguration;
+import source.config.SourceConfiguration;
+import decode.config.AuxDecodeConfiguration;
+import decode.config.DecodeConfiguration;
+import eventlog.config.EventLogConfiguration;
+
+@XmlSeeAlso( { AuxDecodeConfiguration.class,
+ DecodeConfiguration.class,
+ EventLogConfiguration.class,
+ RecordConfiguration.class,
+ SourceConfiguration.class } )
+@XmlRootElement( name = "configuration" )
+public abstract class Configuration
+{
+ public Configuration()
+ {
+ }
+}
diff --git a/src/controller/site/Site.java b/src/controller/site/Site.java
new file mode 100644
index 00000000..2cc24419
--- /dev/null
+++ b/src/controller/site/Site.java
@@ -0,0 +1,86 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.site;
+
+import java.util.ArrayList;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+
+import controller.channel.Channel;
+
+@XmlSeeAlso( { Channel.class } )
+@XmlRootElement( name = "site" )
+public class Site
+{
+ private String mName;
+
+ private ArrayList mChannel = new ArrayList();
+
+ public Site()
+ {
+ this( "New Site" );
+ }
+
+ public Site( String name )
+ {
+ mName = name;
+ }
+
+ public String toString()
+ {
+ return mName;
+ }
+
+ @XmlAttribute
+ public String getName()
+ {
+ return mName;
+ }
+
+ public void setName( String name )
+ {
+ mName = name;
+ }
+
+ public ArrayList getChannel()
+ {
+ return mChannel;
+ }
+
+ public void setChannel( ArrayList configs )
+ {
+ mChannel = configs;
+ }
+
+ public void addChannel( Channel channel )
+ {
+ mChannel.add( channel );
+ }
+
+ public void removeChannel( Channel channel )
+ {
+ mChannel.remove( channel );
+ }
+
+ public void removeAllChannels()
+ {
+ mChannel.clear();
+ }
+}
diff --git a/src/controller/site/SiteEditor.java b/src/controller/site/SiteEditor.java
new file mode 100644
index 00000000..b4d4a74e
--- /dev/null
+++ b/src/controller/site/SiteEditor.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.site;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.tree.TreePath;
+
+import net.miginfocom.swing.MigLayout;
+
+public class SiteEditor extends JPanel implements ActionListener
+{
+ private static final long serialVersionUID = 1L;
+ private SiteNode mSiteNode;
+
+ private JLabel mLabelName;
+ private JTextField mTextSite;
+
+ public SiteEditor( SiteNode site )
+ {
+ mSiteNode = site;
+ initGUI();
+ }
+
+ private void initGUI()
+ {
+ setLayout( new MigLayout() );
+
+ setBorder( BorderFactory.createTitledBorder( "Site" ) );
+
+ mLabelName = new JLabel( "Name:" );
+ add( mLabelName, "align right" );
+
+ mTextSite = new JTextField( mSiteNode.getSite().getName() );
+ add( mTextSite, "grow, wrap" );
+
+ JButton btnSave = new JButton( "Save" );
+ btnSave.addActionListener( SiteEditor.this );
+ add( btnSave );
+
+ JButton btnReset = new JButton( "Reset" );
+ btnReset.addActionListener( SiteEditor.this );
+ add( btnReset, "wrap" );
+ }
+
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ String command = e.getActionCommand();
+
+ if( command.contentEquals( "Save" ) )
+ {
+ boolean expanded = mSiteNode.getModel().getTree()
+ .isExpanded( new TreePath( mSiteNode ) );
+
+ String name = mTextSite.getText();
+
+ if( name != null )
+ {
+ mSiteNode.getSite().setName( name );
+ mSiteNode.save();
+ mSiteNode.show();
+
+ if( expanded )
+ {
+ mSiteNode.getModel().getTree().expandPath( new TreePath( mSiteNode ) );
+ }
+ }
+ else
+ {
+ JOptionPane.showMessageDialog( SiteEditor.this, "Please enter a system name" );
+ }
+ }
+ else if( command.contentEquals( "Reset" ) )
+ {
+ mTextSite.setText( mSiteNode.getSite().getName() );
+ }
+ }
+}
diff --git a/src/controller/site/SiteNode.java b/src/controller/site/SiteNode.java
new file mode 100644
index 00000000..6e30141e
--- /dev/null
+++ b/src/controller/site/SiteNode.java
@@ -0,0 +1,167 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.site;
+
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Enumeration;
+
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JSeparator;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import controller.ConfigurableNode;
+import controller.channel.Channel;
+import controller.channel.ChannelNode;
+import controller.system.System;
+import controller.system.SystemNode;
+
+public class SiteNode extends ConfigurableNode
+{
+ private static final long serialVersionUID = 1L;
+
+ public SiteNode( Site site )
+ {
+ super( site );
+ }
+
+ public void init()
+ {
+ for( Channel channel: getSite().getChannel() )
+ {
+ ChannelNode node = new ChannelNode( channel );
+
+ getModel().insertNodeInto( node, SiteNode.this, getChildCount() );
+
+ node.init();
+ }
+
+ sort();
+ }
+
+ public String getIconPath()
+ {
+ String retVal = "images/site.png";
+
+ @SuppressWarnings( "unchecked" )
+ Enumeration nodes = children();
+
+ while( nodes.hasMoreElements() )
+ {
+ ChannelNode child = (ChannelNode)nodes.nextElement();
+
+ if( child.getIconPath().contains( "green.png" ) )
+ {
+ return "images/site_green.png";
+ }
+ else if( child.getIconPath().contains( "red.png" ) )
+ {
+ retVal = "images/site_red.png";
+ }
+ }
+
+ return retVal;
+ }
+
+ @Override
+ public JPanel getEditor()
+ {
+ return new SiteEditor( this );
+ }
+
+ public Site getSite()
+ {
+ return (Site)getUserObject();
+ }
+
+ public String toString()
+ {
+ return getSite().getName();
+ }
+
+ public void delete()
+ {
+ for( int x = 0; x < getChildCount(); x++ )
+ {
+ ((ChannelNode)getChildAt( x )).delete();
+ }
+
+ ((SystemNode)getParent()).getSystem().removeSite( getSite() );
+
+ save();
+
+ getModel().removeNodeFromParent( SiteNode.this );
+ }
+
+ public JPopupMenu getContextMenu()
+ {
+ JPopupMenu retVal = new JPopupMenu();
+
+ JMenuItem addChannelItem = new JMenuItem( "Add Channel" );
+ addChannelItem.addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ System system = ((SystemNode)getParent()).getSystem();
+
+ Channel channel = new Channel();
+ channel.setSystem( system, false );
+ channel.setSite( getSite(), false );
+
+ getSite().addChannel( channel );
+
+ ChannelNode node = new ChannelNode( channel );
+
+ getModel().addNode( node,
+ SiteNode.this,
+ SiteNode.this.getChildCount() );
+
+ sort();
+ save();
+ node.show();
+
+ /* Give channel reference to the resource manager and the
+ * channel will register the listeners and send a channel
+ * add event */
+ channel.setResourceManager( getModel().getResourceManager() );
+ }
+ } );
+
+ retVal.add( addChannelItem );
+
+ retVal.add( new JSeparator() );
+
+ JMenuItem deleteItem = new JMenuItem( "Delete" );
+ deleteItem.addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ delete();
+ }
+ } );
+
+ retVal.add( deleteItem );
+
+ return retVal;
+ }
+}
diff --git a/src/controller/state/AuxChannelState.java b/src/controller/state/AuxChannelState.java
new file mode 100644
index 00000000..218c37cc
--- /dev/null
+++ b/src/controller/state/AuxChannelState.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.state;
+
+import message.Message;
+import sample.Broadcaster;
+import sample.Listener;
+import controller.state.ChannelState.ChangedAttribute;
+
+/**
+ * ChannelState for auxiliary decoders.
+ */
+public abstract class AuxChannelState implements Listener
+{
+ protected ChannelState mParentChannelState;
+ protected Broadcaster mChangeBroadcaster =
+ new Broadcaster();
+
+ public AuxChannelState( ChannelState parentState )
+ {
+ mParentChannelState = parentState;
+ }
+
+ /**
+ * Prepare for garbage collect
+ */
+ public void dispose()
+ {
+ mChangeBroadcaster.dispose();
+ mParentChannelState = null;
+ }
+
+ /**
+ * Call fade - change display and await a call to reset()
+ */
+ public abstract void fade();
+
+ /**
+ * Activity summary for this decoder
+ */
+ public abstract String getActivitySummary();
+
+ /**
+ * Call end - cleanup the display
+ */
+ public abstract void reset();
+
+ public void addListener( Listener listener )
+ {
+ mChangeBroadcaster.addListener( listener );
+ }
+
+ public void removeListener( Listener listener )
+ {
+ mChangeBroadcaster.removeListener( listener );
+ }
+
+ public void broadcastChange( ChangedAttribute attribute )
+ {
+ if( mChangeBroadcaster != null )
+ {
+ mChangeBroadcaster.broadcast( attribute );
+ }
+ }
+}
diff --git a/src/controller/state/AuxStatePanel.java b/src/controller/state/AuxStatePanel.java
new file mode 100644
index 00000000..8d3bdba9
--- /dev/null
+++ b/src/controller/state/AuxStatePanel.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.state;
+
+import java.awt.Color;
+import java.awt.Font;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import net.miginfocom.swing.MigLayout;
+import sample.Listener;
+import settings.ColorSetting;
+import settings.ColorSetting.ColorSettingName;
+import settings.Setting;
+import settings.SettingChangeListener;
+import settings.SettingsManager;
+import alias.Alias;
+import controller.state.ChannelState.ChangedAttribute;
+
+public abstract class AuxStatePanel extends JPanel
+ implements Listener,
+ SettingChangeListener
+{
+ private static final long serialVersionUID = 1L;
+
+ protected Font mFontDetails = new Font( Font.MONOSPACED, Font.PLAIN, 10 );
+ protected Font mFontAuxDecoder = new Font( Font.MONOSPACED, Font.PLAIN, 10 );
+
+ protected Color mColorLabelDetails;
+ protected Color mColorLabelAuxDecoder;
+
+ protected SettingsManager mSettingsManager;
+ protected AuxChannelState mAuxChannelState;
+
+ public AuxStatePanel( SettingsManager settingsManager,
+ AuxChannelState auxChannelState )
+ {
+ mSettingsManager = settingsManager;
+ mAuxChannelState = auxChannelState;
+
+ /* Register to receive event updates */
+ mAuxChannelState.addListener( this );
+
+ /* Register to receive settings updates */
+ mSettingsManager.addListener( this );
+
+ getColors();
+
+ setLayout( new MigLayout( "insets 1 0 0 0", "[grow,fill]", "[]0[]0[]") );
+ }
+
+ public void dispose()
+ {
+ /* Deregister from changed settings events */
+ mSettingsManager.removeListener( this );
+ mSettingsManager = null;
+
+ mAuxChannelState = null;
+ }
+
+ protected void addLabel( JLabel label, boolean wrap )
+ {
+ label.setFont( mFontAuxDecoder );
+ label.setForeground( mColorLabelAuxDecoder );
+
+ if( wrap )
+ {
+ add( label, "wrap" );
+ }
+ else
+ {
+ add( label );
+ }
+ }
+
+ protected void updateAliasLabel( JLabel label, Alias alias )
+ {
+ if( alias != null )
+ {
+ label.setText( alias.getName() );
+
+ String iconName = alias.getIconName();
+
+ ImageIcon icon = mSettingsManager.getImageIcon( iconName, 12 );
+
+ label.setIcon( icon );
+ }
+ else
+ {
+ label.setText( null );
+ label.setIcon( null );
+ }
+ }
+
+ private void getColors()
+ {
+ mColorLabelDetails = mSettingsManager.getColorSetting(
+ ColorSettingName.CHANNEL_STATE_LABEL_DETAILS ).getColor();
+ mColorLabelAuxDecoder = mSettingsManager.getColorSetting(
+ ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER ).getColor();
+ }
+
+ @Override
+ public void settingChanged( Setting setting )
+ {
+ if( setting instanceof ColorSetting )
+ {
+ ColorSetting colorSetting = (ColorSetting)setting;
+
+ switch( colorSetting.getColorSettingName() )
+ {
+ case CHANNEL_STATE_LABEL_AUX_DECODER:
+ mColorLabelAuxDecoder = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_LABEL_DETAILS:
+ mColorLabelDetails = colorSetting.getColor();
+ repaint();
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void settingDeleted( Setting setting ) {}
+}
diff --git a/src/controller/state/ChannelState.java b/src/controller/state/ChannelState.java
new file mode 100644
index 00000000..0c87c0a2
--- /dev/null
+++ b/src/controller/state/ChannelState.java
@@ -0,0 +1,664 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.state;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import javax.swing.JTable;
+
+import log.Log;
+import message.Message;
+import properties.SystemProperties;
+import sample.Broadcaster;
+import sample.Listener;
+import alias.AliasList;
+import audio.AudioType;
+import audio.AudioTypeListener;
+import audio.SquelchListener;
+import audio.SquelchListener.SquelchState;
+import controller.activity.CallEvent;
+import controller.activity.CallEvent.CallEventType;
+import controller.activity.CallEventAliasCellRenderer;
+import controller.activity.CallEventModel;
+import controller.activity.MessageActivityModel;
+import controller.channel.Channel.ChannelType;
+import controller.channel.ProcessingChain;
+
+/**
+ * ChannelState provides a state machine for tracking a voice or data call
+ * through it's life cycle of CALL/DATA, FADE, and then END, and then back to
+ * an IDLE state. This class also provides squelch control for unsquelching
+ * sound to the speakers, and/or sound to an audio recorder
+ *
+ * State Descriptions:
+ * IDLE No voice or data call activity
+ * CALL/DATA A voice or data call has started, or is continuing
+ * CONTROL Control channel
+ * FADE The phase after a voice or data call when either an explicit
+ * call end has been received, or when no new signalling updates
+ * have been received, and the fade timer has expired. This phase
+ * allows for gui updates to signal to the user that the call is
+ * ended, while continuing to display the call details for the user
+ * END The phase after the voice or data call FADE, when the call is
+ * now over, and the gui should be reset to IDLE.
+ *
+ * When a call is started, invoke mTimerService.startFadeTimer() to start the
+ * call fade monitoring timer process that will automatically signal a call end
+ * once no more call signalling has been detected after the fade timeout has
+ * occurred, by invoking the fade() method.
+ *
+ * When a call ends, invoke the mTimerService.stopFadeTimer() and then invoke
+ * the mTimerService.startResetTimer(). Once the reset timeout occurs,
+ * the reset() method will be called, allowing the channel state to perform
+ * any reset actions, like clearing status and id labels on any view panels.
+ *
+ * AuxChannelState class allows for auxiliary decoders like Fleetsync to both
+ * have a state and leverage the state machine of the parent ChannelState.
+ * This allows a non-squelching channel state like for conventional fm to have
+ * an aux decoder like fleetsync, and allow the fleetsync auxchannelstate to
+ * leverage the call/end states of the parent conventional channelstate.
+ *
+ */
+public abstract class ChannelState implements Listener
+{
+ private State mState = State.IDLE;
+ private Object mCallFadeLock = new Object();
+ private Object mCallResetLock = new Object();
+ private long mFadeTimeout = -1;
+ private long mResetTimeout = -1;
+
+ protected long mCallFadeTimeout;
+ protected long mCallResetTimeout;
+ protected TimerService mTimerService = new TimerService();
+ protected ArrayList mSquelchListeners =
+ new ArrayList();
+ protected ArrayList mAuxChannelStates =
+ new ArrayList();
+ protected Broadcaster mChangeBroadcaster =
+ new Broadcaster();
+ protected AudioTypeListener mAudioTypeListener;
+ protected AudioType mAudioType = AudioType.NORMAL;
+
+
+ protected CallEventModel mCallEventModel = new CallEventModel();
+
+ protected JTable mCallEventTable;
+
+ protected MessageActivityModel mMessageActivityModel =
+ new MessageActivityModel();
+ protected JTable mMessageActivityTable;
+
+ protected ProcessingChain mProcessingChain;
+
+ protected AliasList mAliasList;
+
+ public ChannelState( ProcessingChain processingChain, AliasList aliasList )
+ {
+ mProcessingChain = processingChain;
+ mAliasList = aliasList;
+
+
+ /* Register to receive decoded messages from the processing chain */
+ mProcessingChain.addListener( this );
+
+ SystemProperties props = SystemProperties.getInstance();
+ mCallFadeTimeout = props.get( "call.fade.timeout", 2000 );
+ mCallResetTimeout = props.get( "call.reset.timeout", 4000 );
+ }
+
+ /**
+ * Allows setting the call event model to use for traffic channels, so that
+ * all traffic channels can share the parent control channel call event
+ * model.
+ * @param model
+ */
+ public void setCallEventModel( CallEventModel model )
+ {
+ mCallEventModel = model;
+ }
+
+ public CallEventModel getCallEventModel()
+ {
+ return mCallEventModel;
+ }
+
+ public void receiveCallEvent( CallEvent event )
+ {
+ mCallEventModel.add( event );
+ }
+
+ public void dispose()
+ {
+ mTimerService.dispose();
+
+ mAuxChannelStates.clear();
+ mSquelchListeners.clear();
+ mChangeBroadcaster.dispose();
+ mChangeBroadcaster = null;
+
+ if( mProcessingChain.getChannel().getChannelType() == ChannelType.STANDARD )
+ {
+ mCallEventModel.dispose();
+ }
+ /* If we're a traffic channel, set the call event model to null so that
+ * we don't dispose the parent control channel's call event model */
+ else
+ {
+ mCallEventModel = null;
+ }
+
+ mMessageActivityModel.dispose();
+ mMessageActivityModel = null;
+
+ mAliasList = null;
+ mAudioTypeListener = null;
+ mCallEventModel = null;
+ mProcessingChain = null;
+ }
+
+ /**
+ * Channel Activity Summary - implemented by the sub-class.
+ */
+ public abstract String getActivitySummary();
+
+ public AliasList getAliasList()
+ {
+ return mAliasList;
+ }
+
+ public boolean hasAliasList()
+ {
+ return mAliasList != null;
+ }
+
+ public void broadcastChange( ChangedAttribute attribute )
+ {
+ if( mChangeBroadcaster != null )
+ {
+ mChangeBroadcaster.broadcast( attribute );
+ }
+ }
+
+ public void setCallFadeTimeout( long milliseconds )
+ {
+ mCallFadeTimeout = milliseconds;
+ }
+
+ public JTable getCallEventTable()
+ {
+ if( mCallEventTable == null )
+ {
+ mCallEventTable = new JTable( mCallEventModel );
+ mCallEventTable.setAutoCreateRowSorter( true );
+ mCallEventTable.setAutoResizeMode( JTable.AUTO_RESIZE_LAST_COLUMN );
+
+ CallEventAliasCellRenderer renderer =
+ new CallEventAliasCellRenderer( mProcessingChain
+ .getResourceManager().getSettingsManager() );
+
+ mCallEventTable.getColumnModel().getColumn( 3 )
+ .setCellRenderer( renderer );
+
+ mCallEventTable.getColumnModel().getColumn( 5 )
+ .setCellRenderer( renderer );
+ }
+
+ return mCallEventTable;
+ }
+
+ /**
+ * Returns the message activity model wrapped in a JTable. Although this
+ * may seem to violate separation between model and view, it simplifies
+ * retaining the JTable state for each channel by keeping a local reference
+ * to reuse
+ */
+ public JTable getMessageActivityTable()
+ {
+ if( mMessageActivityTable == null )
+ {
+ mMessageActivityTable = new JTable( mMessageActivityModel );
+ mMessageActivityTable.setAutoResizeMode( JTable.AUTO_RESIZE_LAST_COLUMN );
+ }
+
+ return mMessageActivityTable;
+ }
+
+ public void addAuxChannelState( AuxChannelState state )
+ {
+ mAuxChannelStates.add( state );
+ }
+
+ public ArrayList getAuxChannelStates()
+ {
+ return mAuxChannelStates;
+ }
+
+ public void receive( Message message )
+ {
+ if( mMessageActivityModel != null )
+ {
+ mMessageActivityModel.receive( message );
+ }
+
+ for( AuxChannelState state: mAuxChannelStates )
+ {
+ if( state != null )
+ {
+ state.receive( message );
+ }
+ }
+ }
+
+ public ProcessingChain getProcessingChain()
+ {
+ return mProcessingChain;
+ }
+
+ public State getState()
+ {
+ return mState;
+ }
+
+ public void setState( State state )
+ {
+ /* Let all CALL state changes go through in order to update the fade
+ * timer, otherwise, don't update the state if it hasn't changed */
+ if( mState != state || state == State.CALL )
+ {
+ if( mState.canChangeTo( state ) )
+ {
+ mState = state;
+
+ switch( state )
+ {
+ case CALL:
+ setSquelchState( SquelchState.UNSQUELCH );
+ case DATA:
+ mTimerService.stopResetTimer();
+ mTimerService.startFadeTimer();
+ break;
+ case NO_TUNER:
+ mTimerService.stopResetTimer();
+ if( getProcessingChain().getChannel()
+ .getChannelType() == ChannelType.TRAFFIC )
+ {
+ mTimerService.startFadeTimer();
+ }
+ break;
+ case FADE:
+ mTimerService.stopFadeTimer();
+ mTimerService.startResetTimer();
+ setSquelchState( SquelchState.SQUELCH );
+ if( mAudioTypeListener != null )
+ {
+ mAudioTypeListener.setAudioType( mAudioType );
+ }
+ break;
+ case END:
+ mTimerService.stopResetTimer();
+ mTimerService.stopFadeTimer();
+ setSquelchState( SquelchState.SQUELCH );
+ if( mAudioTypeListener != null )
+ {
+ mAudioTypeListener.setAudioType( mAudioType );
+ }
+
+ /* If this is a traffic channel, stop it */
+ if( mProcessingChain.getChannel()
+ .getChannelType() == ChannelType.TRAFFIC )
+ {
+ mProcessingChain.getChannel().setEnabled( false );
+ }
+ break;
+ case IDLE:
+ break;
+ default:
+ break;
+ }
+
+ broadcastChange( ChangedAttribute.CHANNEL_STATE );
+ }
+ else
+ {
+ Log.error( "Channel State - can't change from [" +
+ mState.toString() + "] to [" + state.toString() + "]" );
+ }
+ }
+ }
+
+ /**
+ * End method to reset the channel state once a call has completely
+ * ended and now you want to return the channel state to idle
+ */
+ protected void reset()
+ {
+ setState( State.END );
+
+ for( AuxChannelState state: mAuxChannelStates )
+ {
+ state.reset();
+ }
+
+ //Reset the state to idle
+ setState( State.IDLE );
+ }
+
+ /**
+ * Method to perform any call fade actions. On fade, you should change the
+ * channel state to fading, and construct a call-reset timer to execute a few
+ * seconds later. This allows the view to signal to the user that the call
+ * has faded/ended, and display faded information to the user, until the
+ * cleanup() task is run.
+ */
+ protected void fade( CallEventType type )
+ {
+ for( AuxChannelState state: mAuxChannelStates )
+ {
+ state.fade();
+ }
+
+ setState( State.FADE );
+ }
+
+ public void setListener( AudioTypeListener listener )
+ {
+ mAudioTypeListener = listener;
+ }
+
+ public void addListener( SquelchListener listener )
+ {
+ mSquelchListeners.add( listener );
+ }
+
+ public void removeListener( SquelchListener listener )
+ {
+ mSquelchListeners.remove( listener );
+ }
+
+ protected void setSquelchState( SquelchState state )
+ {
+ Iterator it = mSquelchListeners.iterator();
+
+ while( it.hasNext() )
+ {
+ it.next().setSquelch( state );
+ }
+ }
+
+ /**
+ * Timer service
+ */
+ public class TimerService extends ScheduledThreadPoolExecutor
+ {
+ private long mTimerMonitorRevisitRate = 100; //100 milliseconds
+
+ private ScheduledFuture> mTimerMonitor;
+
+ public TimerService()
+ {
+ super( 1 );
+ }
+
+ public void dispose()
+ {
+ if( mTimerMonitor != null )
+ {
+ mTimerMonitor.cancel( true );
+ }
+
+ this.shutdownNow();
+ }
+
+ private void checkMonitor()
+ {
+ boolean createTimer = false;
+
+ synchronized( mCallFadeLock )
+ {
+ if( mFadeTimeout != -1 )
+ {
+ createTimer = true;
+ }
+ }
+
+ synchronized( mCallResetLock )
+ {
+ if( mResetTimeout != -1 )
+ {
+ createTimer = true;
+ }
+ }
+
+ if( createTimer )
+ {
+ //Check to see if the timer is already running, if not, start it
+
+ if( mTimerMonitor == null || mTimerMonitor.isDone() )
+ {
+ scheduleAtFixedRate( new StateTimeoutMonitor(),
+ mTimerMonitorRevisitRate,
+ mTimerMonitorRevisitRate,
+ TimeUnit.MILLISECONDS );
+ }
+ }
+ else
+ {
+ if( mTimerMonitor != null )
+ {
+ mTimerMonitor.cancel( true );
+ }
+ }
+ }
+
+ public void startFadeTimer()
+ {
+ synchronized( mCallFadeLock )
+ {
+ mFadeTimeout = System.currentTimeMillis() + mCallFadeTimeout;
+ }
+
+ checkMonitor();
+ }
+
+ public void resetFadeTimer()
+ {
+ startFadeTimer();
+ }
+
+ public void stopFadeTimer()
+ {
+ synchronized( mCallFadeLock )
+ {
+ mFadeTimeout = -1;
+ }
+
+ checkMonitor();
+ }
+
+ public void startResetTimer()
+ {
+ synchronized( mCallResetLock )
+ {
+ mResetTimeout = System.currentTimeMillis() + mCallResetTimeout;
+ }
+
+ checkMonitor();
+ }
+
+ public void stopResetTimer()
+ {
+ synchronized( mCallResetLock )
+ {
+ mResetTimeout = -1;
+ }
+
+ checkMonitor();
+ }
+ }
+
+ public class StateTimeoutMonitor implements Runnable
+ {
+ @Override
+ public void run()
+ {
+ long now = System.currentTimeMillis();
+
+ synchronized( mCallFadeLock )
+ {
+ if( mFadeTimeout != -1 && now >= mFadeTimeout )
+ {
+ mFadeTimeout = -1;
+
+ fade( CallEventType.CALL_TIMEOUT );
+ }
+ }
+
+ synchronized( mCallResetLock )
+ {
+ if( mResetTimeout != -1 && now >= mResetTimeout )
+ {
+ mResetTimeout = -1;
+
+ reset();
+ }
+ }
+ }
+ }
+
+
+ public enum State
+ {
+ IDLE( "IDLE" ) {
+ @Override
+ public boolean canChangeTo( State state )
+ {
+ return state == IDLE ||
+ state == CALL ||
+ state == CONTROL ||
+ state == DATA ||
+ state == FADE ||
+ state == NO_TUNER;
+ }
+ },
+ CALL( "CALL" ) {
+ @Override
+ public boolean canChangeTo( State state )
+ {
+ return state == CALL ||
+ state == CONTROL ||
+ state == DATA ||
+ state == FADE;
+ }
+ },
+ DATA( "DATA" ) {
+ @Override
+ public boolean canChangeTo( State state )
+ {
+ return state == CALL ||
+ state == CONTROL ||
+ state == DATA ||
+ state == FADE;
+ }
+ },
+ CONTROL( "CONTROL" )
+ {
+ @Override
+ public boolean canChangeTo( State state )
+ {
+ return true;
+ }
+ },
+ FADE( "FADE" ) {
+ @Override
+ public boolean canChangeTo( State state )
+ {
+ return state != FADE; //All states except fade allowed
+ }
+ },
+ END( "END" ) {
+ @Override
+ public boolean canChangeTo( State state )
+ {
+ return state != FADE; //Fade is only disallowed state
+ }
+ },
+ NO_TUNER( "NO TUNER" ) {
+ @Override
+ public boolean canChangeTo( State state )
+ {
+ return state == FADE || state == END;
+ }
+ };
+
+ private String mDisplayValue;
+
+ private State( String displayValue )
+ {
+ mDisplayValue = displayValue;
+ }
+
+ public abstract boolean canChangeTo( State state );
+
+ public String getDisplayValue()
+ {
+ return mDisplayValue;
+ }
+ }
+
+ /**
+ * Registers a listener to receive channel state attribute changes
+ */
+ public void addListener( Listener listener )
+ {
+ mChangeBroadcaster.addListener( listener );
+ }
+
+ /**
+ * Removes a listener from receiving channel state attribute changes
+ */
+ public void removeListener( Listener listener )
+ {
+ mChangeBroadcaster.removeListener( listener );
+ }
+
+ public enum ChangedAttribute
+ {
+ AUDIO_TYPE,
+ CHANNEL_NUMBER,
+ CHANNEL_NAME,
+ CHANNEL_SITE_NUMBER,
+ CHANNEL_SITE_NUMBER_ALIAS,
+ CHANNEL_STATE,
+ FROM_TALKGROUP,
+ FROM_TALKGROUP_ALIAS,
+ FROM_TALKGROUP_TYPE,
+ MESSAGE,
+ MESSAGE_TYPE,
+ SITE_NAME,
+ SOURCE,
+ SYSTEM_NAME,
+ SQUELCH,
+ TO_TALKGROUP,
+ TO_TALKGROUP_ALIAS,
+ TO_TALKGROUP_TYPE;
+ }
+}
diff --git a/src/controller/state/ChannelStateList.java b/src/controller/state/ChannelStateList.java
new file mode 100644
index 00000000..6db9b475
--- /dev/null
+++ b/src/controller/state/ChannelStateList.java
@@ -0,0 +1,442 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.state;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Point;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.util.HashMap;
+
+import javax.swing.JMenu;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JSeparator;
+import javax.swing.SwingUtilities;
+
+import net.miginfocom.swing.MigLayout;
+import settings.ColorSetting.ColorSettingName;
+import settings.ColorSettingMenuItem;
+import settings.ColorSettingResetMenuItem;
+import settings.SettingsManager;
+import controller.channel.Channel;
+import controller.channel.Channel.ChannelEvent;
+import controller.channel.Channel.ChannelType;
+import controller.channel.ChannelListener;
+import decode.fleetsync2.FleetsyncChannelState;
+import decode.fleetsync2.FleetsyncPanel;
+import decode.ltrnet.LTRNetChannelState;
+import decode.ltrnet.LTRNetPanel;
+import decode.ltrstandard.LTRChannelState;
+import decode.ltrstandard.LTRPanel;
+import decode.mdc1200.MDCChannelState;
+import decode.mdc1200.MDCPanel;
+import decode.mpt1327.MPT1327ChannelState;
+import decode.mpt1327.MPT1327Panel;
+import decode.nbfm.NBFMChannelState;
+import decode.nbfm.NBFMPanel;
+import decode.passport.PassportChannelState;
+import decode.passport.PassportPanel;
+
+public class ChannelStateList extends JPanel implements ChannelListener
+{
+ private static final long serialVersionUID = 1L;
+
+ private HashMap mDisplayedPanels =
+ new HashMap();
+
+ private SettingsManager mSettingsManager;
+
+ public ChannelStateList( SettingsManager settingsManager )
+ {
+ mSettingsManager = settingsManager;
+
+ init();
+ }
+
+ private void init()
+ {
+ setLayout( new MigLayout( "insets 0 0 0 0", "[grow,fill]", "[]0[]") );
+ setBackground( Color.BLACK );
+
+ addMouseListener( new ListSelectionListener() );
+ }
+
+ @Override
+ public void occurred( Channel channel, ChannelEvent event )
+ {
+ ChannelType type = channel.getChannelType();
+
+ switch( event )
+ {
+ case PROCESSING_STARTED:
+ if( type == ChannelType.STANDARD )
+ {
+ channelStarted( channel );
+ }
+ else if( type == ChannelType.TRAFFIC )
+ {
+ for( Channel parent: mDisplayedPanels.keySet() )
+ {
+ if( parent.hasTrafficChannel( channel ) )
+ {
+ mDisplayedPanels.get( parent ).trafficChannelAdded( channel );
+ validate();
+ }
+ }
+ }
+ break;
+ case PROCESSING_STOPPED:
+ if( type == ChannelType.STANDARD )
+ {
+ channelStopped( channel );
+ }
+ /* Since we don't have a reference to the parent channel that
+ * owns the traffic channel, iterate all channels to find the
+ * parent */
+ else if( type == ChannelType.TRAFFIC )
+ {
+ for( Channel control: mDisplayedPanels.keySet() )
+ {
+ if( control.hasTrafficChannel( channel ) )
+ {
+ control.removeTrafficChannel( channel );
+
+ mDisplayedPanels.get( control ).trafficChannelDeleted( channel );
+
+ validate();
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ public void channelStarted( Channel channel )
+ {
+ if( !mDisplayedPanels.containsKey( channel ) )
+ {
+ ChannelState state = channel.getProcessingChain().getChannelState();
+
+ ChannelStatePanel panel = null;
+
+ if( state instanceof NBFMChannelState )
+ {
+ NBFMChannelState convState =
+ (NBFMChannelState)state;
+
+ panel = new NBFMPanel( channel );
+
+ for( AuxChannelState aux: convState.getAuxChannelStates() )
+ {
+ if( aux instanceof FleetsyncChannelState )
+ {
+ FleetsyncPanel fsPanel =
+ new FleetsyncPanel( mSettingsManager,
+ (FleetsyncChannelState)aux );
+
+ panel.addPanel( fsPanel );
+ }
+ else if( aux instanceof MDCChannelState )
+ {
+ MDCPanel mdsPanel = new MDCPanel( mSettingsManager,
+ (MDCChannelState)aux );
+
+ panel.addPanel( mdsPanel );
+ }
+ }
+ }
+ else if( state instanceof LTRChannelState )
+ {
+ LTRChannelState ltrState = (LTRChannelState)state;
+
+ panel = new LTRPanel( mSettingsManager, channel );
+
+ for( AuxChannelState aux: ltrState.getAuxChannelStates() )
+ {
+ if( aux instanceof FleetsyncChannelState )
+ {
+ FleetsyncPanel fsPanel = new FleetsyncPanel( mSettingsManager,
+ (FleetsyncChannelState)aux );
+
+ panel.addPanel( fsPanel );
+ }
+ }
+ }
+ else if( state instanceof LTRNetChannelState )
+ {
+ LTRNetChannelState ltrState = (LTRNetChannelState)state;
+
+ panel = new LTRNetPanel( channel );
+
+ for( AuxChannelState aux: ltrState.getAuxChannelStates() )
+ {
+ if( aux instanceof FleetsyncChannelState )
+ {
+ FleetsyncPanel fsPanel = new FleetsyncPanel( mSettingsManager,
+ (FleetsyncChannelState)aux );
+
+ panel.addPanel( fsPanel );
+ }
+ }
+ }
+ else if( state instanceof MPT1327ChannelState )
+ {
+ MPT1327ChannelState mptState = (MPT1327ChannelState)state;
+
+ panel = new MPT1327Panel( channel );
+ }
+ else if( state instanceof PassportChannelState )
+ {
+ PassportChannelState passportState = (PassportChannelState)state;
+
+ panel = new PassportPanel( mSettingsManager, channel );
+
+ for( AuxChannelState aux: passportState.getAuxChannelStates() )
+ {
+ if( aux instanceof FleetsyncChannelState )
+ {
+ FleetsyncPanel fsPanel = new FleetsyncPanel( mSettingsManager,
+ (FleetsyncChannelState)aux );
+
+ panel.addPanel( fsPanel );
+ }
+ }
+ }
+
+ if( panel != null )
+ {
+ add( panel, "wrap" );
+
+ mDisplayedPanels.put( channel, panel );
+
+ validate();
+ }
+ }
+ }
+
+ public void channelStopped( Channel channel )
+ {
+ if( mDisplayedPanels.containsKey( channel ) )
+ {
+ ChannelStatePanel panel = mDisplayedPanels.get( channel );
+
+ if( panel != null )
+ {
+ remove( panel );
+
+ validate();
+ }
+
+ mDisplayedPanels.remove( channel );
+ }
+ }
+
+ public void setSelectedChannel( ChannelStatePanel selectedPanel )
+ {
+ boolean current = selectedPanel.getSelected();
+
+ for( ChannelStatePanel panel: mDisplayedPanels.values() )
+ {
+ //Turn selected off
+ panel.setSelected( false );
+ }
+
+ selectedPanel.setSelected( !current );
+ }
+
+ public class ListSelectionListener implements MouseListener
+ {
+ @Override
+ public void mouseClicked( MouseEvent e )
+ {
+ Component component = getComponentAt( e.getPoint() );
+
+ if( component instanceof ChannelStatePanel )
+ {
+ ChannelStatePanel panel = (ChannelStatePanel)component;
+
+ /* If the panel has child traffic channels, attempt to get
+ * one of them for the mouse event */
+ if( panel.getChannel().getTrafficChannels().size() > 0 )
+ {
+ for( ChannelStatePanel trafficPanel: panel.getTrafficPanels().values() )
+ {
+ Point translatedPoint = SwingUtilities
+ .convertPoint( e.getComponent(), e.getPoint(),
+ trafficPanel );
+
+ if( trafficPanel.contains( translatedPoint ) )
+ {
+ panel = trafficPanel;
+ break;
+ }
+ }
+ }
+
+ if( e.getButton() == MouseEvent.BUTTON1 )
+ {
+ setSelectedChannel( panel );
+ }
+ else if( e.getButton() == MouseEvent.BUTTON3 )
+ {
+ JPopupMenu popupMenu = new JPopupMenu();
+ JMenu menu = panel.getChannel().getContextMenu();
+
+ popupMenu.add( menu );
+
+ JMenu colorMenu = new JMenu( "Color" );
+ popupMenu.add( colorMenu );
+
+ JMenu backgroundMenu = new JMenu( "Background" );
+ colorMenu.add( backgroundMenu );
+
+ backgroundMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_BACKGROUND ) );
+ backgroundMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_BACKGROUND ) );
+
+ JMenu channelsMenu = new JMenu( "Channel State" );
+ colorMenu.add( channelsMenu );
+
+ JMenu callMenu = new JMenu( "Call" );
+ channelsMenu.add( callMenu );
+
+ callMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CALL ) );
+ callMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CALL ) );
+ callMenu.add( new JSeparator() );
+
+ callMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CALL ) );
+ callMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CALL ) );
+
+ JMenu controlMenu = new JMenu( "Control" );
+ channelsMenu.add( controlMenu );
+
+ controlMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CONTROL ) );
+ controlMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CONTROL ) );
+ controlMenu.add( new JSeparator() );
+
+ controlMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CONTROL ) );
+ controlMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CONTROL ) );
+
+ JMenu dataMenu = new JMenu( "Data" );
+ channelsMenu.add( dataMenu );
+
+ dataMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_DATA ) );
+ dataMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_DATA ) );
+ dataMenu.add( new JSeparator() );
+
+ dataMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_DATA ) );
+ dataMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_DATA ) );
+
+ JMenu fadeMenu = new JMenu( "Fade" );
+ channelsMenu.add( fadeMenu );
+
+ fadeMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_FADE ) );
+ fadeMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_FADE ) );
+ fadeMenu.add( new JSeparator() );
+
+ fadeMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_FADE ) );
+ fadeMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_FADE ) );
+
+ JMenu idleMenu = new JMenu( "Idle" );
+ channelsMenu.add( idleMenu );
+
+ idleMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_IDLE ) );
+ idleMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_IDLE ) );
+ idleMenu.add( new JSeparator() );
+
+ idleMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_IDLE ) );
+ idleMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_IDLE ) );
+
+ JMenu noTunerMenu = new JMenu( "No Tuner" );
+ channelsMenu.add( noTunerMenu );
+
+ noTunerMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ) );
+ noTunerMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ) );
+ noTunerMenu.add( new JSeparator() );
+
+ noTunerMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ) );
+ noTunerMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ) );
+
+ JMenu labelMenu = new JMenu( "Labels" );
+ colorMenu.add( labelMenu );
+
+ labelMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER ) );
+ labelMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER ) );
+ labelMenu.add( new JSeparator() );
+
+ labelMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_LABEL_DECODER ) );
+ labelMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_LABEL_DECODER ) );
+ labelMenu.add( new JSeparator() );
+
+ labelMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_LABEL_DETAILS ) );
+ labelMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_LABEL_DETAILS ) );
+
+ JMenu selectedMenu = new JMenu( "Selected" );
+ colorMenu.add( selectedMenu );
+
+ selectedMenu.add( new ColorSettingMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_SELECTED_CHANNEL ) );
+ selectedMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
+ ColorSettingName.CHANNEL_STATE_SELECTED_CHANNEL ) );
+
+ popupMenu.show( ChannelStateList.this, e.getX(), e.getY() );
+ }
+ }
+ }
+
+ public void mousePressed( MouseEvent e ) {}
+ public void mouseReleased( MouseEvent e ) {}
+ public void mouseEntered( MouseEvent e ) {}
+ public void mouseExited( MouseEvent e ) {}
+ }
+}
diff --git a/src/controller/state/ChannelStatePanel.java b/src/controller/state/ChannelStatePanel.java
new file mode 100644
index 00000000..9937fc5f
--- /dev/null
+++ b/src/controller/state/ChannelStatePanel.java
@@ -0,0 +1,386 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.state;
+
+import java.awt.Color;
+import java.awt.EventQueue;
+import java.awt.Font;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.util.HashMap;
+
+import javax.swing.JMenu;
+import javax.swing.JPanel;
+
+import log.Log;
+import net.miginfocom.swing.MigLayout;
+import sample.Listener;
+import settings.ColorSetting;
+import settings.ColorSetting.ColorSettingName;
+import settings.Setting;
+import settings.SettingChangeListener;
+import settings.SettingsManager;
+import controller.channel.Channel;
+import controller.state.ChannelState.ChangedAttribute;
+import controller.state.ChannelState.State;
+
+public abstract class ChannelStatePanel extends JPanel
+ implements Listener,
+ SettingChangeListener
+{
+ private static final long serialVersionUID = 1L;
+
+ protected Font mFontDetails = new Font( Font.MONOSPACED, Font.PLAIN, 10 );
+ protected Font mFontDecoder = new Font( Font.MONOSPACED, Font.PLAIN, 10 );
+ protected Font mFontAuxDecoder = new Font( Font.MONOSPACED, Font.PLAIN, 10 );
+
+ protected Color mColorChannelBackground;
+ protected Color mColorChannelSelected;
+ protected Color mColorTopCall;
+ protected Color mColorMiddleCall;
+ protected Color mColorTopControl;
+ protected Color mColorMiddleControl;
+ protected Color mColorTopData;
+ protected Color mColorMiddleData;
+ protected Color mColorTopFade;
+ protected Color mColorMiddleFade;
+ protected Color mColorTopIdle;
+ protected Color mColorMiddleIdle;
+ protected Color mColorTopNoTuner;
+ protected Color mColorMiddleNoTuner;
+ protected Color mColorLabelDetails;
+ protected Color mColorLabelDecoder;
+ protected Color mColorLabelAuxDecoder;
+
+ private HashMap mTrafficPanels =
+ new HashMap();
+
+ protected Channel mChannel;
+
+ public ChannelStatePanel( Channel channel )
+ {
+ mChannel = channel;
+
+ /* Register to receive settings updates */
+ getSettingsManager().addListener( this );
+
+ /* Register to receive channel updates */
+ mChannel.getProcessingChain().getChannelState().addListener( this );
+
+ getColors();
+
+ setLayout( new MigLayout( "insets 3 2 2 2", "[grow,fill]", "[]0[]0[]") );
+ }
+
+ public SettingsManager getSettingsManager()
+ {
+ return mChannel.getResourceManager().getSettingsManager();
+ }
+
+ public void dispose()
+ {
+ mTrafficPanels.clear();
+ mChannel = null;
+ }
+
+ private void getColors()
+ {
+ mColorChannelBackground = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_BACKGROUND ).getColor();
+ mColorChannelSelected = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_SELECTED_CHANNEL ).getColor();
+ mColorTopCall = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CALL ).getColor();
+ mColorMiddleCall = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CALL ).getColor();
+ mColorTopControl = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CONTROL ).getColor();
+ mColorMiddleControl = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CONTROL ).getColor();
+ mColorTopData = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_DATA ).getColor();
+ mColorMiddleData = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_DATA ).getColor();
+ mColorTopFade = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_FADE ).getColor();
+ mColorMiddleFade = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_FADE ).getColor();
+ mColorTopIdle = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_IDLE ).getColor();
+ mColorMiddleIdle = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_IDLE ).getColor();
+ mColorTopNoTuner = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ).getColor();
+ mColorMiddleNoTuner = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ).getColor();
+
+ mColorLabelDetails = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_LABEL_DETAILS ).getColor();
+ mColorLabelDecoder = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_LABEL_DECODER ).getColor();
+ mColorLabelAuxDecoder = getSettingsManager().getColorSetting(
+ ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER ).getColor();
+ }
+
+ public HashMap getTrafficPanels()
+ {
+ return mTrafficPanels;
+ }
+
+ public JMenu getContextMenu()
+ {
+ return mChannel.getContextMenu();
+ }
+
+ public Channel getChannel()
+ {
+ return mChannel;
+ }
+
+ public ChannelState getChannelState()
+ {
+ if( mChannel.getProcessingChain() != null )
+ {
+ return mChannel.getProcessingChain().getChannelState();
+ }
+
+ return null;
+ }
+
+ public boolean getSelected()
+ {
+ return mChannel.getSelected();
+ }
+
+ public void addPanel( final JPanel panel )
+ {
+ EventQueue.invokeLater( new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ add( panel, "span,wrap" );
+ validate();
+ }
+ } );
+ }
+
+ public void removePanel( final JPanel panel )
+ {
+ EventQueue.invokeLater( new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ remove( panel );
+
+ validate();
+
+ getParent().validate();
+
+ if( panel instanceof ChannelStatePanel )
+ {
+ ((ChannelStatePanel)panel).dispose();
+ }
+ else if( panel instanceof AuxStatePanel )
+ {
+ ((AuxStatePanel)panel).dispose();
+ }
+ }
+ } );
+ }
+
+ public void trafficChannelAdded( Channel traffic )
+ {
+ //Disregard -- sub class can override and implement
+ }
+
+ public void trafficChannelDeleted( Channel traffic )
+ {
+ ChannelStatePanel panelToRemove =
+ getTrafficPanels().remove( traffic );
+
+ removePanel( panelToRemove );
+ }
+
+ public void setSelected( boolean selected )
+ {
+ mChannel.setSelected( selected );
+
+ for( ChannelStatePanel traffic: getTrafficPanels().values() )
+ {
+ traffic.setSelected( false );
+ }
+
+ repaint();
+ }
+
+ private GradientPaint getGradient( Color top, Color middle )
+ {
+ return new GradientPaint( 0f, -50.0f, top,
+ 0f, (float)getHeight() / 2.2f, middle, true );
+ }
+
+ @Override
+ protected void paintComponent(Graphics g)
+ {
+ super.paintComponent( g );
+
+ setBackground( mColorChannelBackground );
+
+ Graphics2D g2 = (Graphics2D)g.create();
+
+ Paint p = null;
+
+ ChannelState channelState = getChannelState();
+
+ if( channelState != null )
+ {
+ State state = channelState.getState();
+
+ if( state != null )
+ {
+ switch( state )
+ {
+ case CALL:
+ p = getGradient( mColorTopCall, mColorMiddleCall );
+ break;
+ case CONTROL:
+ p = getGradient( mColorTopControl, mColorMiddleControl );
+ break;
+ case DATA:
+ p = getGradient( mColorTopData, mColorMiddleData );
+ break;
+ case FADE:
+ p = getGradient( mColorTopFade, mColorMiddleFade );
+ break;
+ case NO_TUNER:
+ p = getGradient( mColorTopNoTuner, mColorMiddleNoTuner );
+ break;
+ case END:
+ case IDLE:
+ default:
+ p = getGradient( mColorTopIdle, mColorMiddleIdle );
+ break;
+ }
+ }
+ }
+
+ g2.setPaint( p );
+ g2.fillRect( 0, 0, getWidth(), getHeight() );
+
+ /* Draw bottom separator line */
+ g2.setColor( Color.LIGHT_GRAY );
+ g2.drawLine( 0, getHeight() - 1, getWidth(), getHeight() - 1 );
+
+ /* Draw channel selected box */
+ if( mChannel.getSelected() )
+ {
+ g2.setColor( mColorChannelSelected );
+ g2.drawRect( 1, 1, getWidth() - 2, getHeight() - 2 );
+ }
+
+ g2.dispose();
+ }
+
+ @Override
+ public void settingChanged( Setting setting )
+ {
+ if( setting instanceof ColorSetting )
+ {
+ ColorSetting colorSetting = (ColorSetting)setting;
+
+ switch( colorSetting.getColorSettingName() )
+ {
+ case CHANNEL_STATE_BACKGROUND:
+ mColorChannelBackground = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_GRADIENT_MIDDLE_CALL:
+ mColorMiddleCall = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_GRADIENT_TOP_CALL:
+ mColorTopCall = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_GRADIENT_MIDDLE_CONTROL:
+ mColorMiddleControl = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_GRADIENT_TOP_CONTROL:
+ mColorTopControl = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_GRADIENT_MIDDLE_DATA:
+ mColorMiddleData = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_GRADIENT_TOP_DATA:
+ mColorTopData = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_GRADIENT_MIDDLE_FADE:
+ mColorMiddleFade = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_GRADIENT_TOP_FADE:
+ mColorTopFade = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_GRADIENT_MIDDLE_IDLE:
+ mColorMiddleIdle = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_GRADIENT_TOP_IDLE:
+ mColorTopIdle = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER:
+ mColorMiddleNoTuner = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_GRADIENT_TOP_NO_TUNER:
+ mColorTopNoTuner = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_LABEL_AUX_DECODER:
+ mColorLabelAuxDecoder = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_LABEL_DECODER:
+ mColorLabelDecoder = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_LABEL_DETAILS:
+ mColorLabelDetails = colorSetting.getColor();
+ repaint();
+ break;
+ case CHANNEL_STATE_SELECTED_CHANNEL:
+ mColorChannelSelected = colorSetting.getColor();
+ repaint();
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void settingDeleted( Setting setting ) {}
+}
diff --git a/src/controller/state/ChannelStateSelectionListener.java b/src/controller/state/ChannelStateSelectionListener.java
new file mode 100644
index 00000000..2e4c611a
--- /dev/null
+++ b/src/controller/state/ChannelStateSelectionListener.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.state;
+
+
+public interface ChannelStateSelectionListener
+{
+ public void selected( ChannelState channelState );
+}
diff --git a/src/controller/system/System.java b/src/controller/system/System.java
new file mode 100644
index 00000000..e88432f9
--- /dev/null
+++ b/src/controller/system/System.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.system;
+
+import java.util.ArrayList;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+
+import controller.site.Site;
+
+@XmlSeeAlso( { Site.class } )
+@XmlRootElement( name = "system" )
+public class System
+{
+ private String mName;
+ private ArrayList mSite = new ArrayList();
+
+ public System()
+ {
+ this( "New System" );
+ }
+
+ public System( String name )
+ {
+ mName = name;
+ }
+
+ @XmlAttribute
+ public String getName()
+ {
+ return mName;
+ }
+
+ public void setName( String name )
+ {
+ mName = name;
+ }
+
+ public ArrayList getSite()
+ {
+ return mSite;
+ }
+
+ public void setSite( ArrayList sites )
+ {
+ mSite = sites;
+ }
+
+ public void addSite( Site site )
+ {
+ mSite.add( site );
+ }
+
+ public void removeSite( Site site )
+ {
+ mSite.remove( site );
+ }
+
+ public void clearSites()
+ {
+ mSite.clear();
+ }
+
+ public String toString()
+ {
+ return mName;
+ }
+}
diff --git a/src/controller/system/SystemEditor.java b/src/controller/system/SystemEditor.java
new file mode 100644
index 00000000..f9c4e8b4
--- /dev/null
+++ b/src/controller/system/SystemEditor.java
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.system;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.tree.TreePath;
+
+import net.miginfocom.swing.MigLayout;
+
+public class SystemEditor extends JPanel implements ActionListener
+{
+ private static final long serialVersionUID = 1L;
+ private SystemNode mSystemNode;
+
+ private JLabel mLabelSystem;
+ private JTextField mTextSystem;
+
+ public SystemEditor( SystemNode system )
+ {
+ mSystemNode = system;
+ initGUI();
+ }
+
+ private void initGUI()
+ {
+ setLayout( new MigLayout() );
+
+ setBorder( BorderFactory.createTitledBorder( "System" ) );
+
+ mLabelSystem = new JLabel( "Name:" );
+ add( mLabelSystem, "align right" );
+
+ mTextSystem = new JTextField( mSystemNode.getSystem().getName() );
+ add( mTextSystem, "grow, wrap" );
+
+ JButton btnSave = new JButton( "Save" );
+ btnSave.addActionListener( SystemEditor.this );
+ add( btnSave );
+
+ JButton btnReset = new JButton( "Reset" );
+ btnReset.addActionListener( SystemEditor.this );
+ add( btnReset, "wrap" );
+ }
+
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ String command = e.getActionCommand();
+
+ if( command.contentEquals( "Save" ) )
+ {
+ boolean expanded = mSystemNode.getModel().getTree()
+ .isExpanded( new TreePath( mSystemNode ) );
+
+ String name = mTextSystem.getText();
+
+ if( name != null )
+ {
+ mSystemNode.getSystem().setName( name );
+ mSystemNode.save();
+ mSystemNode.show();
+
+ if( expanded )
+ {
+ mSystemNode.getModel().getTree().expandPath( new TreePath( mSystemNode ) );
+ }
+ }
+ else
+ {
+ JOptionPane.showMessageDialog( SystemEditor.this, "Please enter a system name" );
+ }
+ }
+ else if( command.contentEquals( "Reset" ) )
+ {
+ mTextSystem.setText( mSystemNode.getSystem().getName() );
+ }
+ }
+}
diff --git a/src/controller/system/SystemList.java b/src/controller/system/SystemList.java
new file mode 100644
index 00000000..91fcacec
--- /dev/null
+++ b/src/controller/system/SystemList.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.system;
+
+import java.util.ArrayList;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+
+@XmlSeeAlso( { controller.system.System.class } )
+
+@XmlRootElement( name = "system_list" )
+public class SystemList
+{
+ private ArrayList mSystem = new ArrayList();
+
+ public SystemList()
+ {
+ }
+
+ public ArrayList getSystem()
+ {
+ return mSystem;
+ }
+
+ public void setSystem( ArrayList systems )
+ {
+ mSystem = systems;
+ }
+
+ public void addSystem( System system )
+ {
+ mSystem.add( system );
+ }
+
+ public void removeSystem( System system )
+ {
+ mSystem.remove( system );
+ }
+
+ public void clearSystems()
+ {
+ mSystem.clear();
+ }
+}
diff --git a/src/controller/system/SystemListNode.java b/src/controller/system/SystemListNode.java
new file mode 100644
index 00000000..4629133d
--- /dev/null
+++ b/src/controller/system/SystemListNode.java
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.system;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JMenuItem;
+import javax.swing.JPopupMenu;
+
+import controller.ConfigurableNode;
+
+public class SystemListNode extends ConfigurableNode
+{
+ private static final long serialVersionUID = 1L;
+
+ public SystemListNode( SystemList list )
+ {
+ super( list );
+ }
+
+ public SystemList getSystemList()
+ {
+ return (SystemList)getUserObject();
+ }
+
+ public void init()
+ {
+ for( System system: getSystemList().getSystem() )
+ {
+ SystemNode node = new SystemNode( system );
+
+ getModel().addNode( node, SystemListNode.this, getChildCount() );
+
+ node.init();
+ }
+
+ sort();
+ }
+
+ public String toString()
+ {
+ return "Systems [" + this.getChildCount() + "]";
+ }
+
+ public JPopupMenu getContextMenu()
+ {
+ JPopupMenu retVal = new JPopupMenu();
+
+ JMenuItem addSystemItem = new JMenuItem( "Add System" );
+ addSystemItem.addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ System system = new System();
+
+ getSystemList().addSystem( system );
+
+ getModel().addNode( new SystemNode( system ),
+ SystemListNode.this,
+ SystemListNode.this.getChildCount() );
+ }
+ } );
+
+ retVal.add( addSystemItem );
+
+ return retVal;
+ }
+}
diff --git a/src/controller/system/SystemNode.java b/src/controller/system/SystemNode.java
new file mode 100644
index 00000000..7452c0cd
--- /dev/null
+++ b/src/controller/system/SystemNode.java
@@ -0,0 +1,165 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package controller.system;
+
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Enumeration;
+
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JSeparator;
+import javax.swing.tree.DefaultMutableTreeNode;
+
+import controller.ConfigurableNode;
+import controller.site.Site;
+import controller.site.SiteNode;
+
+public class SystemNode extends ConfigurableNode
+{
+ private static final long serialVersionUID = 1L;
+
+ public SystemNode( System system )
+ {
+ super( system );
+ }
+
+ public void init()
+ {
+ for( Site site: getSystem().getSite() )
+ {
+ SiteNode node = new SiteNode( site );
+
+ getModel().insertNodeInto( node, SystemNode.this, getChildCount() );
+
+ node.init();
+ }
+
+ sort();
+ }
+
+ @Override
+ public String getIconPath()
+ {
+ String retVal = "images/system.png";
+
+ @SuppressWarnings( "unchecked" )
+ Enumeration nodes = children();
+
+ while( nodes.hasMoreElements() )
+ {
+ SiteNode child = (SiteNode)nodes.nextElement();
+
+ if( child.getIconPath().contains( "green.png" ) )
+ {
+ return "images/system_green.png";
+ }
+ else if( child.getIconPath().contains( "red.png" ) )
+ {
+ retVal = "images/system_red.png";
+ }
+ }
+
+ return retVal;
+ }
+
+ @Override
+ public JPanel getEditor()
+ {
+ return new SystemEditor( this );
+ }
+
+ public System getSystem()
+ {
+ return (System)getUserObject();
+ }
+
+ public String toString()
+ {
+ return getSystem().getName();
+ }
+
+ public void delete()
+ {
+ for( int x = 0; x < getChildCount(); x++ )
+ {
+ ((SiteNode)getChildAt( x )).delete();
+ }
+
+ SystemListNode parent = (SystemListNode)getParent();
+
+ parent.getSystemList().removeSystem( getSystem() );
+
+ save();
+
+ getModel().removeNodeFromParent( SystemNode.this );
+
+ /* Show the first child, if one exists so that we don't have a
+ * collapsed list */
+ if( parent.getChildCount() > 0 )
+ {
+ ((ConfigurableNode)parent.getFirstChild()).show();
+ }
+ }
+
+ public JPopupMenu getContextMenu()
+ {
+ JPopupMenu retVal = new JPopupMenu();
+
+ JMenuItem addSiteItem = new JMenuItem( "Add Site" );
+ addSiteItem.addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ Site site = new Site();
+ getSystem().addSite( site );
+
+ SiteNode node = new SiteNode( site );
+
+ getModel().addNode( node,
+ SystemNode.this,
+ SystemNode.this.getChildCount() );
+
+ sort();
+
+ node.show();
+ }
+ } );
+
+ retVal.add( addSiteItem );
+
+ retVal.add( new JSeparator() );
+
+ JMenuItem deleteItem = new JMenuItem( "Delete" );
+ deleteItem.addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ delete();
+ }
+ } );
+
+ retVal.add( deleteItem );
+
+ return retVal;
+ }
+}
diff --git a/src/crc/CRC.java b/src/crc/CRC.java
new file mode 100644
index 00000000..509081f2
--- /dev/null
+++ b/src/crc/CRC.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package crc;
+
+public enum CRC
+{
+ PASSED ( "*", "Pass" ),
+ PASSED_INV ( "#", "Pass Invert" ),
+ FAILED_CRC ( "f", "Fail CRC" ),
+ FAILED_PARITY( "p", "Fail Parity" ),
+ CORRECTED ( "C", "Corrected" ),
+ UNKNOWN ( "-", "Unknown");
+
+ private String mAbbreviation;
+ private String mDisplayText;
+
+ CRC( String abbreviation, String displayText )
+ {
+ mAbbreviation = abbreviation;
+ mDisplayText = displayText;
+ }
+
+ public String getAbbreviation()
+ {
+ return mAbbreviation;
+ }
+
+ public String getDisplayText()
+ {
+ return mDisplayText;
+ }
+
+ public static String format( CRC[] checks )
+ {
+ StringBuilder sb = new StringBuilder();
+
+ for( int x = 0; x < checks.length; x++ )
+ {
+ CRC check = checks[ x ];
+
+ if( check != null )
+ {
+ sb.append( check.getAbbreviation() );
+ }
+ else
+ {
+ sb.append( CRC.UNKNOWN.getAbbreviation() );
+ }
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/src/crc/CRCFleetsync.java b/src/crc/CRCFleetsync.java
new file mode 100644
index 00000000..742bf682
--- /dev/null
+++ b/src/crc/CRCFleetsync.java
@@ -0,0 +1,207 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package crc;
+
+import java.util.BitSet;
+
+/**
+ * Fleetsync CRC checksum utility
+ *
+ * Fleetsync message blocks are 64 bits in length as follows:
+ * 0 - 47: message bits
+ * 48 - 62: CRC-15 check bits
+ * 63: even parity bit
+ *
+ * Fleetsync uses a CRC-15 (0x6815) with an initial fill of 0x0001, coupled with
+ * an even parity checkbit.
+ *
+ * CRC-15 Generating Polynomial: x15 + x14 + x13 + x11 + x4 + x2 + 1 (0x6815)
+ */
+public class CRCFleetsync
+{
+ private static short[] sCHECKSUMS = new short[] {
+ 0x740A, // 111010000001010 Bit 0
+ 0x3A05, // 011101000000101 Bit 1
+ 0x6908, // 110100100001000 Bit 2
+ 0x3484, // 011010010000100 Bit 3
+ 0x1A42, // 001101001000010 Bit 4
+ 0x0D21, // 000110100100001 Bit 5
+ 0x729A, // 111001010011010 Bit 6
+ 0x394D, // 011100101001101 Bit 7
+ 0x68AC, // 110100010101100 Bit 8
+ 0x3456, // 011010001010110 Bit 9
+ 0x1A2B, // 001101000101011 Bit 10
+ 0x791F, // 111100100011111 Bit 11
+ 0x4885, // 100100010000101 Bit 12
+ 0x5048, // 101000001001000 Bit 13
+ 0x2824, // 010100000100100 Bit 14
+ 0x1412, // 001010000010010 Bit 15
+ 0x0A09, // 000101000001001 Bit 16
+ 0x710E, // 111000100001110 Bit 17
+ 0x3887, // 011100010000111 Bit 18
+ 0x6849, // 110100001001001 Bit 19
+ 0x402E, // 100000000101110 Bit 20
+ 0x2017, // 010000000010111 Bit 21
+ 0x6401, // 110010000000001 Bit 22
+ 0x460A, // 100011000001010 Bit 23
+ 0x2305, // 010001100000101 Bit 24
+ 0x6588, // 110010110001000 Bit 25
+ 0x32C4, // 011001011000100 Bit 26
+ 0x1962, // 001100101100010 Bit 27
+ 0x0CB1, // 000110010110001 Bit 28
+ 0x7252, // 111001001010010 Bit 29
+ 0x3929, // 011100100101001 Bit 30
+ 0x689E, // 110100010011110 Bit 31
+ 0x344F, // 011010001001111 Bit 32
+ 0x6E2D, // 110111000101101 Bit 33
+ 0x431C, // 100001100011100 Bit 34
+ 0x218E, // 010000110001110 Bit 35
+ 0x10C7, // 001000011000111 Bit 36
+ 0x7C69, // 111110001101001 Bit 37
+ 0x4A3E, // 100101000111110 Bit 38
+ 0x251F, // 010010100011111 Bit 39
+ 0x6685, // 110011010000101 Bit 40
+ 0x4748, // 100011101001000 Bit 41
+ 0x23A4, // 010001110100100 Bit 42
+ 0x11D2, // 001000111010010 Bit 43
+ 0x08E9, // 000100011101001 Bit 44
+ 0x707E, // 111000001111110 Bit 45
+ 0x383F, // 011100000111111 Bit 46
+ 0x6815, // 110100000010101 Bit 47
+
+ //Single bit errors in the CRC Checksum
+ 0x4000, //Bit 48
+ 0x2000, //Bit 49
+ 0x1000, //Bit 50
+ 0x0800, //Bit 51
+ 0x0400, //Bit 52
+ 0x0200, //Bit 53
+ 0x0100, //Bit 54
+ 0x0080, //Bit 55
+ 0x0040, //Bit 56
+ 0x0020, //Bit 57
+ 0x0010, //Bit 58
+ 0x0008, //Bit 59
+ 0x0004, //Bit 60
+ 0x0002, //Bit 61
+ 0x0001 //Bit 62
+ };
+
+ /**
+ * Determines if message bits 0 - 47 pass the Fleetsync CRC checksum
+ * contained in bits 48 - 63, using a lookup table of CRC checksum values
+ * derived from the CRC-15 value, and verifies the message has even parity
+ */
+ public static CRC check( BitSet msg )
+ {
+ CRC crc = CRC.UNKNOWN;
+
+ int calculated = 1; //Starting value
+
+ //Check even parity
+ if( msg.cardinality() % 2 == 0 )
+ {
+ //Iterate bits that are set and XOR running checksum with lookup value
+ for (int i = msg.nextSetBit( 0 ); i >= 0 && i < 48; i = msg.nextSetBit( i+1 ) )
+ {
+ calculated ^= sCHECKSUMS[ i ];
+ }
+
+ if( calculated == getChecksum( msg ) )
+ {
+ crc = CRC.PASSED;
+ }
+ else
+ {
+ crc = CRC.FAILED_CRC;
+ }
+ }
+ else
+ {
+ crc = CRC.FAILED_PARITY;
+ }
+
+ return crc;
+ }
+
+ /**
+ * Returns the integer value of the 15 bit crc checksum
+ */
+ public static int getChecksum( BitSet msg )
+ {
+ int retVal = 0;
+
+ for( int x = 0; x < 15; x++ )
+ {
+ if( msg.get( x + 48 ) )
+ {
+ retVal += 1<<( 14 - x );
+ }
+ }
+
+ return retVal;
+ }
+
+ /**
+ * Determines the errant bit positions and returns them in an array of
+ * integer bit positions.
+ *
+ * Note: currently only detects single-bit errors
+ *
+ * @param msg to be checked for errors
+ * @return - array of integer positions of bits that need flipped
+ */
+ public static int[] findBitErrors( BitSet msg )
+ {
+ int[] retVal = null;
+
+ int checksum = getChecksum( msg );
+
+ //Remove the initial fill value (1)
+ checksum ^= 1;
+
+ //Iterate set message bits, removing their respective checksum value
+ //from the transmitted checksum, to arrive at the remainder
+ for (int i = msg.nextSetBit( 0 ); i >= 0 && i < 48; i = msg.nextSetBit( i+1 ) )
+ {
+ checksum ^= sCHECKSUMS[ i ];
+ }
+
+ //If at this point the checksum is 0, then we have a parity bit error
+ if( checksum == 0 )
+ {
+ retVal = new int[ 1 ];
+ retVal[ 0 ] = 63;
+ }
+ //Otherwise, try to lookup the syndrome for a single bit error
+ else
+ {
+ for( int x = 0; x < 63; x++ )
+ {
+ if( checksum == sCHECKSUMS[ x ] )
+ {
+ //return this bit position
+ retVal = new int[ 1 ];
+ retVal[ 0 ] = x;
+ }
+ }
+ }
+
+ return retVal;
+ }
+}
diff --git a/src/crc/CRCLTR.java b/src/crc/CRCLTR.java
new file mode 100644
index 00000000..34bc07da
--- /dev/null
+++ b/src/crc/CRCLTR.java
@@ -0,0 +1,185 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package crc;
+
+import java.util.BitSet;
+
+import message.MessageDirection;
+
+/**
+ * LTR CRC checksum utility
+ *
+ * LTR message blocks are 41 bits in length as follows:
+ * 0 - 9: Sync
+ * 10 - 34: message bits
+ * 35 - 41: CRC-7 check bits
+ *
+ * LTR uses a CRC-7 (0xFD) with an initial fill of 0x00.
+ *
+ * CRC-7 Generating Polynomial: x7 + x6 + x5 + x4 + x3 + x2 + 1 (0xFD)
+ */
+public class CRCLTR
+{
+ private static byte[] sCHECKSUMS = new byte[]
+ {
+ 0x38, //Area
+ 0x1C, //Channel 4
+ 0x0E, //Channel 3
+ 0x46, //Channel 2
+ 0x23, //Channel 1
+ 0x51, //Channel 0
+ 0x68, //Home 4
+ 0x75, //Home 3
+ 0x7A, //Home 2
+ 0x3D, //Home 1
+ 0x1F, //Home 0
+ 0x4F, //Group 7
+ 0x26, //Group 6
+ 0x52, //Group 5
+ 0x29, //Group 4
+ 0x15, //Group 3
+ 0x0B, //Group 2
+ 0x45, //Group 1
+ 0x62, //Group 0
+ 0x31, //Free 4
+ 0x19, //Free 3
+ 0x0D, //Free 2
+ 0x07, //Free 1
+ 0x43 //Free 0
+ };
+
+ /**
+ * Determines if message bits 10 - 34 pass the LTR CRC checksum
+ * contained in bits 35 - 41, using a lookup table of CRC checksum values
+ * derived from the CRC-7 value.
+ */
+ public static CRC check( BitSet msg, MessageDirection direction )
+ {
+ CRC crc = CRC.UNKNOWN;
+
+ int calculated = getCalculatedChecksum( msg );
+ int transmitted = getTransmittedChecksum( msg );
+
+ /* Normal CRC only for Outbound Status Word */
+ if( direction == MessageDirection.OSW )
+ {
+ if( calculated == transmitted )
+ {
+ return CRC.PASSED;
+ }
+ }
+ /* Normal or Inverted CRC for Inbound Status Word */
+ else
+ {
+ if( ( calculated ^ 127 ) == transmitted )
+ {
+ return CRC.PASSED_INV;
+ }
+ else if( calculated == transmitted )
+ {
+ return CRC.PASSED;
+ }
+ }
+
+ return CRC.FAILED_CRC;
+ }
+
+ public static byte[] getChecks()
+ {
+ return sCHECKSUMS;
+ }
+
+ public static int getCalculatedChecksum( BitSet msg )
+ {
+ int calculated = 0;
+
+ //Iterate bits that are set and XOR running checksum with lookup value
+ for (int i = msg.nextSetBit( 9 ); i >= 9 && i < 33; i = msg.nextSetBit( i+1 ) )
+ {
+ calculated ^= sCHECKSUMS[ i - 9 ];
+ }
+
+ return calculated;
+ }
+
+ /**
+ * Returns the integer value of the 7 bit crc checksum
+ */
+ public static int getTransmittedChecksum( BitSet msg )
+ {
+ int retVal = 0;
+
+ for( int x = 0; x < 7; x++ )
+ {
+ if( msg.get( x + 33 ) )
+ {
+ retVal += 1<<( 6 - x );
+ }
+ }
+
+ return retVal;
+ }
+
+ /**
+ * Determines the errant bit positions and returns them in an array of
+ * integer bit positions.
+ *
+ * Note: currently only detects single-bit errors
+ *
+ * @param msg to be checked for errors
+ * @return - array of integer positions of bits that need flipped
+ */
+ public static int[] findBitErrors( BitSet msg )
+ {
+ int[] retVal = null;
+
+ int checksum = getTransmittedChecksum( msg );
+
+ //Remove the initial fill value (1)
+ checksum ^= 1;
+
+ //Iterate set message bits, removing their respective checksum value
+ //from the transmitted checksum, to arrive at the remainder
+ for (int i = msg.nextSetBit( 0 ); i >= 0 && i < 47; i = msg.nextSetBit( i+1 ) )
+ {
+ checksum ^= sCHECKSUMS[ i ];
+ }
+
+ //If at this point the checksum is 0, then the errant bit is the parity
+ //bit
+ if( checksum == 0 )
+ {
+ msg.flip( 62 );
+ }
+ //Otherwise, try to lookup the syndrome for a single bit error
+ else
+ {
+ for( int x = 0; x < 47; x++ )
+ {
+ if( checksum == sCHECKSUMS[ x ] )
+ {
+ //return this bit position
+ retVal = new int[ 1 ];
+ retVal[ 0 ] = x;
+ }
+ }
+ }
+
+ return retVal;
+ }
+}
diff --git a/src/crc/CRCPassport.java b/src/crc/CRCPassport.java
new file mode 100644
index 00000000..d5bb9dee
--- /dev/null
+++ b/src/crc/CRCPassport.java
@@ -0,0 +1,215 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package crc;
+
+import java.util.BitSet;
+
+import bits.BitSetBuffer;
+
+/**
+ * Passport CRC checksum utility
+ *
+ * Passport message blocks are 68 bits in length as follows:
+ * 0 - 8: Sync
+ * 9 - 59: message bits
+ * 60 - 67: CRC-7 check bits plus 1 parity
+ *
+ * Passport uses a CRC-7 (0x44) with an initial fill of 0x44 plus parity set.
+ *
+ * CRC-7 Generating Polynomial: x6 + x2 + 1 (0x44)
+ */
+public class CRCPassport
+{
+ private static final byte sFILL_00 = (byte)0x00;
+
+ private static byte[] sCHECKSUMS = new byte[]
+ {
+ (byte)0x6E, //DCC 1
+ (byte)0xBF, //DCC 0
+ (byte)0xD6, //LCN 10
+ (byte)0xE3, //LCN 9
+ (byte)0xF8, //LCN 8
+ (byte)0x7C, //LCN 7
+ (byte)0x3E, //LCN 6
+ (byte)0x97, //LCN 5
+ (byte)0xC2, //LCN 4
+ (byte)0xE9, //LCN 3
+ (byte)0x75, //LCN 2
+ (byte)0x3B, //LCN 1
+ (byte)0x94, //LCN 0
+ (byte)0x4A, //SITE 6
+ (byte)0xAD, //SITE 5
+ (byte)0x57, //SITE 4
+ (byte)0xA2, //SITE 3
+ (byte)0xD9, //SITE 2
+ (byte)0x6D, //SITE 1
+ (byte)0x37, //SITE 0
+ (byte)0x92, //GROUP 15
+ (byte)0xC1, //GROUP 14
+ (byte)0x61, //GROUP 13
+ (byte)0x31, //GROUP 12
+ (byte)0x19, //GROUP 11
+ (byte)0x0D, //GROUP 10
+ (byte)0x07, //GROUP 9
+ (byte)0x8A, //GROUP 8
+ (byte)0xCD, //GROUP 7
+ (byte)0x67, //GROUP 6
+ (byte)0xBA, //GROUP 5
+ (byte)0xD5, //GROUP 4
+ (byte)0x6B, //GROUP 3
+ (byte)0xBC, //GROUP 2
+ (byte)0x5E, //GROUP 1
+ (byte)0xA7, //GROUP 0
+ (byte)0xDA, //TYPE 3
+ (byte)0xE5, //TYPE 2
+ (byte)0x73, //TYPE 1
+ (byte)0xB0, //TYPE 0
+ (byte)0x58, //FREE 10
+ (byte)0x2C, //FREE 9
+ (byte)0x16, //FREE 8
+ (byte)0x83, //FREE 7
+ (byte)0xC8, //FREE 6
+ (byte)0x64, //FREE 5
+ (byte)0x32, //FREE 4
+ (byte)0x91, //FREE 3
+ (byte)0x49, //FREE 2
+ (byte)0x25, //FREE 1
+ (byte)0x13 //FREE 0
+ };
+
+ /**
+ * Determines if message bits 9 - 59 pass the CRC checksum
+ * contained in bits 60 - 68, using a lookup table of CRC checksum values
+ * derived from the CRC-7 value and the final parity bit
+ */
+ public static CRC check( BitSet msg )
+ {
+ CRC crc = CRC.UNKNOWN;
+
+ byte calculated = 0x0; //Starting value for an OSW
+
+ //Iterate bits that are set and XOR running checksum with lookup value
+ for (int i = msg.nextSetBit( 9 ); i >= 9 && i <= 59; i = msg.nextSetBit( i+1 ) )
+ {
+ calculated ^= sCHECKSUMS[ i - 9 ];
+ }
+
+ //Apply the message checksum to derive the residual
+ calculated ^= getChecksum( msg );
+
+ switch( (byte)calculated )
+ {
+ case sFILL_00:
+ crc = CRC.PASSED;
+ break;
+ default:
+ crc = CRC.FAILED_CRC;
+ }
+
+ return crc;
+ }
+
+ public static byte getResidual( BitSet msg )
+ {
+ byte calculated = 0x0; //Initial fill of zero
+
+ //Iterate bits that are set and XOR running checksum with lookup value
+ for (int i = msg.nextSetBit( 9 ); i >= 9 && i <= 59; i = msg.nextSetBit( i+1 ) )
+ {
+ calculated ^= sCHECKSUMS[ i - 9 ];
+ }
+
+ calculated ^= getChecksum( msg );
+
+ return calculated;
+ }
+
+ public static byte[] getChecks()
+ {
+ return sCHECKSUMS;
+ }
+
+ /**
+ * Returns the integer value of the 7 bit crc checksum
+ */
+ public static byte getChecksum( BitSet msg )
+ {
+ byte retVal = 0x0;
+
+ for( int x = 0; x < 8; x++ )
+ {
+ if( msg.get( x + 60 ) )
+ {
+ retVal ^= (byte)( 1<<( 7 - x ) );
+ }
+ }
+
+ return retVal;
+ }
+
+ /**
+ * Initial take on error correction. The majority of the errors detected
+ * are in the final 2 bits, owing to the forward looking soft bit detection
+ * used in the decoder. For right now, correct any messages where the
+ * residual indicates that the final 1 or 2 bits are bad.
+ */
+ public static BitSetBuffer correct( BitSetBuffer msg )
+ {
+ int residual = (int)( 0xFF & getResidual( msg ) );
+
+ switch( residual )
+ {
+ case 1:
+ case 136:
+ msg.flip( 67 );
+ break;
+ case 3:
+ case 138:
+ msg.flip( 66 );
+ msg.flip( 67 );
+ break;
+ case 2:
+ case 139:
+ msg.flip( 66 );
+ break;
+ case 140:
+ msg.flip( 65 );
+ msg.flip( 67 );
+ break;
+ case 153:
+ msg.flip( 67 );
+ break;
+ case 142:
+ msg.flip( 65 );
+ msg.flip( 66 );
+ msg.flip( 67 );
+ break;
+ }
+
+ return msg;
+ }
+
+ /**
+ * Performs an even parity check on the bitset. If the number of bits that
+ * are set is an even number, then it passes the even parity check
+ */
+ public static boolean isEvenParity( BitSetBuffer bits )
+ {
+ return ( bits.cardinality() % 2 == 0 );
+ }
+}
diff --git a/src/crc/CRCUtil.java b/src/crc/CRCUtil.java
new file mode 100644
index 00000000..0c42a195
--- /dev/null
+++ b/src/crc/CRCUtil.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package crc;
+
+import java.util.BitSet;
+
+public class CRCUtil
+{
+ public static BitSet check( BitSet originalMessage,
+ int messageLength,
+ BitSet polynomial,
+ int polynomialLength )
+ {
+ //Make a copy of the message, since we're going to modify it
+ BitSet copy = originalMessage.get( 0, messageLength );
+
+ //Iterate through the message bits
+ for( int x = 0; x < messageLength; x++ )
+ {
+ //If we're aligned against a starting 1 in the message, divide it
+ if( copy.get( 0 ) )
+ {
+ copy.xor( polynomial );
+ }
+
+ //Left shift the message
+ copy = copy.get( 1, messageLength + polynomialLength - x );
+ }
+
+ return copy.get( 0, polynomialLength );
+ }
+
+ public static void log( String message )
+ {
+ System.out.println( message );
+ }
+
+ public enum Status
+ {
+ PASS, FAIL, BITINVERTED, WORDINVERTED;
+ }
+
+}
diff --git a/src/crc/Checksum.java b/src/crc/Checksum.java
new file mode 100644
index 00000000..bf7d8b27
--- /dev/null
+++ b/src/crc/Checksum.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package crc;
+
+import java.util.BitSet;
+
+/**
+ * Calculates a 7-bit checksum using an array of xor values from the
+ * ChecksumType class
+ */
+public class Checksum
+{
+ public static boolean validate7BitChecksum( BitSet message,
+ byte messageChecksum,
+ ChecksumType checksumType )
+ {
+ return ( messageChecksum & get7BitChecksum( message, checksumType ) ) == 0;
+ }
+
+ public static byte get7BitChecksum( BitSet message,
+ ChecksumType checksumType )
+ {
+ byte checksum = 0x00;
+
+ short[] checkValues = checksumType.getValues();
+
+ for( int x = 0; x < checkValues.length; x++ )
+ {
+ if( message.get( x ) )
+ {
+ checksum ^= checkValues[ x ];
+ }
+ }
+
+ return checksum;
+ }
+}
diff --git a/src/crc/ChecksumTest.java b/src/crc/ChecksumTest.java
new file mode 100644
index 00000000..bd0bd1dc
--- /dev/null
+++ b/src/crc/ChecksumTest.java
@@ -0,0 +1,96 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package crc;
+
+import java.util.BitSet;
+
+public class ChecksumTest
+{
+
+ /**
+ * @param args
+ */
+ public static void main( String[] args )
+ {
+ log( "START!" );
+
+ String msg = "111000001000010010001110";
+ log( msg + " check:" + String.format( "%02X", Checksum.get7BitChecksum( load( msg ), ChecksumType.LTR ) ) );
+
+ log( "DONE!" );
+ }
+
+ public static void log( String message )
+ {
+ System.out.println( message );
+ }
+
+ public static String getString( BitSet bitset, int length )
+ {
+ StringBuilder sb = new StringBuilder();
+ for( int x = 0; x < length; x++ )
+ {
+ if( bitset.get( x ) )
+ {
+ sb.append( "1" );
+ }
+ else
+ {
+ sb.append( "0" );
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public static String getStringReverse( BitSet bitset, int length )
+ {
+ StringBuilder sb = new StringBuilder();
+ for( int x = length - 1; x > -1; x-- )
+ {
+ if( bitset.get( x ) )
+ {
+ sb.append( "1" );
+ }
+ else
+ {
+ sb.append( "0" );
+ }
+ }
+
+ return sb.toString();
+ }
+
+
+ public static BitSet load( String message )
+ {
+ BitSet retVal = new BitSet();
+
+ for( int x = 0; x < message.length(); x++ )
+ {
+ if( message.substring( x, x + 1 ).contentEquals( "1" ) )
+ {
+ retVal.set( x );
+ }
+ }
+
+ return retVal;
+ }
+
+
+}
diff --git a/src/crc/ChecksumType.java b/src/crc/ChecksumType.java
new file mode 100644
index 00000000..42d9d6d1
--- /dev/null
+++ b/src/crc/ChecksumType.java
@@ -0,0 +1,223 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package crc;
+
+public enum ChecksumType
+{
+ FLEETSYNC_ANI( new short[]
+ {
+ 0x0000, //Unknown Always One
+ 0x0000, //Status Msg Flag
+ 0x14C3, //Status Message 6 001010011000011
+ 0x6908, //Status Message 5 110100100001000
+ 0x3484, //Status Message 4 011010010000100
+ 0x1A42, //Status Message 3 001101001000010
+ 0x0D21, //Status Message 2 000110100100001
+ 0x729A, //Status Message 1 111001010011010
+ 0x394D, //Status Message 0 011100101001101
+ 0x0000, //Type 7
+ 0x0000, //Type 6
+ 0x0000, //Type 5
+ 0x0000, //Type 4
+ 0x0000, //Type 3
+ 0x0000, //Type 2
+ 0x0000, //Type 1
+ 0x0000, //Type 0
+ 0x0000, //Fleet From 7
+ 0x0000, //Fleet From 6
+ 0x0000, //Fleet From 5
+ 0x0000, //Fleet From 4
+ 0x402E, //Fleet From 3 100000000101110
+ 0x2017, //Fleet From 2 010000000010111
+ 0x6401, //Fleet From 1 110010000000001
+ 0x460A, //Fleet From 0 100011000001010
+ 0x0000, //Ident From 11
+ 0x0000, //Ident From 10
+ 0x0000, //Ident From 9
+ 0x0000, //Ident From 8
+ 0x0CB1, //Ident From 7 000110010110001
+ 0x7252, //Ident From 6 111001001010010
+ 0x3929, //Ident From 5 011100100101001
+ 0x689E, //Ident From 4 110100010011110
+ 0x344F, //Ident From 3 011010001001111
+ 0x6E2D, //Ident From 2 110111000101101
+ 0x431C, //Ident From 1 100001100011100
+ 0x218E, //Ident From 0 010000110001110
+ 0x0000, //Ident To 11
+ 0x0000, //Ident To 10
+ 0x0000, //Ident To 9
+ 0x0000, //Ident To 8
+ 0x6685, //Ident To 7 110011010000101
+ 0x4748, //Ident To 6 100011101001000
+ 0x23A4, //Ident To 5 010001110100100
+ 0x11D2, //Ident To 4 001000111010010
+ 0x08E9, //Ident To 3 000100011101001
+ 0x707E, //Ident To 2 111000001111110
+ 0x383F, //Ident To 1 011100000111111
+ 0x6815, //Ident To 0 110100000010101
+ }),
+
+ LTR( new short[]
+ { 0x0038, //Area
+ 0x001C, //Channel 4
+ 0x000E, //Channel 3
+ 0x0046, //Channel 2
+ 0x0023, //Channel 1
+ 0x0051, //Channel 0
+ 0x0068, //Home 4
+ 0x0075, //Home 3
+ 0x007A, //Home 2
+ 0x003D, //Home 1
+ 0x001F, //Home 0
+ 0x004F, //Group 7
+ 0x0026, //Group 6
+ 0x0052, //Group 5
+ 0x0029, //Group 4
+ 0x0015, //Group 3
+ 0x000B, //Group 2
+ 0x0045, //Group 1
+ 0x0062, //Group 0
+ 0x0031, //Free 4
+ 0x0019, //Free 3
+ 0x000D, //Free 2
+ 0x0007, //Free 1
+ 0x0043 //Free 0
+ } ),
+
+ PASSPORT( new short[]
+ {
+// 0x6e,
+// 0xbf,
+// 0xd6,
+// 0xe3,
+// 0xf8,
+// 0x7c,
+// 0x3e,
+// 0x97,
+// 0xc2,
+// 0xe9,
+// 0x75,
+// 0x3b,
+// 0x94,
+// 0x4a,
+// 0xad,
+// 0x57,
+// 0xa2,
+// 0xd9,
+// 0x6d,
+// 0x37,
+// 0x92,
+// 0xc1,
+// 0x61,
+// 0x31,
+// 0x19,
+// 0x0d,
+// 0x07,
+// 0x8a,
+// 0xcd,
+// 0x67,
+// 0xba,
+// 0xd5,
+// 0x6b, // 0111110
+// 0xbc, // 1111100
+// 0x5e, // 1101001
+// 0xa7, // 1000011
+// 0xda, // 0010111
+// 0xe5, // 0101110
+// 0x73, // 1011100
+// 0xb0, // 0101001
+// 0x58, // 1010010
+// 0x2c, // 0110101 Bit 43
+// 0x16, // 1101010
+// 0x83, // 1000101 Bit 45
+// 0xc8, // 0011011 Bit 46
+// 0x64, // 0110110
+// 0x32, // 1101100
+// 0x91, // 1001001
+// 0x49,
+// 0x25,
+// 0x13,
+ } ),
+
+ MULTINET( new short[]
+ {
+// 0x96, // 0010110
+// 0xAC, // 0101100
+// 0xd8, // 1011000
+// 0x21, // 0100001
+// 0x42, // 1000010
+// 0x95, // 0010101
+// 0xaa, // 0101010
+// 0xd4, // 1010100
+// 0x39, // 0111001
+// 0x72, // 1110010
+// 0xf5, // 1110101 Bit 10
+// 0x7b, // 1111011
+// 0xe7, // 1100111
+// 0x5f, // 1011111
+// 0xaf, // 0101111
+// 0xde, // 1011110
+// 0x2d, // 0101101
+// 0x5a, // 1011010
+// 0xa5, // 0100101
+// 0xca, // 1001010
+// 0x05, // 0000101
+// 0x0a, // 0001010
+// 0x14, // 0010100
+// 0x28, // 0101000
+// 0x50, // 1010000
+// 0xb1, // 0110001
+// 0xe2, // 1100010
+// 0x55, // 1010101
+// 0xbb, // 0111011
+// 0xf6, // 1110110
+// 0x7d, // 1111101
+// 0xeb, // 1101011
+// 0x47, // 1000111
+// 0x9f, // 0011111 Bit 33
+// 0xbe, // 0111110
+// 0xfc, // 1111100
+// 0x69, // 1101001
+// 0xc3, // 1000011
+// 0x17, // 0010111
+// 0x2e, // 0101110
+// 0x5c, // 1011100
+// 0xA9, // 0101001
+// 0xD2, // 1010010
+// 0x35, // 0110101 Bit 43
+// 0x6A, // 1101010
+// 0xC5, // 1000101 Bit 45
+// 0x1B, // 0011011 Bit 46
+// 0x36, // 0110110
+// 0x6C, // 1101100
+// 0xC9 // 1001001
+ });
+
+ private short[] mValues;
+
+ ChecksumType( short[] values )
+ {
+ mValues = values;
+ }
+
+ public short[] getValues()
+ {
+ return mValues;
+ }
+
+}
diff --git a/src/decode/AuxDecodeComponentEditor.java b/src/decode/AuxDecodeComponentEditor.java
new file mode 100644
index 00000000..16b37ac6
--- /dev/null
+++ b/src/decode/AuxDecodeComponentEditor.java
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+import javax.swing.BorderFactory;
+import javax.swing.JCheckBox;
+import javax.swing.SwingUtilities;
+
+import controller.channel.AbstractChannelEditor;
+import controller.channel.ChannelNode;
+import decode.config.AuxDecodeConfiguration;
+
+public class AuxDecodeComponentEditor extends AbstractChannelEditor
+{
+ private static final long serialVersionUID = 1L;
+
+ private HashMap mControls =
+ new HashMap();
+
+ public AuxDecodeComponentEditor( ChannelNode channelNode )
+ {
+ super( channelNode );
+
+ List decoders = DecoderType.getAuxDecoders();
+
+ Collections.sort( decoders );
+
+ for( DecoderType decoder: decoders )
+ {
+ AuxDecoderCheckBox control = new AuxDecoderCheckBox( decoder );
+
+ if( getConfig().getAuxDecoders().contains( decoder ) )
+ {
+ control.setSelected( true );
+ }
+
+ add( control, "wrap" );
+
+ mControls.put( decoder, control );
+ }
+ }
+
+ public AuxDecodeConfiguration getConfig()
+ {
+ return getChannelNode().getChannel().getAuxDecodeConfiguration();
+ }
+
+ @Override
+ public void save()
+ {
+ getConfig().clearAuxDecoders();
+
+ for( DecoderType decoder: mControls.keySet() )
+ {
+ if( mControls.get( decoder ) .isSelected() )
+ {
+ getConfig().addAuxDecoder( decoder );
+ }
+ }
+ }
+
+ @Override
+ public void reset()
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ for( DecoderType decoder: mControls.keySet() )
+ {
+ mControls.get( decoder ).setSelected(
+ getConfig().getAuxDecoders().contains( decoder ) );
+ }
+ }
+ });
+
+ }
+
+ public class AuxDecoderCheckBox extends JCheckBox
+ {
+ private DecoderType mDecoderType;
+
+ public AuxDecoderCheckBox( DecoderType decoder )
+ {
+ super( decoder.getDisplayString() );
+
+ mDecoderType = decoder;
+ }
+ }
+}
diff --git a/src/decode/DecodeComponentEditor.java b/src/decode/DecodeComponentEditor.java
new file mode 100644
index 00000000..114dce05
--- /dev/null
+++ b/src/decode/DecodeComponentEditor.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode;
+
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.BorderFactory;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JComboBox;
+import javax.swing.SwingUtilities;
+
+import controller.channel.AbstractChannelEditor;
+import controller.channel.ChannelNode;
+import decode.config.DecodeConfigFactory;
+import decode.config.DecodeConfiguration;
+
+public class DecodeComponentEditor extends AbstractChannelEditor
+{
+ private static final long serialVersionUID = 1L;
+
+ private JComboBox mComboDecoders;
+
+ private DecodeEditor mEditor;
+
+ public DecodeComponentEditor( ChannelNode channelNode )
+ {
+ super( channelNode );
+
+ /**
+ * ComboBox: Decoders
+ */
+ mComboDecoders = new JComboBox();
+
+ DefaultComboBoxModel model =
+ new DefaultComboBoxModel();
+
+ for( DecoderType type: DecoderType.getAvailableDecoders() )
+ {
+ model.addElement( type );
+ }
+
+ mComboDecoders.setModel( model );
+
+ mComboDecoders.addActionListener( new ActionListener()
+ {
+ @Override
+ public void actionPerformed( ActionEvent e )
+ {
+ DecoderType selected = mComboDecoders
+ .getItemAt( mComboDecoders.getSelectedIndex() );
+
+ if( selected != null )
+ {
+ DecodeConfiguration config;
+
+ if( mChannelNode.getChannel()
+ .getDecodeConfiguration().getDecoderType() ==
+ selected )
+ {
+ config = mChannelNode.getChannel()
+ .getDecodeConfiguration();
+ }
+ else
+ {
+ config = DecodeConfigFactory
+ .getDecodeConfiguration( selected );
+ }
+
+ //Remove the existing one
+ if( mEditor != null )
+ {
+ remove( mEditor );
+ }
+
+ //Change to the new one
+ mEditor = DecodeEditorFactory.getPanel( config, mChannelNode );
+
+ add( mEditor, "span 2" );
+
+ revalidate();
+ repaint();
+ }
+ }
+ });
+ add( mComboDecoders, "wrap" );
+
+ reset();
+ }
+
+ public void reset()
+ {
+ final DecoderType decoder = mChannelNode.getChannel()
+ .getDecodeConfiguration().getDecoderType();
+
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ @Override
+ public void run() {
+ mComboDecoders.setSelectedItem( decoder );
+
+ mComboDecoders.requestFocus();
+
+ mComboDecoders.requestFocusInWindow();
+ }
+ });
+ }
+
+ @Override
+ public void save()
+ {
+ mEditor.save();
+
+ //The component that calls save will invoke the change broadcast
+ mChannelNode.getChannel()
+ .setDecodeConfiguration( mEditor.getConfig(), false );
+ }
+}
diff --git a/src/decode/DecodeEditor.java b/src/decode/DecodeEditor.java
new file mode 100644
index 00000000..cdc1170c
--- /dev/null
+++ b/src/decode/DecodeEditor.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode;
+
+import controller.Editor;
+import decode.config.DecodeConfiguration;
+
+public class DecodeEditor extends Editor
+{
+ private static final long serialVersionUID = 1L;
+
+ protected DecodeConfiguration mConfig;
+
+ public DecodeEditor( DecodeConfiguration config )
+ {
+ mConfig = config;
+ }
+
+ public DecodeConfiguration getConfig()
+ {
+ return mConfig;
+ }
+
+ @Override
+ public void save()
+ {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void reset()
+ {
+ // TODO Auto-generated method stub
+
+ }
+}
diff --git a/src/decode/DecodeEditorFactory.java b/src/decode/DecodeEditorFactory.java
new file mode 100644
index 00000000..ed95699a
--- /dev/null
+++ b/src/decode/DecodeEditorFactory.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode;
+
+import controller.channel.ChannelNode;
+import decode.config.DecodeConfiguration;
+import decode.ltrnet.LTRNetEditor;
+import decode.ltrstandard.LTRStandardEditor;
+import decode.mpt1327.MPT1327Editor;
+import decode.nbfm.NBFMEditor;
+import decode.passport.PassportEditor;
+
+public class DecodeEditorFactory
+{
+ public static DecodeEditor getPanel( DecodeConfiguration config,
+ ChannelNode channelNode )
+ {
+ DecodeEditor configuredPanel;
+
+ switch( config.getDecoderType() )
+ {
+ case NBFM:
+ configuredPanel = new NBFMEditor( config );
+ break;
+ case LTR_STANDARD:
+ configuredPanel = new LTRStandardEditor( config );
+ break;
+ case LTR_NET:
+ configuredPanel = new LTRNetEditor( config );
+ break;
+ case MPT1327:
+ configuredPanel = new MPT1327Editor( config, channelNode );
+ break;
+ case PASSPORT:
+ configuredPanel = new PassportEditor( config );
+ break;
+ default:
+ configuredPanel = new DecodeEditor( config );
+ break;
+ }
+
+ return configuredPanel;
+ }
+}
diff --git a/src/decode/Decoder.java b/src/decode/Decoder.java
new file mode 100644
index 00000000..add1f52a
--- /dev/null
+++ b/src/decode/Decoder.java
@@ -0,0 +1,148 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import message.Message;
+import sample.Broadcaster;
+import sample.Listener;
+import sample.complex.ComplexSample;
+import source.Source.SampleType;
+import eventlog.MessageEventLogger;
+
+public abstract class Decoder implements Listener
+{
+ private MessageEventLogger mEventLogger;
+ private Broadcaster mMessageBroadcaster = new Broadcaster();
+ private Broadcaster mFloatBroadcaster = new Broadcaster();
+ private Broadcaster mComplexBroadcaster =
+ new Broadcaster();
+
+ protected SampleType mSourceSampleType;
+ protected ArrayList mAuxiliaryDecoders = new ArrayList();
+
+ public Decoder( SampleType sampleType )
+ {
+ mSourceSampleType = sampleType;
+ }
+
+ /**
+ * Returns a float listener interface for connecting this decoder to a
+ * float stream provider
+ */
+ public Listener getFloatReceiver()
+ {
+ return (Listener)mFloatBroadcaster;
+ }
+
+ /**
+ * Returns a complex listener interface for connecting this decoder to a
+ * float stream provider
+ */
+ public Listener getComplexReceiver()
+ {
+ return (Listener)mComplexBroadcaster;
+ }
+
+ public abstract DecoderType getType();
+
+ public void addAuxiliaryDecoder( Decoder decoder )
+ {
+ mAuxiliaryDecoders.add( decoder );
+ mFloatBroadcaster.addListener( decoder.getFloatReceiver() );
+ decoder.addMessageListener( this );
+ }
+
+ /**
+ * Cleanup method. Invoke this method after stop and before delete.
+ */
+ public void dispose()
+ {
+ for( Decoder auxiliaryDecoder: mAuxiliaryDecoders )
+ {
+ auxiliaryDecoder.dispose();
+ }
+
+ mAuxiliaryDecoders.clear();
+
+ mMessageBroadcaster.clear();
+ mComplexBroadcaster.clear();
+ mFloatBroadcaster.clear();
+ }
+
+ public List getAuxiliaryDecoders()
+ {
+ return mAuxiliaryDecoders;
+ }
+
+ public void setEventLogger( MessageEventLogger eventLogger )
+ {
+ mEventLogger = eventLogger;
+ }
+
+ public MessageEventLogger getEventLogger()
+ {
+ return mEventLogger;
+ }
+
+ @Override
+ public void receive( Message message )
+ {
+ send( message );
+ }
+
+ public void send( Message message )
+ {
+ if( mMessageBroadcaster != null )
+ {
+ mMessageBroadcaster.receive( message );
+ }
+ }
+
+ public void addMessageListener( Listener listener )
+ {
+ mMessageBroadcaster.addListener( listener );
+ }
+
+ public void removeMessageListener( Listener listener )
+ {
+ mMessageBroadcaster.removeListener( listener );
+ }
+
+ public void addFloatListener( Listener listener )
+ {
+ mFloatBroadcaster.addListener( listener );
+ }
+
+ public void removeFloatListener( Listener listener )
+ {
+ mFloatBroadcaster.removeListener( listener );
+ }
+
+ public void addComplexListener( Listener listener )
+ {
+ mComplexBroadcaster.addListener( listener );
+ }
+
+ public void removeComplexListener( Listener listener )
+ {
+ mComplexBroadcaster.removeListener( listener );
+ }
+}
diff --git a/src/decode/DecoderFactory.java b/src/decode/DecoderFactory.java
new file mode 100644
index 00000000..402529b3
--- /dev/null
+++ b/src/decode/DecoderFactory.java
@@ -0,0 +1,181 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode;
+
+import source.Source.SampleType;
+import alias.AliasList;
+import controller.channel.ChannelMap;
+import controller.channel.ProcessingChain;
+import controller.state.ChannelState;
+import decode.config.AuxDecodeConfiguration;
+import decode.config.DecodeConfigLTRNet;
+import decode.config.DecodeConfigLTRStandard;
+import decode.config.DecodeConfigMPT1327;
+import decode.config.DecodeConfigNBFM;
+import decode.config.DecodeConfiguration;
+import decode.fleetsync2.Fleetsync2Decoder;
+import decode.fleetsync2.FleetsyncChannelState;
+import decode.ltrnet.LTRNetChannelState;
+import decode.ltrnet.LTRNetDecoder;
+import decode.ltrstandard.LTRChannelState;
+import decode.ltrstandard.LTRStandardDecoder;
+import decode.mdc1200.MDCChannelState;
+import decode.mdc1200.MDCDecoder;
+import decode.mpt1327.MPT1327ChannelState;
+import decode.mpt1327.MPT1327Decoder;
+import decode.nbfm.NBFMChannelState;
+import decode.nbfm.NBFMConventionalDecoder;
+import decode.passport.PassportChannelState;
+import decode.passport.PassportDecoder;
+
+public class DecoderFactory
+{
+ /**
+ * Returns a fully configured decoder with embedded auxiliary decoders
+ * @param channelConfig
+ * @return
+ */
+ public static Decoder getDecoder( ProcessingChain chain,
+ SampleType sampleType,
+ AliasList aliasList )
+ {
+ Decoder retVal = null;
+
+ DecodeConfiguration config =
+ chain.getChannel().getDecodeConfiguration();
+
+
+ switch( config.getDecoderType() )
+ {
+ case NBFM:
+ retVal = new NBFMConventionalDecoder( sampleType );
+ break;
+ case LTR_STANDARD:
+ DecodeConfigLTRStandard ltrStandardConfig =
+ (DecodeConfigLTRStandard)config;
+
+ retVal = new LTRStandardDecoder( sampleType, aliasList,
+ ltrStandardConfig.getMessageDirection() );
+ break;
+ case LTR_NET:
+ DecodeConfigLTRNet ltrNetConfig = (DecodeConfigLTRNet)config;
+
+ retVal = new LTRNetDecoder( sampleType, aliasList,
+ ltrNetConfig.getMessageDirection() );
+ break;
+ case MPT1327:
+ retVal = new MPT1327Decoder( sampleType, aliasList );
+ break;
+ case PASSPORT:
+ retVal = new PassportDecoder( sampleType, aliasList );
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown decoder type [" +
+ config.getDecoderType().toString() + "]" );
+ }
+
+ if( retVal != null )
+ {
+ AuxDecodeConfiguration auxConfig =
+ chain.getChannel().getAuxDecodeConfiguration();
+
+ for( DecoderType auxDecoder: auxConfig.getAuxDecoders() )
+ {
+ switch( auxDecoder )
+ {
+ case FLEETSYNC2:
+ retVal.addAuxiliaryDecoder(
+ new Fleetsync2Decoder( aliasList ) );
+ break;
+ case MDC1200:
+ retVal.addAuxiliaryDecoder(
+ new MDCDecoder( aliasList ) );
+ break;
+ }
+ }
+ }
+
+ return retVal;
+ }
+
+ public static ChannelState getChannelStateNew( ProcessingChain chain,
+ AliasList aliasList )
+ {
+ ChannelState retVal = null;
+
+ DecodeConfiguration decodeConfig =
+ chain.getChannel().getDecodeConfiguration();
+
+ switch( decodeConfig.getDecoderType() )
+ {
+ case NBFM:
+ retVal = new NBFMChannelState( chain, aliasList );
+
+ DecodeConfigNBFM conv =
+ (DecodeConfigNBFM)decodeConfig;
+
+ break;
+ case LTR_STANDARD:
+ retVal = new LTRChannelState( chain, aliasList );
+ break;
+ case LTR_NET:
+ retVal = new LTRNetChannelState( chain, aliasList );
+ break;
+ case MPT1327:
+ DecodeConfigMPT1327 mpt = (DecodeConfigMPT1327)decodeConfig;
+
+ String channelMapName = mpt.getChannelMapName();
+
+ ChannelMap map = chain.getResourceManager().getPlaylistManager()
+ .getPlayist().getChannelMapList()
+ .getChannelMap( channelMapName );
+
+ retVal = new MPT1327ChannelState( chain, aliasList, map );
+ break;
+ case PASSPORT:
+ retVal = new PassportChannelState( chain, aliasList );
+ break;
+ default:
+ break;
+ }
+
+ if( retVal != null )
+ {
+ AuxDecodeConfiguration auxConfig =
+ chain.getChannel().getAuxDecodeConfiguration();
+
+ for( DecoderType auxDecoder: auxConfig.getAuxDecoders() )
+ {
+ switch( auxDecoder )
+ {
+ case FLEETSYNC2:
+ retVal.addAuxChannelState(
+ new FleetsyncChannelState( retVal ) );
+ break;
+ case MDC1200:
+ retVal.addAuxChannelState(
+ new MDCChannelState( retVal ) );
+ break;
+ }
+ }
+ }
+
+ return retVal;
+ }
+}
diff --git a/src/decode/DecoderType.java b/src/decode/DecoderType.java
new file mode 100644
index 00000000..ef192fc2
--- /dev/null
+++ b/src/decode/DecoderType.java
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+
+/**
+ * Enumeration of decoder types
+ */
+public enum DecoderType
+{
+ FLEETSYNC2( "Fleetsync II", "Fsync2", "images/fm", 12500 ),
+ LTR_STANDARD( "LTR-Standard", "LTR", "images/ltr_standard", 10000 ),
+ LTR_NET( "LTR-Net", "LTR-Net", "images/ltr_net", 10000 ),
+ MDC1200( "MDC1200", "MDC1200", "images/fm", 12500 ),
+ MPT1327( "MPT1327", "MPT1327", "images/mpt1327", 12500 ),
+ NBFM( "NBFM", "NBFM", "images/fm", 12500 ),
+ PASSPORT( "Passport", "Passport", "images/passport", 12500 );
+
+ private String mDisplayString;
+ private String mShortDisplayString;
+ private String mIconPrefix;
+ private int mChannelBandwidth;
+
+ DecoderType( String displayString, String shortDisplayString,
+ String iconPrefix, int bandwidth )
+ {
+ mDisplayString = displayString;
+ mShortDisplayString = shortDisplayString;
+ mIconPrefix = iconPrefix;
+ mChannelBandwidth = bandwidth;
+ }
+
+ /**
+ * Primary decoders
+ */
+ public static EnumSet getAvailableDecoders()
+ {
+ return EnumSet.of( DecoderType.LTR_NET,
+ DecoderType.LTR_STANDARD,
+ DecoderType.MPT1327,
+ DecoderType.NBFM,
+ DecoderType.PASSPORT );
+ }
+
+ /**
+ * Decoders that can be used in the viewer application
+ */
+ public static EnumSet getInstrumentableDecoders()
+ {
+ return EnumSet.of( DecoderType.FLEETSYNC2,
+ DecoderType.MDC1200,
+ DecoderType.MPT1327 );
+ }
+
+ /**
+ * Returns listing of auxiliary decoders.
+ */
+ public static ArrayList getAuxDecoders()
+ {
+ ArrayList decoders = new ArrayList();
+
+ decoders.add( DecoderType.FLEETSYNC2 );
+ decoders.add( DecoderType.MDC1200 );
+
+ return decoders;
+ }
+
+ public String getDisplayString()
+ {
+ return mDisplayString;
+ }
+
+ public String getShortDisplayString()
+ {
+ return mShortDisplayString;
+ }
+
+ public String getIconPrefix()
+ {
+ return mIconPrefix;
+ }
+
+ public int getChannelBandwidth()
+ {
+ return mChannelBandwidth;
+ }
+
+ @Override
+ public String toString()
+ {
+ return mDisplayString;
+ }
+}
diff --git a/src/decode/config/AuxDecodeConfiguration.java b/src/decode/config/AuxDecodeConfiguration.java
new file mode 100644
index 00000000..dcb31f40
--- /dev/null
+++ b/src/decode/config/AuxDecodeConfiguration.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode.config;
+
+import java.util.ArrayList;
+
+import javax.xml.bind.annotation.XmlElement;
+
+import controller.config.Configuration;
+import decode.DecoderType;
+
+public class AuxDecodeConfiguration extends Configuration
+{
+ ArrayList mAuxDecoders = new ArrayList();
+
+ public AuxDecodeConfiguration()
+ {
+ }
+
+ @XmlElement( name="aux_decoder" )
+ public ArrayList getAuxDecoders()
+ {
+ return mAuxDecoders;
+ }
+
+ public void setAuxDecoders( ArrayList decoders )
+ {
+ mAuxDecoders = decoders;
+ }
+
+ public void addAuxDecoder( DecoderType decoder )
+ {
+ mAuxDecoders.add( decoder );
+ }
+
+ public void removeAuxDecoder( DecoderType decoder )
+ {
+ mAuxDecoders.remove( decoder );
+ }
+
+ public void clearAuxDecoders()
+ {
+ mAuxDecoders.clear();
+ }
+}
diff --git a/src/decode/config/DecodeConfigFactory.java b/src/decode/config/DecodeConfigFactory.java
new file mode 100644
index 00000000..723d5ffd
--- /dev/null
+++ b/src/decode/config/DecodeConfigFactory.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode.config;
+
+import decode.DecoderType;
+
+public class DecodeConfigFactory
+{
+ public static DecodeConfiguration getDefaultDecodeConfiguration()
+ {
+ return getDecodeConfiguration( DecoderType.NBFM );
+ }
+
+ public static DecodeConfiguration
+ getDecodeConfiguration( DecoderType decoder )
+ {
+ DecodeConfiguration retVal;
+
+ switch( decoder )
+ {
+ case NBFM:
+ retVal = new DecodeConfigNBFM();
+ break;
+ case LTR_STANDARD:
+ retVal = new DecodeConfigLTRStandard();
+ break;
+ case LTR_NET:
+ retVal = new DecodeConfigLTRNet();
+ break;
+ case MPT1327:
+ retVal = new DecodeConfigMPT1327();
+ break;
+ case PASSPORT:
+ retVal = new DecodeConfigPassport();
+ break;
+ default:
+ throw new IllegalArgumentException( "DecodeConfigFactory - "
+ + "unknown decoder type [" + decoder.toString() + "]" );
+ }
+
+ return retVal;
+ }
+}
diff --git a/src/decode/config/DecodeConfigLTRNet.java b/src/decode/config/DecodeConfigLTRNet.java
new file mode 100644
index 00000000..6c62e6ff
--- /dev/null
+++ b/src/decode/config/DecodeConfigLTRNet.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode.config;
+
+import javax.xml.bind.annotation.XmlAttribute;
+
+import message.MessageDirection;
+import decode.DecoderType;
+
+public class DecodeConfigLTRNet extends DecodeConfiguration
+{
+ private MessageDirection mMessageDirection = MessageDirection.OSW;
+
+ public DecodeConfigLTRNet()
+ {
+ super( DecoderType.LTR_NET );
+ }
+
+ @XmlAttribute( name = "direction" )
+ public MessageDirection getMessageDirection()
+ {
+ return mMessageDirection;
+ }
+
+ public void setMessageDirection( MessageDirection direction )
+ {
+ mMessageDirection = direction;
+ }
+}
diff --git a/src/decode/config/DecodeConfigLTRStandard.java b/src/decode/config/DecodeConfigLTRStandard.java
new file mode 100644
index 00000000..62118f0f
--- /dev/null
+++ b/src/decode/config/DecodeConfigLTRStandard.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode.config;
+
+import javax.xml.bind.annotation.XmlAttribute;
+
+import message.MessageDirection;
+import decode.DecoderType;
+
+public class DecodeConfigLTRStandard extends DecodeConfiguration
+{
+ private MessageDirection mMessageDirection = MessageDirection.OSW;
+
+ public DecodeConfigLTRStandard()
+ {
+ super( DecoderType.LTR_STANDARD );
+ }
+
+ @XmlAttribute( name = "direction" )
+ public MessageDirection getMessageDirection()
+ {
+ return mMessageDirection;
+ }
+
+ public void setMessageDirection( MessageDirection direction )
+ {
+ mMessageDirection = direction;
+ }
+}
diff --git a/src/decode/config/DecodeConfigMPT1327.java b/src/decode/config/DecodeConfigMPT1327.java
new file mode 100644
index 00000000..2a5db097
--- /dev/null
+++ b/src/decode/config/DecodeConfigMPT1327.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode.config;
+
+import javax.xml.bind.annotation.XmlElement;
+
+import decode.DecoderType;
+
+public class DecodeConfigMPT1327 extends DecodeConfiguration
+{
+ private String mChannelMapName;
+
+ public DecodeConfigMPT1327()
+ {
+ super( DecoderType.MPT1327 );
+ }
+
+ @XmlElement
+ public String getChannelMapName()
+ {
+ return mChannelMapName;
+ }
+
+ public void setChannelMapName( String name )
+ {
+ mChannelMapName = name;
+ }
+}
diff --git a/src/decode/config/DecodeConfigNBFM.java b/src/decode/config/DecodeConfigNBFM.java
new file mode 100644
index 00000000..625ffffc
--- /dev/null
+++ b/src/decode/config/DecodeConfigNBFM.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode.config;
+
+import decode.DecoderType;
+
+public class DecodeConfigNBFM extends DecodeConfiguration
+{
+ public DecodeConfigNBFM()
+ {
+ super( DecoderType.NBFM );
+ }
+}
diff --git a/src/decode/config/DecodeConfigPassport.java b/src/decode/config/DecodeConfigPassport.java
new file mode 100644
index 00000000..f6d8d9ae
--- /dev/null
+++ b/src/decode/config/DecodeConfigPassport.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode.config;
+
+import decode.DecoderType;
+
+public class DecodeConfigPassport extends DecodeConfiguration
+{
+ public DecodeConfigPassport()
+ {
+ super( DecoderType.PASSPORT );
+ }
+}
diff --git a/src/decode/config/DecodeConfiguration.java b/src/decode/config/DecodeConfiguration.java
new file mode 100644
index 00000000..8ede32d2
--- /dev/null
+++ b/src/decode/config/DecodeConfiguration.java
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode.config;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+
+import controller.config.Configuration;
+import decode.DecoderType;
+
+@XmlSeeAlso( { DecodeConfigNBFM.class,
+ DecodeConfigLTRNet.class,
+ DecodeConfigLTRStandard.class,
+ DecodeConfigMPT1327.class,
+ DecodeConfigPassport.class } )
+@XmlRootElement( name = "decode_configuration" )
+public abstract class DecodeConfiguration extends Configuration
+{
+ private DecoderType mDecoderType = DecoderType.NBFM;
+
+ public DecodeConfiguration()
+ {
+ this( DecoderType.NBFM );
+ }
+
+ public DecodeConfiguration( DecoderType type )
+ {
+ mDecoderType = type;
+ }
+
+ public DecoderType getDecoderType()
+ {
+ return mDecoderType;
+ }
+}
diff --git a/src/decode/fleetsync1/Fleetsync1Message.java b/src/decode/fleetsync1/Fleetsync1Message.java
new file mode 100644
index 00000000..8e45d25b
--- /dev/null
+++ b/src/decode/fleetsync1/Fleetsync1Message.java
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode.fleetsync1;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import map.Plottable;
+import message.Message;
+import alias.Alias;
+import bits.BitSetBuffer;
+
+public class Fleetsync1Message extends Message
+{
+ private static SimpleDateFormat mSDF = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
+
+ private BitSetBuffer mMessage;
+
+ public Fleetsync1Message( BitSetBuffer message )
+ {
+ mMessage = message;
+ }
+
+ public boolean isValid()
+ {
+ return true;
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append( mSDF.format( new Date( System.currentTimeMillis() ) ) );
+ sb.append( " " );
+ sb.append( "FS1 [" + mMessage.toString() + "]" );
+
+ return sb.toString();
+ }
+
+ /**
+ * Pads spaces onto the end of the value to make it 'places' long
+ */
+ public String pad( String value, int places, String padCharacter )
+ {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append( value );
+
+ while( sb.length() < places )
+ {
+ sb.append( padCharacter );
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Pads an integer value with additional zeroes to make it decimalPlaces long
+ */
+ public String format( int number, int decimalPlaces )
+ {
+ StringBuilder sb = new StringBuilder();
+
+ int paddingRequired = decimalPlaces - ( String.valueOf( number ).length() );
+
+ for( int x = 0; x < paddingRequired; x++)
+ {
+ sb.append( "0" );
+ }
+
+ sb.append( number );
+
+ return sb.toString();
+ }
+
+ @SuppressWarnings( "unused" )
+ private int getInt( int[] bits )
+ {
+ int retVal = 0;
+
+ for( int x = 0; x < bits.length; x++ )
+ {
+ if( mMessage.get( bits[ x ] ) )
+ {
+ retVal += 1<
+ ******************************************************************************/
+package decode.fleetsync1;
+
+import log.Log;
+import message.Message;
+import sample.Broadcaster;
+import sample.Listener;
+import bits.BitSetBuffer;
+
+public class Fleetsync1MessageProcessor implements Listener
+{
+ private Broadcaster mBroadcaster = new Broadcaster();
+
+ @Override
+ public void receive( BitSetBuffer buffer )
+ {
+ Fleetsync1Message message = new Fleetsync1Message( buffer );
+
+ Log.info( message.toString() );
+
+ mBroadcaster.receive( message );
+ }
+
+ public void addMessageListener( Listener listener )
+ {
+ mBroadcaster.addListener( listener );
+ }
+
+ public void removeMessageListener( Listener listener )
+ {
+ mBroadcaster.removeListener( listener );
+ }
+}
diff --git a/src/decode/fleetsync2/Fleetsync2Decoder.java b/src/decode/fleetsync2/Fleetsync2Decoder.java
new file mode 100644
index 00000000..2e56ad9d
--- /dev/null
+++ b/src/decode/fleetsync2/Fleetsync2Decoder.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode.fleetsync2;
+
+import instrument.Instrumentable;
+import instrument.tap.Tap;
+import instrument.tap.stream.BinaryTap;
+import instrument.tap.stream.FloatTap;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import sample.Listener;
+import source.Source.SampleType;
+import alias.AliasList;
+import bits.MessageFramer;
+import bits.SyncPattern;
+import decode.Decoder;
+import decode.DecoderType;
+import dsp.filter.Filters;
+import dsp.filter.FloatFIRFilter;
+import dsp.filter.FloatHalfBandFilter;
+import dsp.fsk.FSK2Decoder;
+import dsp.fsk.FSK2Decoder.Output;
+
+/**
+ * Fleetsync II Decoder - 1200 baud 2FSK decoder that can process 48k sample rate
+ * complex or floating point samples and output fully framed Fleetsync II
+ * messages
+ */
+public class Fleetsync2Decoder extends Decoder implements Instrumentable
+{
+ /* Decimated sample rate ( 48,000 / 2 = 24,000 ) feeding the decoder */
+ private static final int sDECIMATED_SAMPLE_RATE = 24000;
+
+ /* Baud or Symbol Rate */
+ private static final int sSYMBOL_RATE = 1200;
+
+ /* Message length - 5 x REVS + 16 x SYNC + 8 x 64Bit Blocks */
+ private static final int sMESSAGE_LENGTH = 537;
+
+ /* Instrumentation Taps */
+ private ArrayList mAvailableTaps;
+ private static final String INSTRUMENT_INPUT =
+ "Tap Point: Float Input";
+ private static final String INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD =
+ "Tap Point: Bandpass Filter > < FSK2 Decoder";
+ private static final String INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER =
+ "Tap Point: FSK2 Decoder > < Message Framer";
+
+ private FSK2Decoder mFSKDecoder;
+ private FloatHalfBandFilter mDecimationFilter;
+ private FloatFIRFilter mBandPassFilter;
+ private MessageFramer mMessageFramer;
+ private Fleetsync2MessageProcessor mMessageProcessor;
+
+ public Fleetsync2Decoder( AliasList aliasList )
+ {
+ super( SampleType.FLOAT );
+
+ mDecimationFilter = new FloatHalfBandFilter(
+ Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO, 1.0002 );
+ addFloatListener( mDecimationFilter );
+
+ mBandPassFilter = new FloatFIRFilter(
+ Filters.FIRBP_1200FSK_24000FS.getCoefficients(), 1.02 );
+ mDecimationFilter.setListener( mBandPassFilter );
+
+ mFSKDecoder = new FSK2Decoder( sDECIMATED_SAMPLE_RATE,
+ sSYMBOL_RATE, Output.INVERTED );
+ mBandPassFilter.setListener( mFSKDecoder );
+
+ mMessageFramer = new MessageFramer(
+ SyncPattern.FLEETSYNC2.getPattern(), sMESSAGE_LENGTH );
+ mFSKDecoder.setListener( mMessageFramer );
+
+ mMessageProcessor = new Fleetsync2MessageProcessor( aliasList );
+ mMessageFramer.addMessageListener( mMessageProcessor );
+ mMessageProcessor.addMessageListener( this );
+ }
+
+ public void dispose()
+ {
+ super.dispose();
+
+ mDecimationFilter.dispose();
+ mBandPassFilter.dispose();
+ mFSKDecoder.dispose();
+ mMessageFramer.dispose();
+ mMessageProcessor.dispose();
+ }
+
+ @Override
+ public DecoderType getType()
+ {
+ return DecoderType.FLEETSYNC2;
+ }
+
+ /**
+ * Returns a float listener interface for connecting this decoder to a
+ * float stream provider
+ */
+ public Listener getFloatReceiver()
+ {
+ return (Listener)mDecimationFilter;
+ }
+
+ @Override
+ public List getTaps()
+ {
+ if( mAvailableTaps == null )
+ {
+ mAvailableTaps = new ArrayList();
+
+ mAvailableTaps.add( new FloatTap( INSTRUMENT_INPUT, 0, 1.0f ) );
+ mAvailableTaps.add( new FloatTap(
+ INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD, 0, 0.5f ) );
+ mAvailableTaps.addAll( mFSKDecoder.getTaps() );
+ mAvailableTaps.add( new BinaryTap(
+ INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER, 0, 0.025f ) );
+ }
+
+ return mAvailableTaps;
+ }
+
+ @Override
+ public void addTap( Tap tap )
+ {
+ mFSKDecoder.addTap( tap );
+
+ switch( tap.getName() )
+ {
+ case INSTRUMENT_INPUT:
+ FloatTap inputTap = (FloatTap)tap;
+ addFloatListener( inputTap );
+ break;
+ case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
+ FloatTap bpTap = (FloatTap)tap;
+ mBandPassFilter.setListener( bpTap );
+ bpTap.setListener( mFSKDecoder );
+ break;
+ case INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER:
+ BinaryTap decoderTap = (BinaryTap)tap;
+ mFSKDecoder.setListener( decoderTap );
+ decoderTap.setListener( mMessageFramer );
+ break;
+ }
+ }
+
+ @Override
+ public void removeTap( Tap tap )
+ {
+ mFSKDecoder.removeTap( tap );
+
+ switch( tap.getName() )
+ {
+ case INSTRUMENT_INPUT:
+ FloatTap inputTap = (FloatTap)tap;
+ removeFloatListener( inputTap );
+ break;
+ case INSTRUMENT_BANDPASS_FILTER_TO_FSK2_DEMOD:
+ mBandPassFilter.setListener( mFSKDecoder );
+ break;
+ case INSTRUMENT_FSK2_DECODER_TO_MESSAGE_FRAMER:
+ mFSKDecoder.setListener( mMessageFramer );
+ break;
+ }
+ }
+}
diff --git a/src/decode/fleetsync2/Fleetsync2MessageProcessor.java b/src/decode/fleetsync2/Fleetsync2MessageProcessor.java
new file mode 100644
index 00000000..1c5f7dda
--- /dev/null
+++ b/src/decode/fleetsync2/Fleetsync2MessageProcessor.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode.fleetsync2;
+
+import message.Message;
+import sample.Broadcaster;
+import sample.Listener;
+import alias.AliasList;
+import bits.BitSetBuffer;
+
+public class Fleetsync2MessageProcessor implements Listener
+{
+ private Broadcaster mBroadcaster = new Broadcaster();
+
+ private AliasList mAliasList;
+
+ public Fleetsync2MessageProcessor( AliasList list )
+ {
+ mAliasList = list;
+ }
+
+ public void dispose()
+ {
+ mBroadcaster.dispose();
+ }
+
+ @Override
+ public void receive( BitSetBuffer buffer )
+ {
+ FleetsyncMessage message = new FleetsyncMessage( buffer, mAliasList );
+
+ mBroadcaster.receive( message );
+ }
+
+ public void addMessageListener( Listener listener )
+ {
+ mBroadcaster.addListener( listener );
+ }
+
+ public void removeMessageListener( Listener listener )
+ {
+ mBroadcaster.removeListener( listener );
+ }
+}
diff --git a/src/decode/fleetsync2/FleetsyncActivitySummary.java b/src/decode/fleetsync2/FleetsyncActivitySummary.java
new file mode 100644
index 00000000..c1540651
--- /dev/null
+++ b/src/decode/fleetsync2/FleetsyncActivitySummary.java
@@ -0,0 +1,171 @@
+/*******************************************************************************
+ * 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
+ ******************************************************************************/
+package decode.fleetsync2;
+
+import java.util.Iterator;
+import java.util.TreeSet;
+
+import message.Message;
+import sample.Listener;
+import alias.Alias;
+import alias.AliasList;
+import controller.activity.ActivitySummaryProvider;
+
+public class FleetsyncActivitySummary implements ActivitySummaryProvider,
+ Listener
+{
+ private AliasList mAliasList;
+ private TreeSet mIdents = new TreeSet