#90 [WIP] on redesigning the channel processing gui components and backing model. Simplifies channel state by introducing a new Metadata class that tracks all metadata for all decoders.

This commit is contained in:
Denny
2017-01-14 11:21:53 -05:00
parent 6a9e522d77
commit 7115b4feb1
57 changed files with 1534 additions and 298 deletions

View File

@@ -0,0 +1,91 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package channel.metadata;
import alias.Alias;
public class AliasedIdentifier
{
private String mIdentifier;
private Alias mAlias;
/**
* Identifier and corresponding optional alias
*/
public AliasedIdentifier()
{
}
/**
* Resets alias and value to null
*/
public void reset()
{
mIdentifier = null;
mAlias = null;
}
/**
* Identifier
*/
public String getIdentifier()
{
return mIdentifier;
}
/**
* Sets the identifier to the argument value
*/
public void setIdentifier(String identifier)
{
mIdentifier = identifier;
}
/**
* Indicates if there is a non-null, non-empty identifier
*/
public boolean hasIdentifier()
{
return mIdentifier != null && !mIdentifier.isEmpty();
}
/**
* Alias for the identifier.
*/
public Alias getAlias()
{
return mAlias;
}
/**
* Sets the alias to the argument
*/
public void setAlias(Alias alias)
{
mAlias = alias;
}
/**
* Indicates if there is an alias corresponding to the identifier
*/
public boolean hasAlias()
{
return mAlias != null;
}
}

View File

@@ -0,0 +1,62 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package channel.metadata;
public enum Attribute
{
CHANNEL_CONFIGURATION_LABEL_1,
CHANNEL_CONFIGURATION_LABEL_2,
CHANNEL_FREQUENCY,
CHANNEL_ID,
CHANNEL_STATE,
MESSAGE,
MESSAGE_TYPE,
NETWORK_ID_1,
NETWORK_ID_2,
PRIMARY_ADDRESS_FROM,
PRIMARY_ADDRESS_TO,
SECONDARY_ADDRESS_FROM,
SECONDARY_ADDRESS_TO,
//TODO: remove all attributes below here ********************
CHANNEL_NUMBER,
CHANNEL_NAME,
CHANNEL_SITE_NUMBER,
CHANNEL_SITE_NUMBER_ALIAS,
DESCRIPTION,
FROM_TALKGROUP,
FROM_TALKGROUP_ALIAS,
FROM_TALKGROUP_TYPE,
NAC,
SITE,
SITE_ALIAS,
SITE_NAME,
SOURCE,
SYSTEM,
SYSTEM_NAME,
TO_TALKGROUP,
TO_TALKGROUP_ALIAS,
TO_TALKGROUP_TYPE;
}

View File

@@ -0,0 +1,111 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package channel.metadata;
import alias.Alias;
public class AttributeChangeRequest<T>
{
private Attribute mAttribute;
private T mValue;
private Alias mAlias;
/**
* Request to change the value of an attribute
* @param attribute to change
* @param value to change for the attribute
* @param alias (optional) for the value
*/
public AttributeChangeRequest(Attribute attribute, T value, Alias alias)
{
mAttribute = attribute;
mValue = value;
mAlias = alias;
}
/**
* Request to change the value of an attribute
* @param attribute to change
* @param value to change for the attribute
*/
public AttributeChangeRequest(Attribute attribute, T value)
{
this(attribute, value, null);
}
/**
* Attribute to change
*/
public Attribute getAttribute()
{
return mAttribute;
}
/**
* Value to set for the attribute
*/
public T getValue()
{
return mValue;
}
/**
* Indicates if this change request has a non-null value for the attribute
*/
public boolean hasValue()
{
return mValue != null;
}
/**
* Alias for the corresponding attribute value
*/
public Alias getAlias()
{
return mAlias;
}
/**
* Indicates if this change request contains an alias that corresponds to the attribute value
*/
public boolean hasAlias()
{
return mAlias != null;
}
public String getStringValue()
{
if(mValue instanceof String)
{
return (String)mValue;
}
return null;
}
public Long getLongValue()
{
if(mValue instanceof Long)
{
return (Long)mValue;
}
return null;
}
}

View File

@@ -0,0 +1,139 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package channel.metadata;
import controller.channel.Channel;
import sample.Listener;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ChannelMetadataModel extends AbstractTableModel implements Listener<MetadataChangeEvent>
{
public static final int COLUMN_STATE = 0;
public static final int COLUMN_PRIMARY = 1;
public static final int COLUMN_SECONDARY = 2;
public static final int COLUMN_MESSAGE = 3;
public static final int COLUMN_NETWORK = 4;
public static final int COLUMN_FREQUENCY = 5;
public static final int COLUMN_CONFIGURATION = 6;
private static final String[] COLUMNS =
{"State", "Primary", "Secondary", "Message", "Network", "Frequency", "Configuration"};
private List<Metadata> mChannelMetadata = new ArrayList();
private Map<Metadata,Channel> mMetadataChannelMap = new HashMap();
public void add(Metadata metadata, Channel channel)
{
mChannelMetadata.add(metadata);
mMetadataChannelMap.put(metadata, channel);
int index = mChannelMetadata.indexOf(metadata);
fireTableRowsInserted(index, index);
metadata.addListener(this);
}
public void remove(Metadata metadata)
{
metadata.removeListener(this);
int index = mChannelMetadata.indexOf(metadata);
mChannelMetadata.remove(metadata);
mMetadataChannelMap.remove(metadata);
fireTableRowsDeleted(index, index);
}
@Override
public int getRowCount()
{
return mChannelMetadata.size();
}
@Override
public int getColumnCount()
{
return COLUMNS.length;
}
@Override
public String getColumnName(int column)
{
return COLUMNS[column];
}
@Override
public Class<?> getColumnClass(int columnIndex)
{
return Metadata.class;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
{
return mChannelMetadata.get(rowIndex);
}
@Override
public void receive(MetadataChangeEvent metadataChangeEvent)
{
int rowIndex = mChannelMetadata.indexOf(metadataChangeEvent.getMetadata());
if(rowIndex >= 0)
{
switch(metadataChangeEvent.getAttribute())
{
case CHANNEL_CONFIGURATION_LABEL_1:
case CHANNEL_CONFIGURATION_LABEL_2:
fireTableCellUpdated(rowIndex, COLUMN_CONFIGURATION);
break;
case CHANNEL_FREQUENCY:
case CHANNEL_ID:
fireTableCellUpdated(rowIndex, COLUMN_FREQUENCY);
break;
case CHANNEL_STATE:
fireTableCellUpdated(rowIndex, COLUMN_STATE);
break;
case MESSAGE:
case MESSAGE_TYPE:
fireTableCellUpdated(rowIndex, COLUMN_MESSAGE);
break;
case NETWORK_ID_1:
case NETWORK_ID_2:
fireTableCellUpdated(rowIndex, COLUMN_NETWORK);
break;
case PRIMARY_ADDRESS_FROM:
case PRIMARY_ADDRESS_TO:
fireTableCellUpdated(rowIndex, COLUMN_PRIMARY);
break;
case SECONDARY_ADDRESS_FROM:
case SECONDARY_ADDRESS_TO:
fireTableCellUpdated(rowIndex, COLUMN_SECONDARY);
break;
}
}
}
}

View File

@@ -0,0 +1,237 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package channel.metadata;
import alias.Alias;
import icon.IconTableModel;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.text.DecimalFormat;
public class ChannelMetadataPanel extends JPanel
{
private ChannelMetadataModel mChannelMetadataModel;
private IconTableModel mIconTableModel;
private JTable mTable;
public ChannelMetadataPanel(ChannelMetadataModel channelMetadataModel, IconTableModel iconTableModel)
{
mChannelMetadataModel = channelMetadataModel;
mIconTableModel = iconTableModel;
init();
}
/**
* Initializes the panel
*/
private void init()
{
setLayout( new MigLayout( "", "[grow,fill]", "[grow,fill]") );
mTable = new JTable(mChannelMetadataModel);
mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_STATE).setCellRenderer(new StateCellRenderer());
mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_PRIMARY)
.setCellRenderer(new AliasedValueCellRenderer(Attribute.PRIMARY_ADDRESS_TO, Attribute.PRIMARY_ADDRESS_FROM));
mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_SECONDARY)
.setCellRenderer(new AliasedValueCellRenderer(Attribute.SECONDARY_ADDRESS_TO, Attribute.SECONDARY_ADDRESS_FROM));
mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_MESSAGE)
.setCellRenderer(new AliasedValueCellRenderer(Attribute.MESSAGE, Attribute.MESSAGE_TYPE));
mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_NETWORK)
.setCellRenderer(new AliasedValueCellRenderer(Attribute.NETWORK_ID_1, Attribute.NETWORK_ID_2));
mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_FREQUENCY)
.setCellRenderer(new FrequencyCellRenderer());
mTable.getColumnModel().getColumn(ChannelMetadataModel.COLUMN_CONFIGURATION)
.setCellRenderer(new AliasedValueCellRenderer(Attribute.CHANNEL_CONFIGURATION_LABEL_1,
Attribute.CHANNEL_CONFIGURATION_LABEL_2));
JScrollPane scrollPane = new JScrollPane(mTable, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
add(scrollPane);
}
/**
* Adds a selection listener to the contained metadata JTable
*/
public void addListSelectionListener(ListSelectionListener listener)
{
mTable.getSelectionModel().addListSelectionListener(listener);
}
/**
* Removes the selection listener from the contained metadata JTable
*/
public void removeListSelectionListener(ListSelectionListener listener)
{
mTable.getSelectionModel().removeListSelectionListener(listener);
}
public class AliasedValueCellRenderer extends JPanel implements TableCellRenderer
{
private JLabel mLabel1;
private JLabel mLabel2;
private Attribute mAttribute1;
private Attribute mAttribute2;
public AliasedValueCellRenderer(Attribute attribute1, Attribute attribute2)
{
attribute1 = attribute1;
attribute2 = attribute2;
setLayout( new MigLayout( "", "[grow,fill]", "[grow,fill][grow,fill]") );
mLabel1 = new JLabel();
add(mLabel1,"wrap");
mLabel2 = new JLabel();
add(mLabel2,"wrap");
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column)
{
Metadata metadata = (Metadata)value;
if(mAttribute1 != null)
{
String value1 = metadata.getValue(mAttribute1);
mLabel1.setText(value1);
Alias alias1 = metadata.getAlias(mAttribute1);
if(alias1 != null)
{
mLabel1.setIcon(mIconTableModel.getIcon(alias1.getIconName()).getIcon());
mLabel1.setForeground(alias1.getDisplayColor());
}
else
{
mLabel1.setIcon(null);
mLabel1.setForeground(mTable.getForeground());
}
}
if(mAttribute2 != null)
{
String value2 = metadata.getValue(mAttribute2);
mLabel2.setText(value2);
Alias alias2 = metadata.getAlias(mAttribute2);
if(alias2 != null)
{
mLabel2.setIcon(mIconTableModel.getIcon(alias2.getIconName()).getIcon());
mLabel2.setForeground(alias2.getDisplayColor());
}
else
{
mLabel2.setIcon(null);
mLabel2.setForeground(mTable.getForeground());
}
}
setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
return this;
}
}
public static class FrequencyCellRenderer extends JPanel implements TableCellRenderer
{
private static final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.0000" );
private JLabel mLabel1;
private JLabel mLabel2;
public FrequencyCellRenderer()
{
setLayout( new MigLayout( "", "[grow,fill]", "[grow,fill][grow,fill]") );
mLabel1 = new JLabel();
add(mLabel1,"wrap");
mLabel2 = new JLabel();
add(mLabel2,"wrap");
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column)
{
Metadata metadata = (Metadata)value;
if(metadata.hasChannelFrequency())
{
mLabel1.setText(FREQUENCY_FORMATTER.format((double)metadata.getChannelFrequency() / 1E6d));
}
else
{
mLabel1.setText(null);
}
mLabel2.setText(metadata.getChannelID());
setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
return this;
}
}
public static class StateCellRenderer extends JPanel implements TableCellRenderer
{
private static final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.0000" );
private JLabel mLabel1;
public StateCellRenderer()
{
setLayout( new MigLayout( "", "[grow,fill,align center]", "[grow,fill,align center]") );
mLabel1 = new JLabel();
add(mLabel1);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int column)
{
Metadata metadata = (Metadata)value;
mLabel1.setText(metadata.getState().getDisplayValue());
setBackground(isSelected ? table.getSelectionBackground() : table.getBackground());
return this;
}
}
}

View File

@@ -0,0 +1,61 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package channel.metadata;
import com.jidesoft.swing.JideSplitPane;
import com.jidesoft.swing.JideTabbedPane;
import icon.IconTableModel;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
public class ChannelMetadataViewer extends JPanel
{
private ChannelMetadataModel mChannelMetadataModel;
private IconTableModel mIconTableModel;
/**
* GUI panel that combines the currently decoding channels metadata table and viewers for channel details,
* messages, events, and spectral view.
*/
public ChannelMetadataViewer(ChannelMetadataModel channelMetadataModel, IconTableModel iconTableModel)
{
mChannelMetadataModel = channelMetadataModel;
mIconTableModel = iconTableModel;
}
private void init()
{
setLayout( new MigLayout( "", "[grow,fill]", "[grow,fill]") );
ChannelMetadataPanel channelMetadataPanel = new ChannelMetadataPanel(mChannelMetadataModel, mIconTableModel);
JideTabbedPane tabbedPane = new JideTabbedPane();
tabbedPane.addTab("Details", new JPanel());
tabbedPane.addTab("Events", new JPanel());
tabbedPane.addTab("Messages", new JPanel());
tabbedPane.addTab("Spectrum", new JPanel());
JideSplitPane splitPane = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);
splitPane.add(channelMetadataPanel);
splitPane.add(tabbedPane);
add(splitPane);
}
}

View File

@@ -0,0 +1,463 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package channel.metadata;
import alias.Alias;
import channel.state.State;
import sample.Broadcaster;
import sample.Listener;
public class Metadata implements Listener<AttributeChangeRequest>
{
private State mState = State.IDLE;
private String mChannelConfigurationLabel1;
private String mChannelConfigurationLabel2;
private String mChannelID;
private long mChannelFrequency;
private AliasedIdentifier mNetworkID1 = new AliasedIdentifier();
private AliasedIdentifier mNetworkID2 = new AliasedIdentifier();
//Temporal attributes
private String mMessage;
private String mMessageType;
private AliasedIdentifier mPrimaryAddressFrom = new AliasedIdentifier();
private AliasedIdentifier mPrimaryAddressTo = new AliasedIdentifier();
private AliasedIdentifier mSecondaryAddressFrom = new AliasedIdentifier();
private AliasedIdentifier mSecondaryAddressTo = new AliasedIdentifier();
private Broadcaster<MetadataChangeEvent> mMetadataChangeEventBroadcaster = new Broadcaster<>();
/**
* Channel metadata. Contains all attributes that reflect the state and current attribute values for a channel
* that is currently decoding. This metadata is intended to support any decoding channel gui components to
* graphically convey the current state of a decoding channel.
*/
public Metadata()
{
}
/**
* Sets the state of this metadata
*/
public void setState(State state)
{
mState = state;
broadcast(Attribute.CHANNEL_STATE);
}
/**
* Request to change an attribute value for this metadata. This is the primary method for updating all attributes
* in this metadata set.
*/
@Override
public void receive(AttributeChangeRequest request)
{
switch(request.getAttribute())
{
case CHANNEL_CONFIGURATION_LABEL_1:
mChannelConfigurationLabel1 = request.getStringValue();
broadcast(Attribute.CHANNEL_CONFIGURATION_LABEL_1);
break;
case CHANNEL_CONFIGURATION_LABEL_2:
mChannelConfigurationLabel2 = request.getStringValue();
broadcast(Attribute.CHANNEL_CONFIGURATION_LABEL_2);
break;
case CHANNEL_FREQUENCY:
if(request.hasValue())
{
mChannelFrequency = request.getLongValue();
}
else
{
mChannelFrequency = 0;
}
broadcast(Attribute.CHANNEL_FREQUENCY);
break;
case CHANNEL_ID:
mChannelID = request.getStringValue();
broadcast(Attribute.CHANNEL_ID);
break;
case MESSAGE:
mMessage = request.getStringValue();
broadcast(Attribute.MESSAGE);
break;
case MESSAGE_TYPE:
mMessageType = request.getStringValue();
broadcast(Attribute.MESSAGE_TYPE);
break;
case NETWORK_ID_1:
mNetworkID1.setIdentifier(request.getStringValue());
mNetworkID1.setAlias(request.getAlias());
broadcast(Attribute.NETWORK_ID_1);
break;
case NETWORK_ID_2:
mNetworkID2.setIdentifier(request.getStringValue());
mNetworkID2.setAlias(request.getAlias());
broadcast(Attribute.NETWORK_ID_2);
break;
case PRIMARY_ADDRESS_FROM:
mPrimaryAddressFrom.setIdentifier(request.getStringValue());
mPrimaryAddressFrom.setAlias(request.getAlias());
broadcast(Attribute.PRIMARY_ADDRESS_FROM);
break;
case PRIMARY_ADDRESS_TO:
mPrimaryAddressTo.setIdentifier(request.getStringValue());
mPrimaryAddressTo.setAlias(request.getAlias());
broadcast(Attribute.PRIMARY_ADDRESS_TO);
break;
case SECONDARY_ADDRESS_FROM:
mSecondaryAddressFrom.setIdentifier(request.getStringValue());
mSecondaryAddressFrom.setAlias(request.getAlias());
broadcast(Attribute.SECONDARY_ADDRESS_FROM);
break;
case SECONDARY_ADDRESS_TO:
mSecondaryAddressTo.setIdentifier(request.getStringValue());
mSecondaryAddressTo.setAlias(request.getAlias());
broadcast(Attribute.SECONDARY_ADDRESS_TO);
break;
default:
throw new IllegalArgumentException("Unrecognized Metadata Attribute: " +
request.getAttribute().name());
}
}
/**
* Broadcasts to registered listeners that an attribute has changed for this metadata
*/
private void broadcast(Attribute attribute)
{
mMetadataChangeEventBroadcaster.broadcast(new MetadataChangeEvent(this, attribute));
}
/**
* Adds the listener to receive metadata change events for this channel metadata
*/
public void addListener(Listener<MetadataChangeEvent> listener)
{
mMetadataChangeEventBroadcaster.addListener(listener);
}
/**
* Removes the listener from receiving metadata change events for this channel metadata
*/
public void removeListener(Listener<MetadataChangeEvent> listener)
{
mMetadataChangeEventBroadcaster.removeListener(listener);
}
/**
* Resets temporal attributes to null value. This only resets attributes that have a non-ull value in order to
* limit the number of change events that are produced.
*/
public void resetTemporalAttributes()
{
if(hasMessage())
{
mMessage = null;
broadcast(Attribute.MESSAGE);
}
if(hasMessageType())
{
mMessageType = null;
broadcast(Attribute.MESSAGE_TYPE);
}
if(mPrimaryAddressFrom.hasIdentifier())
{
mPrimaryAddressFrom.reset();
broadcast(Attribute.PRIMARY_ADDRESS_FROM);
}
if(mPrimaryAddressTo.hasIdentifier())
{
mPrimaryAddressTo.reset();
broadcast(Attribute.PRIMARY_ADDRESS_TO);
}
if(mSecondaryAddressFrom.hasIdentifier())
{
mSecondaryAddressFrom.reset();
broadcast(Attribute.SECONDARY_ADDRESS_FROM);
}
if(mSecondaryAddressTo.hasIdentifier())
{
mSecondaryAddressTo.reset();
broadcast(Attribute.SECONDARY_ADDRESS_TO);
}
}
/**
* Resets all attributes to empty values
*/
public void resetAllAttributes()
{
resetTemporalAttributes();
if(hasChannelConfigurationLabel1())
{
mChannelConfigurationLabel1 = null;
broadcast(Attribute.CHANNEL_CONFIGURATION_LABEL_1);
}
if(hasChannelConfigurationLabel2())
{
mChannelConfigurationLabel2 = null;
broadcast(Attribute.CHANNEL_CONFIGURATION_LABEL_2);
}
if(hasChannelID())
{
mChannelID = null;
broadcast(Attribute.CHANNEL_ID);
}
if(hasChannelFrequency())
{
mChannelFrequency = 0;
broadcast(Attribute.CHANNEL_FREQUENCY);
}
if(mNetworkID1.hasIdentifier())
{
mNetworkID1.reset();
broadcast(Attribute.NETWORK_ID_1);
}
if(mNetworkID2.hasIdentifier())
{
mNetworkID2.reset();
broadcast(Attribute.NETWORK_ID_2);
}
}
/**
* Returns the alias associated with the attribute or null
*/
public Alias getAlias(Attribute attribute)
{
switch(attribute)
{
case NETWORK_ID_1:
return getNetworkID1().getAlias();
case NETWORK_ID_2:
return getNetworkID2().getAlias();
case PRIMARY_ADDRESS_FROM:
return getPrimaryAddressFrom().getAlias();
case PRIMARY_ADDRESS_TO:
return getPrimaryAddressTo().getAlias();
case SECONDARY_ADDRESS_FROM:
return getSecondaryAddressFrom().getAlias();
case SECONDARY_ADDRESS_TO:
return getSecondaryAddressTo().getAlias();
}
return null;
}
/**
* Returns the string value for the specified attribute or null
*/
public String getValue(Attribute attribute)
{
switch(attribute)
{
case CHANNEL_CONFIGURATION_LABEL_1:
return mChannelConfigurationLabel1;
case CHANNEL_CONFIGURATION_LABEL_2:
return mChannelConfigurationLabel2;
case CHANNEL_ID:
return mChannelID;
case CHANNEL_STATE:
return mState.getDisplayValue();
case MESSAGE:
return mMessage;
case MESSAGE_TYPE:
return mMessageType;
case NETWORK_ID_1:
return mNetworkID1.getIdentifier();
case NETWORK_ID_2:
return mNetworkID2.getIdentifier();
case PRIMARY_ADDRESS_FROM:
return mPrimaryAddressFrom.getIdentifier();
case PRIMARY_ADDRESS_TO:
return mPrimaryAddressTo.getIdentifier();
case SECONDARY_ADDRESS_FROM:
return mSecondaryAddressFrom.getIdentifier();
case SECONDARY_ADDRESS_TO:
return mPrimaryAddressTo.getIdentifier();
}
return null;
}
/**
* Current channel state
*/
public State getState()
{
return mState;
}
/**
* System name from the channel configuration
*/
public String getChannelConfigurationLabel1()
{
return mChannelConfigurationLabel1;
}
/**
* Indicates if there is a non-null, non-empty channel configuration system value
*/
public boolean hasChannelConfigurationLabel1()
{
return mChannelConfigurationLabel1 != null && !mChannelConfigurationLabel1.isEmpty();
}
/**
* Site name from the channel configuration
*/
public String getChannelConfigurationLabel2()
{
return mChannelConfigurationLabel2;
}
/**
* Indicates if there is a non-null, non-empty channel configuration site value
*/
public boolean hasChannelConfigurationLabel2()
{
return mChannelConfigurationLabel2 != null && !mChannelConfigurationLabel2.isEmpty();
}
/**
* Channel ID or Channel Number
*/
public String getChannelID()
{
return mChannelID;
}
/**
* Indicates if there is a non-null, non-empty channel identifier value
*/
public boolean hasChannelID()
{
return mChannelID != null && !mChannelID.isEmpty();
}
/**
* Channel frequency
*/
public long getChannelFrequency()
{
return mChannelFrequency;
}
/**
* Indicates if there is a non-zero channel frequency value
*/
public boolean hasChannelFrequency()
{
return mChannelFrequency > 0;
}
/**
* Network Identifier 1
*/
public AliasedIdentifier getNetworkID1()
{
return mNetworkID1;
}
/**
* Network Identifier 2
*/
public AliasedIdentifier getNetworkID2()
{
return mNetworkID2;
}
/**
* Message
*/
public String getMessage()
{
return mMessage;
}
/**
* Indicates if there is a non-null, non-empty message value
*/
public boolean hasMessage()
{
return mMessage != null && !mMessage.isEmpty();
}
/**
* Message and Decoder type
*/
public String getMessageType()
{
return mMessageType;
}
/**
* Indicates if there is a non-null, non-empty message and decoder type value
*/
public boolean hasMessageType()
{
return mMessageType != null && !mMessageType.isEmpty();
}
/**
* Primary FROM Address
*/
public AliasedIdentifier getPrimaryAddressFrom()
{
return mPrimaryAddressFrom;
}
/**
* Primary TO Address
*/
public AliasedIdentifier getPrimaryAddressTo()
{
return mPrimaryAddressTo;
}
/**
* Secondary FROM Address
*/
public AliasedIdentifier getSecondaryAddressFrom()
{
return mSecondaryAddressFrom;
}
/**
* Secondary TO Address
*/
public AliasedIdentifier getSecondaryAddressTo()
{
return mSecondaryAddressTo;
}
}

View File

@@ -0,0 +1,53 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package channel.metadata;
public class MetadataChangeEvent
{
private Metadata mMetadata;
private Attribute mAttribute;
/**
* Channel metadata changed attribute event
*
* @param metadata where the change occurred
* @param attribute that changed
*/
public MetadataChangeEvent(Metadata metadata, Attribute attribute)
{
mMetadata = metadata;
mAttribute = attribute;
}
/**
* Channel metadata where the change occurred
*/
public Metadata getMetadata()
{
return mMetadata;
}
/**
* Channel metadata attribute that has changed
*/
public Attribute getAttribute()
{
return mAttribute;
}
}

View File

@@ -0,0 +1,98 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package channel.state;
import java.util.concurrent.ScheduledExecutorService;
import message.Message;
import module.decode.DecoderType;
import channel.state.DecoderStateEvent.Event;
import audio.metadata.Metadata;
import audio.metadata.MetadataType;
/**
* Basic decoder channel state - provides the minimum channel state functionality
* to support an always un-squelched audio decoder.
*/
public class AlwaysUnsquelchedDecoderState extends DecoderState
{
private DecoderType mDecoderType;
private String mChannelName;
public AlwaysUnsquelchedDecoderState( DecoderType decoderType, String channelName )
{
super( null );
mDecoderType = decoderType;
mChannelName = channelName;
}
@Override
public void init()
{
}
@Override
public void reset()
{
/* No reset actions needed */
}
@Override
public String getActivitySummary()
{
StringBuilder sb = new StringBuilder();
sb.append( "Activity Summary\n" );
sb.append( "\tDecoder:\t" );
sb.append( mDecoderType );
sb.append( "\n\n" );
return sb.toString();
}
@Override
public void receive( Message t )
{
/* Not implemented */
}
@Override
public void receiveDecoderStateEvent( DecoderStateEvent event )
{
/* Not implemented */
}
@Override
public DecoderType getDecoderType()
{
return mDecoderType;
}
@Override
public void start( ScheduledExecutorService executor )
{
broadcast( new Metadata( MetadataType.TO, mChannelName, false ) );
broadcast( new DecoderStateEvent( this, Event.ALWAYS_UNSQUELCH, State.IDLE ) );
}
@Override
public void stop()
{
}
}

View File

@@ -0,0 +1,29 @@
package channel.state;
import controller.channel.Channel.ChannelType;
/**
* Allows decoders to change the call timeout setting in the channel state
*/
public class ChangeChannelTimeoutEvent extends DecoderStateEvent
{
private ChannelType mChannelType;
private long mCallTimeout;
public ChangeChannelTimeoutEvent( Object source, ChannelType channelType, long timeout )
{
super( source, Event.CHANGE_CALL_TIMEOUT, State.IDLE );
mChannelType = channelType;
mCallTimeout = timeout;
}
public ChannelType getChannelType()
{
return mChannelType;
}
public long getCallTimeout()
{
return mCallTimeout;
}
}

View File

@@ -0,0 +1,317 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package channel.state;
import audio.AudioPanel;
import controller.channel.Channel;
import controller.channel.ChannelEvent;
import controller.channel.ChannelEvent.Event;
import controller.channel.ChannelEventListener;
import controller.channel.ChannelModel;
import controller.channel.ChannelProcessingManager;
import icon.IconManager;
import net.miginfocom.swing.MigLayout;
import settings.ColorSetting.ColorSettingName;
import settings.ColorSettingMenuItem;
import settings.ColorSettingResetMenuItem;
import settings.SettingsManager;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Gui wrapper for the list of currently processing primary channels
*/
public class ChannelList extends JPanel implements ChannelEventListener
{
private static final long serialVersionUID = 1L;
private Map<Channel, ChannelPanel> mDisplayedPanels = new ConcurrentHashMap<>();
private ChannelModel mChannelModel;
private ChannelProcessingManager mChannelProcessingManager;
private IconManager mIconManager;
private SettingsManager mSettingsManager;
private AudioPanel mAudioPanel;
public ChannelList(ChannelModel channelModel,
ChannelProcessingManager channelProcessingManager,
IconManager iconManager,
SettingsManager settingsManager,
AudioPanel audioPanel)
{
mChannelModel = channelModel;
mChannelProcessingManager = channelProcessingManager;
mIconManager = iconManager;
mSettingsManager = settingsManager;
mAudioPanel = audioPanel;
init();
}
private void init()
{
setLayout(new MigLayout("insets 0 0 0 0,wrap 1", "[grow,fill]", "[]0[]"));
setBackground(Color.BLACK);
addMouseListener(new ListSelectionListener());
add(mAudioPanel);
JSeparator separator = new JSeparator(JSeparator.HORIZONTAL);
add(separator);
}
@Override
public void channelChanged(ChannelEvent event)
{
switch(event.getEvent())
{
case NOTIFICATION_PROCESSING_START:
addChannelPanel(event.getChannel());
break;
case NOTIFICATION_PROCESSING_STOP:
removeChannelPanel(event.getChannel());
break;
default:
break;
}
}
private void addChannelPanel(Channel channel)
{
if(!mDisplayedPanels.containsKey(channel))
{
ChannelPanel channelPanel = new ChannelPanel(mChannelModel,
mChannelProcessingManager, mIconManager, mSettingsManager, channel);
add(channelPanel);
mDisplayedPanels.put(channel, channelPanel);
revalidate();
repaint();
}
}
public void removeChannelPanel(Channel channel)
{
if(mDisplayedPanels.containsKey(channel))
{
ChannelPanel panel = mDisplayedPanels.remove(channel);
if(panel != null)
{
remove(panel);
panel.dispose();
}
revalidate();
repaint();
}
}
public class ListSelectionListener implements MouseListener
{
@Override
public void mouseClicked(MouseEvent e)
{
Component component = getComponentAt(e.getPoint());
if(component instanceof ChannelPanel)
{
ChannelPanel panel = (ChannelPanel) component;
Channel channel = panel.getChannel();
if(e.getButton() == MouseEvent.BUTTON1)
{
if(channel.isSelected())
{
mChannelModel.broadcast(new ChannelEvent(channel, Event.REQUEST_DESELECT));
}
else
{
mChannelModel.broadcast(new ChannelEvent(channel, Event.REQUEST_SELECT));
}
}
else if(e.getButton() == MouseEvent.BUTTON3)
{
JPopupMenu popupMenu = new JPopupMenu();
JMenu menu = panel.getContextMenu();
if(menu != null)
{
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(ChannelList.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)
{
}
}
}

View File

@@ -0,0 +1,466 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package channel.state;
import channel.metadata.Attribute;
import controller.channel.Channel;
import controller.channel.ChannelEvent;
import controller.channel.ChannelEvent.Event;
import controller.channel.ChannelEventListener;
import controller.channel.ChannelModel;
import controller.channel.ChannelProcessingManager;
import controller.channel.ChannelUtils;
import icon.IconManager;
import module.ProcessingChain;
import module.decode.DecoderFactory;
import net.miginfocom.swing.MigLayout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import record.RecorderType;
import record.config.RecordConfiguration;
import sample.Listener;
import settings.ColorSetting;
import settings.ColorSetting.ColorSettingName;
import settings.Setting;
import settings.SettingChangeListener;
import settings.SettingsManager;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
public class ChannelPanel extends JPanel
implements Listener<Attribute>,
SettingChangeListener,
ChannelEventListener
{
private static final long serialVersionUID = 1L;
private final static Logger mLog = LoggerFactory.getLogger(ChannelPanel.class);
private Font mFontDetails = new Font(Font.MONOSPACED, Font.PLAIN, 12);
private Font mFontDecoder = new Font(Font.MONOSPACED, Font.PLAIN, 12);
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 JLabel mStateLabel;
private JLabel mSourceLabel;
private JLabel mOptionsLabel;
private JLabel mSystemLabel;
private JLabel mSiteLabel;
private JLabel mChannelLabel;
protected SettingsManager mSettingsManager;
protected IconManager mIconManager;
private Channel mChannel;
private ChannelState mChannelState;
private List<DecoderPanel> mDecoderPanels = new ArrayList<>();
private ChannelModel mChannelModel;
private ChannelProcessingManager mChannelProcessingManager;
/**
* Gui component to a channel state. Provides System, Site and channel
* name, source and channel state visualization.
*
* Updates color background according to current channel state.
*/
public ChannelPanel(ChannelModel channelModel,
ChannelProcessingManager channelProcessingManager,
IconManager iconManager,
SettingsManager settingsManager,
Channel channel)
{
mChannelModel = channelModel;
mChannelProcessingManager = channelProcessingManager;
mIconManager = iconManager;
mSettingsManager = settingsManager;
mChannel = channel;
ProcessingChain processingChain =
channelProcessingManager.getProcessingChain(mChannel);
if(processingChain != null)
{
mChannelState = processingChain.getChannelState();
init();
for(DecoderState decoderState : processingChain.getDecoderStates())
{
DecoderPanel panel = DecoderFactory.getDecoderPanel(mIconManager, mSettingsManager, decoderState);
mDecoderPanels.add(panel);
add(panel, "grow,span");
}
mChannelState.setChangedAttributeListener(this);
mChannelModel.addListener(this);
mSettingsManager.addListener(this);
}
else
{
mLog.error("Processing Chain was null!");
}
}
private void init()
{
getColors();
setLayout(new MigLayout("insets 1 1 1 1", "[grow,fill]", "[]0[]0[]"));
mSystemLabel = new JLabel(mChannel.getSystem());
mSystemLabel.setFont(mFontDetails);
mSystemLabel.setForeground(mColorLabelDetails);
add(mSystemLabel);
mSiteLabel = new JLabel(mChannel.getSite());
mSiteLabel.setFont(mFontDetails);
mSiteLabel.setForeground(mColorLabelDetails);
add(mSiteLabel);
mChannelLabel = new JLabel(mChannel.getName());
mChannelLabel.setFont(mFontDetails);
mChannelLabel.setForeground(mColorLabelDetails);
add(mChannelLabel, "wrap");
mStateLabel = new JLabel(mChannelState.getState().getDisplayValue());
mStateLabel.setFont(mFontDecoder);
mStateLabel.setForeground(mColorLabelDecoder);
add(mStateLabel);
mSourceLabel = new JLabel(mChannel.getSourceConfiguration().getDescription());
mSourceLabel.setFont(mFontDetails);
mSourceLabel.setForeground(mColorLabelDetails);
add(mSourceLabel);
StringBuilder sb = new StringBuilder();
if(!mChannel.getEventLogConfiguration().getLoggers().isEmpty())
{
sb.append("LOG ");
}
RecordConfiguration recordConfig = mChannel.getRecordConfiguration();
if(recordConfig.getRecorders().contains(RecorderType.AUDIO))
{
sb.append("AUDIO ");
}
if(recordConfig.getRecorders().contains(RecorderType.BASEBAND) ||
recordConfig.getRecorders().contains(RecorderType.TRAFFIC_BASEBAND))
{
sb.append("BASEBAND");
}
mOptionsLabel = new JLabel(sb.toString());
mOptionsLabel.setFont(mFontDetails);
mOptionsLabel.setForeground(mColorLabelAuxDecoder);
add(mOptionsLabel, "wrap");
}
public Channel getChannel()
{
return mChannel;
}
public void dispose()
{
mChannelModel.removeListener(this);
mChannelModel = null;
mChannelProcessingManager = null;
mSettingsManager.removeListener(this);
mSettingsManager = null;
mChannelState.removeChangedAttributeListener();
mChannelState = null;
mChannel = null;
for(DecoderPanel panel : mDecoderPanels)
{
panel.dispose();
remove(panel);
}
mDecoderPanels.clear();
}
@Override
public void receive(final Attribute attribute)
{
EventQueue.invokeLater(new Runnable()
{
@Override
public void run()
{
switch(attribute)
{
case CHANNEL_STATE:
if(mChannelState != null && mChannelState.getState() != null)
{
mStateLabel.setText(mChannelState.getState().getDisplayValue());
}
break;
case SOURCE:
mSourceLabel.setText(mChannel.getSourceConfiguration()
.getDescription());
break;
case CHANNEL_NAME:
case SITE_NAME:
case SYSTEM_NAME:
mChannelLabel.setText(mChannel.toString());
break;
default:
break;
}
repaint();
}
});
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
setBackground(mColorChannelBackground);
Graphics2D g2 = (Graphics2D) g.create();
Paint p = null;
final Channel channel = mChannel;
if(mChannelState != null)
{
switch(mChannelState.getState())
{
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 IDLE:
default:
p = getGradient(mColorTopIdle, mColorMiddleIdle);
break;
}
}
if(p == null)
{
p = getGradient(mColorTopIdle, mColorMiddleIdle);
}
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(getChannel() != null && getChannel().isSelected())
{
g2.setColor(mColorChannelSelected);
g2.drawRect(1, 1, getWidth() - 2, getHeight() - 2);
}
g2.dispose();
}
private void getColors()
{
mColorChannelBackground = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_BACKGROUND).getColor();
mColorChannelSelected = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_SELECTED_CHANNEL).getColor();
mColorTopCall = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CALL).getColor();
mColorMiddleCall = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CALL).getColor();
mColorTopControl = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_CONTROL).getColor();
mColorMiddleControl = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_CONTROL).getColor();
mColorTopData = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_DATA).getColor();
mColorMiddleData = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_DATA).getColor();
mColorTopFade = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_FADE).getColor();
mColorMiddleFade = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_FADE).getColor();
mColorTopIdle = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_IDLE).getColor();
mColorMiddleIdle = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_IDLE).getColor();
mColorTopNoTuner = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER).getColor();
mColorMiddleNoTuner = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER).getColor();
mColorLabelDetails = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_DETAILS).getColor();
mColorLabelDecoder = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_DECODER).getColor();
mColorLabelAuxDecoder = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_AUX_DECODER).getColor();
}
private GradientPaint getGradient(Color top, Color middle)
{
return new GradientPaint(0f, -50.0f, top,
0f, (float) getHeight() / 2.2f, middle, true);
}
@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;
default:
break;
}
}
}
@Override
public void settingDeleted(Setting setting)
{
}
public JMenu getContextMenu()
{
return ChannelUtils.getContextMenu(mChannelModel, mChannelProcessingManager,
mChannel, ChannelPanel.this);
}
@Override
public void channelChanged(ChannelEvent event)
{
if(event.getEvent() == Event.NOTIFICATION_SELECTION_CHANGE &&
event.getChannel() == mChannel)
{
repaint();
}
}
}

View File

@@ -0,0 +1,678 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package channel.state;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import channel.metadata.Attribute;
import channel.traffic.TrafficChannelManager;
import module.Module;
import module.decode.config.DecodeConfiguration;
import module.decode.event.CallEvent;
import module.decode.event.ICallEventProvider;
import channel.state.DecoderStateEvent.Event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import audio.metadata.IMetadataProvider;
import audio.metadata.Metadata;
import audio.metadata.MetadataReset;
import audio.metadata.MetadataType;
import audio.squelch.ISquelchStateProvider;
import audio.squelch.SquelchState;
import controller.channel.Channel.ChannelType;
public class ChannelState extends Module implements ICallEventProvider, IChangedAttributeProvider,
IDecoderStateEventListener, IDecoderStateEventProvider, IMetadataProvider, ISquelchStateProvider
{
private final static Logger mLog =
LoggerFactory.getLogger( ChannelState.class );
public static final long FADE_TIMEOUT_DELAY = 1200;
public static final long RESET_TIMEOUT_DELAY = 2000;
private State mState = State.IDLE;
private Listener<CallEvent> mCallEventListener;
private Listener<Attribute> mChangedAttributeListener;
private Listener<DecoderStateEvent> mDecoderStateListener;
private Listener<Metadata> mMetadataListener;
private Listener<SquelchState> mSquelchStateListener;
private DecoderStateEventReceiver mDecoderStateEventReceiver =
new DecoderStateEventReceiver();
private boolean mSquelchLocked = false;
private boolean mSelected = false;
private long mStandardChannelFadeTimeout = FADE_TIMEOUT_DELAY;
private long mTrafficChannelFadeTimeout = DecodeConfiguration.DEFAULT_CALL_TIMEOUT_SECONDS * 1000;
private long mFadeTimeout;
private long mEndTimeout;
private StateMonitor mStateMonitor = new StateMonitor();
private ScheduledFuture<?> mStateMonitorFuture;
private ChannelType mChannelType;
private TrafficChannelManager mTrafficChannelEndListener;
private CallEvent mTrafficChannelCallEvent;
/**
* Channel state tracks the overall state of all processing modules and decoders
* configured for the channel and provides squelch control and decoder state
* reset events.
*
* Uses a state enumeration that defines allowable channel state transitions in
* order to track a call or data decode event from start to finish. Uses a
* timer to monitor for inactivity and to provide a FADE period that indicates
* to the user that the activity has stopped while continuing to provide details
* about the call, before the state is reset to IDLE.
*
* Since this class is multi-threaded between decoder events and the internal
* timer that monitors for inactivity, this class uses the mState object for
* thread synchronization.
*
* State Descriptions:
* IDLE Normal state. No voice or data call activity
* CALL/DATA/ENCRYPTED/CONTROL
* Decoding states.
* 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
* TEARDOWN Indicates a traffic channel that will be torn down for reuse.
*/
public ChannelState( ChannelType channelType )
{
mChannelType = channelType;
}
/**
* Resets this channel state and prepares it for reuse.
*/
@Override
public void reset()
{
broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) );
broadcast( new MetadataReset() );
mState = State.IDLE;
broadcast( Attribute.CHANNEL_STATE );
}
@Override
public void start( ScheduledExecutorService executor )
{
if( mStateMonitorFuture == null && mStateMonitor != null )
{
if( mTrafficChannelEndListener != null )
{
setState( State.CALL );
}
try
{
mStateMonitorFuture = executor.scheduleAtFixedRate( mStateMonitor, 0, 20, TimeUnit.MILLISECONDS );
}
catch( RejectedExecutionException ree )
{
mLog.error( "state monitor scheduled task rejected", ree );
}
}
}
@Override
public void stop()
{
if( mStateMonitorFuture != null )
{
boolean success = mStateMonitorFuture.cancel( true );
if( !success )
{
mLog.error("Couldn't stop monitoring scheduled future" );
}
}
mTrafficChannelEndListener = null;
if( mTrafficChannelCallEvent != null )
{
mTrafficChannelCallEvent.end();
broadcast( mTrafficChannelCallEvent );
}
mTrafficChannelCallEvent = null;
mStateMonitorFuture = null;
mSquelchLocked = false;
}
public void dispose()
{
mCallEventListener = null;
mChangedAttributeListener = null;
mDecoderStateListener = null;
mSquelchStateListener = null;
mStateMonitor = null;
}
private boolean isStandardChannel()
{
return mChannelType == ChannelType.STANDARD;
}
private boolean isTrafficChannel()
{
return mChannelType == ChannelType.TRAFFIC;
}
public void setStandardChannelTimeout( long milliseconds )
{
mStandardChannelFadeTimeout = milliseconds;
if( mChannelType == ChannelType.STANDARD )
{
mFadeTimeout = mStandardChannelFadeTimeout;
}
}
public void setTrafficChannelTimeout( long milliseconds )
{
mTrafficChannelFadeTimeout = milliseconds;
if( mChannelType == ChannelType.TRAFFIC )
{
mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout;
}
}
public void setSelected( boolean selected )
{
mSelected = selected;
}
public boolean isSelected()
{
return mSelected;
}
public State getState()
{
return mState;
}
/**
* Updates the fade timeout threshold to the current time plus delay
*/
private void updateFadeTimeout()
{
if( isTrafficChannel() )
{
mFadeTimeout = System.currentTimeMillis() + mTrafficChannelFadeTimeout;
}
else
{
mFadeTimeout = System.currentTimeMillis() + mStandardChannelFadeTimeout;
}
}
/**
* Updates the reset timeout threshold to the current time plus delay
*/
private void updateResetTimeout()
{
if( isTrafficChannel() )
{
mEndTimeout = System.currentTimeMillis();
}
else
{
mEndTimeout = System.currentTimeMillis() + RESET_TIMEOUT_DELAY;
}
}
/**
* Broadcasts the squelch state to the registered listener
*/
protected void broadcast( SquelchState state )
{
if( mSquelchStateListener != null && !mSquelchLocked )
{
mSquelchStateListener.receive( state );
}
}
/**
* Sets the squelch state listener
*/
@Override
public void setSquelchStateListener( Listener<SquelchState> listener )
{
mSquelchStateListener = listener;
}
/**
* Removes the squelch state listener
*/
@Override
public void removeSquelchStateListener()
{
mSquelchStateListener = null;
}
/**
* Sets the channel state to the specified state, or updates the timeout
* values so that the state monitor will not change state. Broadcasts a
* squelch event when the state changes and the audio squelch state should
* change. Also broadcasts changed attribute and decoder state events so
* that external processes can maintain sync with this channel state.
*/
protected void setState( State state )
{
synchronized( mState )
{
if( state == mState )
{
if( State.CALL_STATES.contains( state ) )
{
updateFadeTimeout();
}
}
else if( state != mState && mState.canChangeTo( state ) )
{
switch( state )
{
case CONTROL:
//Don't allow traffic channels to be control channels,
//otherwise they can't transition to call tear down
if( isStandardChannel() )
{
broadcast( SquelchState.SQUELCH );
updateFadeTimeout();
mState = state;
broadcast( Attribute.CHANNEL_STATE );
}
break;
case DATA:
case ENCRYPTED:
broadcast( SquelchState.SQUELCH );
updateFadeTimeout();
mState = state;
broadcast( Attribute.CHANNEL_STATE );
break;
case CALL:
broadcast( SquelchState.UNSQUELCH );
updateFadeTimeout();
mState = state;
broadcast( Attribute.CHANNEL_STATE );
break;
case FADE:
processFadeState();
break;
case IDLE:
processIdleState();
break;
case TEARDOWN:
processTeardownState();
break;
case RESET:
mState = State.IDLE;
break;
default:
break;
}
}
else
{
// mLog.debug( "Can't change from [" + mState + "] to [" + state + "]" );
}
}
}
/**
* Sets the state and processes related actions
*/
private void processFadeState()
{
updateResetTimeout();
mState = State.FADE;
broadcast( SquelchState.SQUELCH );
broadcast( Attribute.CHANNEL_STATE );
}
private void processIdleState()
{
broadcast( SquelchState.SQUELCH );
if( mState == State.FADE )
{
broadcast( new DecoderStateEvent( this, Event.RESET, State.IDLE ) );
broadcast( new MetadataReset() );
}
mState = State.IDLE;
broadcast( Attribute.CHANNEL_STATE );
}
private void processTeardownState()
{
broadcast( SquelchState.SQUELCH );
broadcast( new MetadataReset() );
mState = State.TEARDOWN;
broadcast( Attribute.CHANNEL_STATE );
if( mTrafficChannelEndListener != null )
{
mTrafficChannelEndListener.callEnd( mTrafficChannelCallEvent.getChannel() );
}
}
/**
* Broadcasts the call event to the registered listener
*/
protected void broadcast( CallEvent event )
{
if( mCallEventListener != null )
{
mCallEventListener.receive( event );
}
}
@Override
public void addCallEventListener( Listener<CallEvent> listener )
{
mCallEventListener = listener;
}
@Override
public void removeCallEventListener( Listener<CallEvent> listener )
{
mCallEventListener = null;
}
/**
* Broadcasts the channel state attribute change event to all registered
* listeners
*/
protected void broadcast( Attribute attribute )
{
if( mChangedAttributeListener != null )
{
mChangedAttributeListener.receive( attribute );
}
}
/**
* Adds the listener to receive channel state attribute change events
*/
@Override
public void setChangedAttributeListener( Listener<Attribute> listener )
{
mChangedAttributeListener = listener;
}
/**
* Removes the listener to receive channel state attribute change events
*/
@Override
public void removeChangedAttributeListener()
{
mChangedAttributeListener = null;
}
/**
* Broadcasts a channel state event to any registered listeners
*/
protected void broadcast( DecoderStateEvent event )
{
if( mDecoderStateListener != null )
{
mDecoderStateListener.receive( event );
}
}
/**
* Adds a decoder state event listener
*/
@Override
public void setDecoderStateListener( Listener<DecoderStateEvent> listener )
{
mDecoderStateListener = listener;
}
/**
* Removes the decoder state event listener
*/
@Override
public void removeDecoderStateListener()
{
mDecoderStateListener = null;
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mDecoderStateEventReceiver;
}
/**
* Broadcasts metadata to the registered listener
*/
protected void broadcast( Metadata metadata )
{
if( mMetadataListener != null )
{
mMetadataListener.receive( metadata );
}
}
/**
* Adds a metadata listener
*/
@Override
public void setMetadataListener( Listener<Metadata> listener )
{
mMetadataListener = listener;
}
/**
* Removes the decoder state event listener
*/
@Override
public void removeMetadataListener()
{
mMetadataListener = null;
}
/**
* Registers a listener to be notified when a traffic channel call event is
* completed, so that the listener can perform call tear-down
*/
public void configureAsTrafficChannel( TrafficChannelManager manager, CallEvent callEvent )
{
mTrafficChannelEndListener = manager;
mTrafficChannelCallEvent = callEvent;
/* Broadcast the call event details as metadata for the audio manager */
String channel = mTrafficChannelCallEvent.getChannel();
if( channel != null )
{
broadcast( new Metadata( MetadataType.CHANNEL_NUMBER, channel, true ) );
}
broadcast( new Metadata( MetadataType.PROTOCOL,
mTrafficChannelCallEvent.getDecoderType().getDisplayString(), true ) );
String details = mTrafficChannelCallEvent.getDetails();
if( details != null )
{
broadcast( new Metadata( MetadataType.DETAILS, details, true ) );
}
String from = mTrafficChannelCallEvent.getFromID();
if( from != null )
{
broadcast( new Metadata( mTrafficChannelCallEvent.getFromIDType(),
from, mTrafficChannelCallEvent.getFromIDAlias(), true ) );
}
String to = mTrafficChannelCallEvent.getToID();
if( to != null )
{
broadcast( new Metadata( mTrafficChannelCallEvent.getToIDType(), to,
mTrafficChannelCallEvent.getToIDAlias(), true ) );
}
long frequency = mTrafficChannelCallEvent.getFrequency();
if( frequency > 0 )
{
broadcast( new Metadata( MetadataType.FREQUENCY, String.valueOf( frequency ), true ) );
}
/* Rebroadcast the allocation event so that the internal decoder states
* can self-configure with the call event details */
broadcast( mTrafficChannelCallEvent );
}
/**
* DecoderStateEvent receiver wrapper
*/
public class DecoderStateEventReceiver implements Listener<DecoderStateEvent>
{
@Override
public void receive( DecoderStateEvent event )
{
if( event.getSource() != this )
{
switch( event.getEvent() )
{
case ALWAYS_UNSQUELCH:
broadcast( SquelchState.UNSQUELCH );
mSquelchLocked = true;
break;
case CHANGE_CALL_TIMEOUT:
if( event instanceof ChangeChannelTimeoutEvent )
{
ChangeChannelTimeoutEvent timeout = (ChangeChannelTimeoutEvent)event;
if( timeout.getChannelType() == ChannelType.STANDARD )
{
setStandardChannelTimeout( timeout.getCallTimeout() );
}
else
{
setTrafficChannelTimeout( timeout.getCallTimeout() );
}
}
case CONTINUATION:
case DECODE:
case START:
if( State.CALL_STATES.contains( event.getState() ) )
{
setState( event.getState() );
}
break;
case END:
if( isTrafficChannel() )
{
setState( State.TEARDOWN );
}
else
{
setState( State.FADE );
}
break;
case RESET:
/* Channel State does not respond to reset events */
break;
default:
break;
}
}
}
}
/**
* Monitors decoder state events to automatically transition the channel
* state to IDLE (standard channel) or to TEARDOWN (traffic channel) when
* decoding stops or the monitored channel returns to a no signal state.
*
* Provides a FADE transition state to allow for momentary decoding dropouts
* and to allow the user access to call details for a fade period upon
* call end.
*/
public class StateMonitor implements Runnable
{
@Override
public void run()
{
try
{
synchronized( mState )
{
if( State.CALL_STATES.contains( mState ) &&
mFadeTimeout <= System.currentTimeMillis() )
{
processFadeState();
}
else if( mState == State.FADE &&
mEndTimeout <= System.currentTimeMillis() )
{
if( isTrafficChannel() )
{
processTeardownState();
}
else
{
processIdleState();
}
}
}
}
catch( Exception e )
{
mLog.error( "Exception thrown while state monitor is running " +
"- state [" + getState() +
"] current [" + System.currentTimeMillis() +
"] mResetTimeout [" + mEndTimeout +
"] mFadeTimeout [" + mFadeTimeout +
"]", e );
}
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
******************************************************************************/
package channel.state;
public interface ChannelStateSelectionListener
{
public void selected( ChannelState channelState );
}

View File

@@ -0,0 +1,163 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package channel.state;
import alias.Alias;
import channel.metadata.Attribute;
import icon.IconManager;
import sample.Listener;
import settings.ColorSetting;
import settings.ColorSetting.ColorSettingName;
import settings.Setting;
import settings.SettingChangeListener;
import settings.SettingsManager;
import javax.swing.*;
import java.awt.*;
public abstract class DecoderPanel extends JPanel
implements Listener<Attribute>, SettingChangeListener
{
private static final long serialVersionUID = 1L;
protected Font mFontDetails = new Font(Font.MONOSPACED, Font.PLAIN, 12);
protected Font mFontDecoder = new Font(Font.MONOSPACED, Font.PLAIN, 12);
protected Font mFontAuxDecoder = new Font(Font.MONOSPACED, Font.PLAIN, 10);
public static final int ICON_SIZE = 12;
protected Color mColorLabelDetails;
protected Color mColorLabelDecoder;
protected Color mColorLabelAuxDecoder;
protected IconManager mIconManager;
protected SettingsManager mSettingsManager;
protected DecoderState mDecoderState;
public DecoderPanel(IconManager iconManager, SettingsManager settingsManager, DecoderState decoderState)
{
mIconManager = iconManager;
mSettingsManager = settingsManager;
mSettingsManager.addListener(this);
mDecoderState = decoderState;
mDecoderState.setChangedAttributeListener(this);
}
public void dispose()
{
mSettingsManager.removeListener(this);
mSettingsManager = null;
mDecoderState.removeChangedAttributeListener();
mDecoderState = null;
}
public DecoderState getDecoderState()
{
return mDecoderState;
}
protected void init()
{
getColors();
setOpaque(false); //Use the parent panel's background color
}
private void getColors()
{
mColorLabelDetails = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_DETAILS).getColor();
mColorLabelDecoder = mSettingsManager.getColorSetting(
ColorSettingName.CHANNEL_STATE_LABEL_DECODER).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_DECODER:
mColorLabelDecoder = colorSetting.getColor();
repaint();
break;
case CHANNEL_STATE_LABEL_DETAILS:
mColorLabelDetails = colorSetting.getColor();
repaint();
break;
default:
break;
}
}
}
@Override
public void settingDeleted(Setting setting)
{
}
protected void setAliasLabel(final JLabel label, final Alias alias)
{
setAliasLabel(label, alias, true);
}
protected void setAliasLabel(final JLabel label, final Alias alias, boolean includeIcon)
{
if(alias != null)
{
label.setText(alias.getName());
if(includeIcon)
{
String iconName = alias.getIconName();
if(iconName != null)
{
label.setIcon(mIconManager.getIcon(iconName, IconManager.DEFAULT_ICON_SIZE));
}
else
{
label.setIcon(null);
}
}
else
{
label.setIcon(null);
}
}
else
{
label.setText("");
label.setIcon(null);
}
}
}

View File

@@ -0,0 +1,228 @@
package channel.state;
import channel.metadata.Attribute;
import message.IMessageListener;
import message.Message;
import module.Module;
import module.decode.DecoderType;
import module.decode.event.ActivitySummaryProvider;
import module.decode.event.CallEvent;
import module.decode.event.ICallEventProvider;
import sample.Broadcaster;
import sample.Listener;
import alias.AliasList;
import audio.metadata.IMetadataProvider;
import audio.metadata.Metadata;
/**
* Channel state monitors the stream of decoded messages produced by the
* decoder and broadcasts call events as they occur within the decoded message activity.
*
* Provides access to a textual activity summary of events observed.
*/
public abstract class DecoderState extends Module
implements ActivitySummaryProvider,
Listener<Message>,
ICallEventProvider,
IChangedAttributeProvider,
IDecoderStateEventListener,
IDecoderStateEventProvider,
IMessageListener,
IMetadataProvider
{
/* This has to be a broadcaster in order for references to persist */
private Broadcaster<CallEvent> mCallEventBroadcaster = new Broadcaster<>();
private Listener<Attribute> mChangedAttributeListener;
private Listener<DecoderStateEvent> mDecoderStateListener;
private Listener<Metadata> mMetadataListener;
private DecoderStateEventListener mDecoderStateEventListener =
new DecoderStateEventListener();
protected CallEvent mCurrentCallEvent;
private AliasList mAliasList;
public DecoderState( AliasList aliasList )
{
mAliasList = aliasList;
}
public abstract DecoderType getDecoderType();
public AliasList getAliasList()
{
return mAliasList;
}
public boolean hasAliasList()
{
return mAliasList != null;
}
/**
* Reset the decoder state to prepare for processing a different sample
* source
*/
public abstract void reset();
/**
* Allow the decoder to perform any setup actions
*/
public abstract void init();
/**
* Implements the IDecoderStateEventListener interface to receive state
* reset events.
*/
public abstract void receiveDecoderStateEvent( DecoderStateEvent event );
/**
* Disposes any resources or pointers held by this instance to prepare for
* garbage collection
*/
public void dispose()
{
mCallEventBroadcaster.dispose();
mCallEventBroadcaster = null;
mChangedAttributeListener = null;
mDecoderStateListener = null;
mMetadataListener = null;
mAliasList = null;
}
/**
* Activity Summary - textual summary of activity observed by the channel state.
*/
public abstract String getActivitySummary();
/**
* Broadcasts a call event to any registered listeners
*/
protected void broadcast( CallEvent event )
{
mCallEventBroadcaster.broadcast( event );
}
/**
* Adds a call event listener
*/
@Override
public void addCallEventListener( Listener<CallEvent> listener )
{
mCallEventBroadcaster.addListener( listener );
}
/**
* Removes the call event listener
*/
@Override
public void removeCallEventListener( Listener<CallEvent> listener )
{
mCallEventBroadcaster.removeListener( listener );
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mDecoderStateEventListener;
}
private class DecoderStateEventListener implements Listener<DecoderStateEvent>
{
@Override
public void receive( DecoderStateEvent event )
{
receiveDecoderStateEvent( event );
}
}
/**
* Broadcasts the channel state attribute change event to all registered
* listeners
*/
protected void broadcast( Attribute attribute )
{
if( mChangedAttributeListener != null )
{
mChangedAttributeListener.receive( attribute );
}
}
/**
* Adds the listener to receive channel state attribute change events
*/
@Override
public void setChangedAttributeListener( Listener<Attribute> listener )
{
mChangedAttributeListener = listener;
}
/**
* Removes the listener to receive channel state attribute change events
*/
@Override
public void removeChangedAttributeListener()
{
mChangedAttributeListener = null;
}
/**
* Broadcasts a channel state event to any registered listeners
*/
protected void broadcast( DecoderStateEvent event )
{
if( mDecoderStateListener != null )
{
mDecoderStateListener.receive( event );
}
}
/**
* Adds a decoder state event listener
*/
@Override
public void setDecoderStateListener( Listener<DecoderStateEvent> listener )
{
mDecoderStateListener = listener;
}
/**
* Removes the decoder state event listener
*/
@Override
public void removeDecoderStateListener()
{
mDecoderStateListener = null;
}
/**
* Broadcasts metadata to a registered listener
*/
protected void broadcast( Metadata metadata )
{
if( mMetadataListener != null )
{
mMetadataListener.receive( metadata );
}
}
@Override
public void setMetadataListener( Listener<Metadata> listener )
{
mMetadataListener = listener;
}
@Override
public void removeMetadataListener()
{
mMetadataListener = null;
}
@Override
public Listener<Message> getMessageListener()
{
return this;
}
}

View File

@@ -0,0 +1,68 @@
package channel.state;
public class DecoderStateEvent
{
private Object mSource;
private Event mEvent;
private State mState;
private long mFrequency;
public DecoderStateEvent( Object source, Event event, State state )
{
mSource = source;
mEvent = event;
mState = state;
}
public DecoderStateEvent( Object source, Event event, State state, long frequency )
{
this( source, event, state );
mFrequency = frequency;
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append( "Decoder State Event - source[" + mSource.getClass() +
"] event[" + mEvent.toString() +
"] state[" + mState.toString() +
"] frequency [" + mFrequency + "]" );
return sb.toString();
}
public Object getSource()
{
return mSource;
}
public Event getEvent()
{
return mEvent;
}
public State getState()
{
return mState;
}
public long getFrequency()
{
return mFrequency;
}
public enum Event
{
ALWAYS_UNSQUELCH,
CHANGE_CALL_TIMEOUT,
CONTINUATION,
DECODE,
END,
RESET,
SOURCE_FREQUENCY,
START,
TRAFFIC_CHANNEL_ALLOCATION;
}
}

View File

@@ -0,0 +1,10 @@
package channel.state;
import channel.metadata.Attribute;
import sample.Listener;
public interface IChangedAttributeProvider
{
public void setChangedAttributeListener( Listener<Attribute> listener );
public void removeChangedAttributeListener();
}

View File

@@ -0,0 +1,8 @@
package channel.state;
import sample.Listener;
public interface IDecoderStateEventListener
{
public Listener<DecoderStateEvent> getDecoderStateListener();
}

View File

@@ -0,0 +1,9 @@
package channel.state;
import sample.Listener;
public interface IDecoderStateEventProvider
{
public void setDecoderStateListener( Listener<DecoderStateEvent> listener );
public void removeDecoderStateListener();
}

View File

@@ -0,0 +1,120 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014-2016 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
******************************************************************************/
package channel.state;
import java.util.EnumSet;
/**
* Details the set of states for a channel and the allowable transition states
*/
public enum State
{
IDLE( "IDLE" )
{
@Override
public boolean canChangeTo( State state )
{
return state != TEARDOWN &&
state != RESET;
}
},
CALL( "CALL" )
{
@Override
public boolean canChangeTo( State state )
{
return state == CONTROL ||
state == DATA ||
state == ENCRYPTED ||
state == FADE ||
state == TEARDOWN;
}
},
DATA( "DATA" )
{
@Override
public boolean canChangeTo( State state )
{
return state == CALL ||
state == CONTROL ||
state == ENCRYPTED ||
state == FADE ||
state == TEARDOWN;
}
},
ENCRYPTED( "ENCRYPTED" )
{
@Override
public boolean canChangeTo( State state )
{
return state == FADE || state == TEARDOWN;
}
},
CONTROL( "CONTROL" )
{
@Override
public boolean canChangeTo( State state )
{
return state == IDLE ||
state == FADE;
}
},
FADE( "FADE" )
{
@Override
public boolean canChangeTo( State state )
{
return state != FADE &&
state != RESET;
}
},
RESET( "RESET" )
{
@Override
public boolean canChangeTo(State state)
{
return state == IDLE;
}
},
TEARDOWN( "TEARDOWN" )
{
@Override
public boolean canChangeTo(State state)
{
return state == RESET;
}
};
private String mDisplayValue;
public static final EnumSet<State> CALL_STATES = EnumSet.of( CALL, CONTROL, DATA, ENCRYPTED );
public static final EnumSet<State> IDLE_STATES = EnumSet.of( IDLE, FADE );
private State( String displayValue )
{
mDisplayValue = displayValue;
}
public abstract boolean canChangeTo( State state );
public String getDisplayValue()
{
return mDisplayValue;
}
}

View File

@@ -0,0 +1,45 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package channel.traffic;
import channel.state.DecoderStateEvent;
import channel.state.State;
import module.decode.event.CallEvent;
/**
* Traffic channel allocation event wraps a call event indicating the channel
* and frequency for a traffic channel allocation event, so that the traffic
* channel manager can respond and allocate a decoder channel and source if
* available, or change the wrapped call event to a call detect event and log it.
*/
public class TrafficChannelAllocationEvent extends DecoderStateEvent
{
private CallEvent mCallEvent;
public TrafficChannelAllocationEvent( Object source, CallEvent callEvent )
{
super( source, Event.TRAFFIC_CHANNEL_ALLOCATION, State.CALL );
mCallEvent = callEvent;
}
public CallEvent getCallEvent()
{
return mCallEvent;
}
}

View File

@@ -0,0 +1,477 @@
/*******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2017 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
package channel.traffic;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import channel.state.DecoderStateEvent;
import channel.state.IDecoderStateEventListener;
import module.Module;
import module.decode.config.DecodeConfiguration;
import module.decode.event.CallEvent;
import module.decode.event.CallEvent.CallEventType;
import module.decode.event.ICallEventProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import record.config.RecordConfiguration;
import sample.Listener;
import source.config.SourceConfigTuner;
import source.tuner.TunerChannel;
import source.tuner.TunerChannel.Type;
import alias.Alias;
import alias.id.priority.Priority;
import controller.channel.Channel;
import controller.channel.Channel.ChannelType;
import controller.channel.ChannelEvent;
import controller.channel.ChannelEvent.Event;
import controller.channel.ChannelModel;
import controller.channel.ChannelProcessingManager;
import controller.channel.TrafficChannelEvent;
public class TrafficChannelManager extends Module
implements ICallEventProvider, IDecoderStateEventListener
{
private final static Logger mLog = LoggerFactory.getLogger( TrafficChannelManager.class );
public static final String CHANNEL_START_REJECTED = "CHANNEL START REJECTED";
public static final String NO_TUNER_AVAILABLE = "NO TUNER AVAILABLE";
public static final String UNKNOWN_FREQUENCY = "UNKNOWN FREQUENCY";
private int mTrafficChannelPoolMaximumSize =
DecodeConfiguration.TRAFFIC_CHANNEL_LIMIT_DEFAULT;
private List<Channel> mTrafficChannelPool = new ArrayList<Channel>();
private Map<String,Channel> mTrafficChannelsInUse = new ConcurrentHashMap<String,Channel>();
private DecoderStateEventListener mEventListener = new DecoderStateEventListener();
private Listener<CallEvent> mCallEventListener;
private ChannelModel mChannelModel;
private ChannelProcessingManager mChannelProcessingManager;
private DecodeConfiguration mDecodeConfiguration;
private RecordConfiguration mRecordConfiguration;
private String mSystem;
private String mSite;
private String mAliasListName;
private CallEvent mPreviousDoNotMonitorCallEvent;
/**
* Monitors call events and allocates traffic decoder channels in response
* to traffic channel allocation call events. Manages a pool of reusable
* traffic channel allocations.
*
* @param channelModel containing channels currently in use
*
* @param channelProcessingManager
*
* @param decodeConfiguration - decoder configuration to use for each
* traffic channel allocation.
*
* @param recordConfiguration - recording options for each traffic channel
*
* @param system label to use to describe the system
*
* @param site label to use to describe the site
*
* @param aliasListName designated for the channel
*
* @param trafficChannelTimeout - millisecond call timer limit used when an
* end of call signalling event is not decoded.
*
* @param trafficChannelPoolSize - maximum number of allocated traffic channels
* in the pool
*/
public TrafficChannelManager( ChannelModel channelModel,
ChannelProcessingManager channelProcessingManager,
DecodeConfiguration decodeConfiguration,
RecordConfiguration recordConfiguration,
String system,
String site,
String aliasListName,
long trafficChannelTimeout,
int trafficChannelPoolSize )
{
mChannelModel = channelModel;
mChannelProcessingManager = channelProcessingManager;
mDecodeConfiguration = decodeConfiguration;
mRecordConfiguration = recordConfiguration;
mSystem = system;
mSite = site;
mAliasListName = aliasListName;
mTrafficChannelPoolMaximumSize = trafficChannelPoolSize;
}
@Override
public void dispose()
{
for( Channel trafficChannel: mTrafficChannelPool )
{
mChannelModel.broadcast( new ChannelEvent( trafficChannel, Event.REQUEST_DISABLE ) );
}
mTrafficChannelPool.clear();
mTrafficChannelsInUse.clear();
mCallEventListener = null;
mDecodeConfiguration = null;
mPreviousDoNotMonitorCallEvent = null;
}
/**
* Provides a channel by either reusing an existing channel or constructing
* a new one, limited by the total number of constructed channels allowed.
*
* Note: you must enforce thread safety on the mTrafficChannelsInUse
* external to this method.
*
* @param channelNumber
* @param tunerChannel
* @return
*/
private Channel getChannel( String channelNumber, TunerChannel tunerChannel )
{
Channel channel = null;
if( !mTrafficChannelsInUse.containsKey( channelNumber ) )
{
for( Channel configuredChannel: mTrafficChannelPool )
{
if( !configuredChannel.getEnabled() )
{
channel = configuredChannel;
break;
}
}
if( channel == null && mTrafficChannelPool.size() < mTrafficChannelPoolMaximumSize )
{
channel = new Channel( "Traffic", ChannelType.TRAFFIC );
channel.setDecodeConfiguration( mDecodeConfiguration );
channel.setRecordConfiguration( mRecordConfiguration );
channel.setAliasListName( mAliasListName );
mChannelModel.addChannel( channel );
mTrafficChannelPool.add( channel );
}
/* If we have a configured channel, update metadata */
if( channel != null )
{
channel.setSourceConfiguration( new SourceConfigTuner( tunerChannel ) );
channel.setSystem( mSystem );
channel.setSite( mSite );
channel.setName( channelNumber );
mChannelModel.broadcast( new ChannelEvent( channel, Event.NOTIFICATION_CONFIGURATION_CHANGE ) );
}
}
return channel;
}
/**
* Processes the event and creates a traffic channel is resources are
* available
*/
private void process( TrafficChannelAllocationEvent event )
{
CallEvent callEvent = event.getCallEvent();
/* Check for duplicate events and suppress */
synchronized( mTrafficChannelsInUse )
{
if( mTrafficChannelsInUse.containsKey( callEvent.getChannel() ) )
{
return;
}
long frequency = callEvent.getFrequency();
/* Check the from/to aliases for do not monitor priority */
if( isDoNotMonitor( callEvent ) )
{
callEvent.setCallEventType( CallEventType.CALL_DO_NOT_MONITOR );
if( isSameCallEvent( mPreviousDoNotMonitorCallEvent, callEvent ) )
{
return;
}
else
{
mPreviousDoNotMonitorCallEvent = callEvent;
}
}
else if( frequency > 0 )
{
Channel channel = getChannel( callEvent.getChannel(), new TunerChannel( Type.TRAFFIC,
frequency, mDecodeConfiguration.getDecoderType().getChannelBandwidth() ) );
if( channel != null )
{
TrafficChannelEvent trafficChannelEvent =
new TrafficChannelEvent( this, channel, Event.REQUEST_ENABLE, callEvent );
//Request to enable the channel
mChannelModel.broadcast( trafficChannelEvent );
if( channel.getEnabled() )
{
mTrafficChannelsInUse.put( callEvent.getChannel(), channel );
}
else
{
callEvent.setCallEventType( CallEventType.CALL_DETECT );
String details = callEvent.getDetails();
if(details == null || details.isEmpty())
{
callEvent.setDetails(CHANNEL_START_REJECTED);
}
else if(!details.contains(CHANNEL_START_REJECTED))
{
callEvent.setDetails(new StringBuilder(CHANNEL_START_REJECTED)
.append(" : ")
.append(callEvent.getDetails()).toString());
}
}
}
else
{
callEvent.setCallEventType( CallEventType.CALL_DETECT );
String details = callEvent.getDetails();
if(details == null || details.isEmpty())
{
callEvent.setDetails(NO_TUNER_AVAILABLE);
}
else if(!details.contains(NO_TUNER_AVAILABLE))
{
callEvent.setDetails(new StringBuilder(NO_TUNER_AVAILABLE)
.append(" : ")
.append(callEvent.getDetails()).toString());
}
}
}
else
{
callEvent.setCallEventType( CallEventType.CALL_DETECT );
String details = callEvent.getDetails();
if(details == null || details.isEmpty())
{
callEvent.setDetails(UNKNOWN_FREQUENCY);
}
else if(!details.contains(UNKNOWN_FREQUENCY))
{
callEvent.setDetails(new StringBuilder(UNKNOWN_FREQUENCY)
.append(" : ")
.append(callEvent.getDetails()).toString());
}
}
final Listener<CallEvent> listener = mCallEventListener;
if( listener != null )
{
listener.receive( callEvent );
}
}
}
/**
* Compares the call type, channel and to fields for equivalence and the
* from field for either both null, or equivalence.
*
* @param e1
* @param e2
* @return
*/
public static boolean isSameCallEvent( CallEvent e1, CallEvent e2 )
{
if( e1 == null || e2 == null )
{
return false;
}
if( e1.getCallEventType() != e2.getCallEventType() )
{
return false;
}
if( e1.getChannel() == null ||
e2.getChannel() == null ||
!e1.getChannel().contentEquals( e2.getChannel() ) )
{
return false;
}
if( e1.getToID() == null ||
e2.getToID() == null ||
!e1.getToID().contentEquals( e2.getToID() ) )
{
return false;
}
if( e1.getFromID() == null || e2.getFromID() == null )
{
if( e1.getFromID() == null && e2.getFromID() == null )
{
return true;
}
else
{
return false;
}
}
else if( e1.getFromID().contentEquals( e2.getFromID() ) )
{
return true;
}
return false;
}
/**
* Checks for aliases that contain a do not follow/process priority.
*
* @param event
* @return - true if there is an alias that has a do not process alias id
*/
private boolean isDoNotMonitor( CallEvent event )
{
Alias to = event.getToIDAlias();
if( to != null &&
to.hasCallPriority() &&
to.getCallPriority() == Priority.DO_NOT_MONITOR )
{
return true;
}
Alias from = event.getFromIDAlias();
if( from != null &&
from.hasCallPriority() &&
from.getCallPriority() == Priority.DO_NOT_MONITOR )
{
return true;
}
return false;
}
@Override
public Listener<DecoderStateEvent> getDecoderStateListener()
{
return mEventListener;
}
@Override
public void addCallEventListener( Listener<CallEvent> listener )
{
mCallEventListener = listener;
}
@Override
public void removeCallEventListener( Listener<CallEvent> listener )
{
mCallEventListener = null;
}
@Override
public void reset()
{
}
@Override
public void start( ScheduledExecutorService executor )
{
}
@Override
public void stop()
{
if( !mTrafficChannelsInUse.isEmpty() )
{
List<String> channels = new ArrayList<>();
/* Copy the keyset so we don't get concurrent modification of the map */
channels.addAll( mTrafficChannelsInUse.keySet() );
for( String channel: channels )
{
callEnd( channel );
}
}
}
/**
* Callback used by the TrafficChannelStatusListener class to signal the
* end of a an allocated traffic channel call event
*
* @param channelNumber - channel number from the call event that signaled
* the start of a traffic channel allocation
*/
public void callEnd( String channelNumber )
{
synchronized( mTrafficChannelsInUse )
{
if( channelNumber != null && mTrafficChannelsInUse.containsKey( channelNumber ) )
{
Channel channel = mTrafficChannelsInUse.get( channelNumber );
mChannelModel.broadcast( new ChannelEvent( channel, Event.REQUEST_DISABLE ) );
mTrafficChannelsInUse.remove( channelNumber );
}
}
}
/**
* Wrapper class for the decoder state event listener interface to catch
* traffic channel allocation requests
*/
public class DecoderStateEventListener implements Listener<DecoderStateEvent>
{
@Override
public void receive( DecoderStateEvent event )
{
switch( event.getEvent() )
{
case TRAFFIC_CHANNEL_ALLOCATION:
process( (TrafficChannelAllocationEvent)event );
break;
default:
break;
}
}
}
}