mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
Adds event type as a filtering option in the Events tab
This commit is contained in:
committed by
Dennis Sheirer
parent
adfc8b2d6f
commit
d246ffff30
@@ -313,6 +313,10 @@ public class DecodeEvent implements IDecodeEvent
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Decode Event type for this event.
|
||||
* @param eventType
|
||||
*/
|
||||
public DecodeEventBuilder eventType(DecodeEventType eventType) {
|
||||
mDecodeEventType = eventType;
|
||||
return this;
|
||||
@@ -374,6 +378,7 @@ public class DecodeEvent implements IDecodeEvent
|
||||
decodeEvent.setChannelDescriptor(mChannelDescriptor);
|
||||
decodeEvent.setDetails(mDetails);
|
||||
decodeEvent.setDuration(mDuration);
|
||||
decodeEvent.setEventType(mDecodeEventType);
|
||||
decodeEvent.setEventDescription(mEventDescription);
|
||||
decodeEvent.setIdentifierCollection(mIdentifierCollection);
|
||||
decodeEvent.setProtocol(mProtocol);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
* *****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.event;
|
||||
@@ -23,6 +23,7 @@ public enum DecodeEventType
|
||||
{
|
||||
AFFILIATE("Affiliate"),
|
||||
ANNOUNCEMENT("Announcement"),
|
||||
ACKNOWLEDGE("Acknowledge"),
|
||||
AUTOMATIC_REGISTRATION_SERVICE("Motorola ARS"),
|
||||
CALL("Call"),
|
||||
CALL_ENCRYPTED("Encrypted Call"),
|
||||
@@ -32,6 +33,7 @@ public enum DecodeEventType
|
||||
CALL_PATCH_GROUP_ENCRYPTED("Encrypted Patch Call"),
|
||||
CALL_ALERT("Call Alert"),
|
||||
CALL_DETECT("Call Detect"),
|
||||
CALL_IN_PROGRESS("Call In Progress"),
|
||||
CALL_DO_NOT_MONITOR("Call-Do Not Monitor"),
|
||||
CALL_END("Call End"),
|
||||
CALL_INTERCONNECT("Telephone Call"),
|
||||
|
||||
@@ -103,6 +103,7 @@ public class PlottableDecodeEvent extends DecodeEvent
|
||||
private long mTimeStart;
|
||||
private long mDuration;
|
||||
private String mEventDescription;
|
||||
private DecodeEventType mDecodeEventType;
|
||||
private IdentifierCollection mIdentifierCollection;
|
||||
private IChannelDescriptor mChannelDescriptor;
|
||||
private String mDetails;
|
||||
@@ -190,6 +191,15 @@ public class PlottableDecodeEvent extends DecodeEvent
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Decode Event type for this event.
|
||||
* @param eventType
|
||||
*/
|
||||
public PlottableDecodeEventBuilder eventType(DecodeEventType eventType) {
|
||||
mDecodeEventType = eventType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the location for the plottable event
|
||||
*/
|
||||
@@ -226,6 +236,7 @@ public class PlottableDecodeEvent extends DecodeEvent
|
||||
decodeEvent.setChannelDescriptor(mChannelDescriptor);
|
||||
decodeEvent.setDetails(mDetails);
|
||||
decodeEvent.setDuration(mDuration);
|
||||
decodeEvent.setEventType(mDecodeEventType);
|
||||
decodeEvent.setEventDescription(mEventDescription);
|
||||
decodeEvent.setIdentifierCollection(mIdentifierCollection);
|
||||
decodeEvent.setProtocol(mProtocol);
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.event.filter;
|
||||
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventType;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class DecodedCommandEventFilter extends EventFilter
|
||||
@@ -11,6 +29,7 @@ public class DecodedCommandEventFilter extends EventFilter
|
||||
super("Commands", Arrays.asList(
|
||||
DecodeEventType.ANNOUNCEMENT,
|
||||
DecodeEventType.STATION_ID,
|
||||
DecodeEventType.ACKNOWLEDGE,
|
||||
DecodeEventType.PAGE,
|
||||
DecodeEventType.QUERY,
|
||||
DecodeEventType.RADIO_CHECK,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.github.dsheirer.module.decode.event.filter.lastheard;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
|
||||
public class LastHeardFilterSet extends FilterSet<IDecodeEvent>
|
||||
{
|
||||
|
||||
public LastHeardFilterSet(AliasModel aliasModel, IconModel iconModel)
|
||||
{
|
||||
super("Last Heard To Channel");
|
||||
|
||||
addFilter(new LastHeardIconFilter("Icon", aliasModel, iconModel));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.event.filter.lastheard;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.module.HistoryModule;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventHistoryPreloadData;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEventListener;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
/**
|
||||
* Decode event history module. Maintains a history of decode events and constrains the total history size.
|
||||
*/
|
||||
public class LastHeardHistory extends HistoryModule<IDecodeEvent> implements IDecodeEventListener
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public LastHeardHistory(int historySize)
|
||||
{
|
||||
super(historySize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the IDecodeEventListener interface - delegates to receive(event) method.
|
||||
*/
|
||||
@Override
|
||||
public Listener<IDecodeEvent> getDecodeEventListener()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process preload data
|
||||
*/
|
||||
@Subscribe
|
||||
public void process(DecodeEventHistoryPreloadData preloadData)
|
||||
{
|
||||
for(IDecodeEvent decodeEvent: preloadData.getData())
|
||||
{
|
||||
receive(decodeEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a request for decode event history and posts the response back
|
||||
* to the processing chain event bus so that any of the modules can receive that history.
|
||||
*
|
||||
* Note: this is principally used by the DMR decoder for Capacity+ REST channel rotation to transfer the decode
|
||||
* event history from a traffic channel conversion to the new rest channel.
|
||||
*
|
||||
* @param request for decode event history
|
||||
*/
|
||||
@Subscribe
|
||||
public void process(LastHeardHistoryRequest request)
|
||||
{
|
||||
getInterModuleEventBus().post(new LastHeardHistoryResponse(this));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.event.filter.lastheard;
|
||||
|
||||
import io.github.dsheirer.module.ModuleEventBusMessage;
|
||||
|
||||
/**
|
||||
* Request for decode event history from a processing chain for the specified channel.
|
||||
*/
|
||||
public class LastHeardHistoryRequest extends ModuleEventBusMessage
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public LastHeardHistoryRequest()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.event.filter.lastheard;
|
||||
|
||||
import io.github.dsheirer.module.ModuleEventBusMessage;
|
||||
|
||||
/**
|
||||
* Response to decode event history request from a processing chain for the specified channel.
|
||||
*/
|
||||
public class LastHeardHistoryResponse extends ModuleEventBusMessage
|
||||
{
|
||||
private LastHeardHistory mLastHeardHistory;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param history that has a decode event history
|
||||
*/
|
||||
public LastHeardHistoryResponse(LastHeardHistory history)
|
||||
{
|
||||
mLastHeardHistory = history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode event history module
|
||||
*/
|
||||
public LastHeardHistory getDecodeEventHistory()
|
||||
{
|
||||
return mLastHeardHistory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package io.github.dsheirer.module.decode.event.filter.lastheard;
|
||||
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.icon.Icon;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class LastHeardIconFilter extends Filter<IDecodeEvent>
|
||||
{
|
||||
private AliasModel mAliasModel;
|
||||
private Map<String, FilterElement<String>> mElements = new HashMap<>();
|
||||
|
||||
public LastHeardIconFilter(String name, AliasModel aliasModel, IconModel iconModel)
|
||||
{
|
||||
super(name);
|
||||
this.mAliasModel = aliasModel;
|
||||
|
||||
ObservableList<Icon> icons = iconModel.iconsProperty();
|
||||
for (Icon icon : icons) {
|
||||
mElements.put(icon.getName(), new FilterElement<>(icon.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes(IDecodeEvent iDecodeEvent)
|
||||
{
|
||||
if (mEnabled && canProcess(iDecodeEvent))
|
||||
{
|
||||
return matchAliasToFilter(iDecodeEvent)
|
||||
.map(FilterElement::isEnabled)
|
||||
.orElse(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Optional<FilterElement<String>> matchAliasToFilter(IDecodeEvent iDecodeEvent)
|
||||
{
|
||||
IdentifierCollection identifierCollection = iDecodeEvent.getIdentifierCollection();
|
||||
List<Identifier> identifiers = identifierCollection.getIdentifiers(Role.TO);
|
||||
if (iDecodeEvent.getDuration() > 0 && identifiers != null && !identifiers.isEmpty())
|
||||
{
|
||||
AliasList aliasList = mAliasModel.getAliasList(identifierCollection);
|
||||
if (aliasList != null)
|
||||
{
|
||||
for(Identifier identifier: identifiers)
|
||||
{
|
||||
if (identifier.getForm() == Form.TALKGROUP)
|
||||
{
|
||||
List<Alias> aliases = aliasList.getAliases(identifier);
|
||||
if (!aliases.isEmpty())
|
||||
{
|
||||
for (Alias alias : aliases) {
|
||||
if (alias.getIconName() == null)
|
||||
{
|
||||
// Return any to match aliases with no icon defined.
|
||||
return Optional.of(mElements.get(IconModel.DEFAULT_ICON));
|
||||
}
|
||||
else
|
||||
{
|
||||
return Optional.of(mElements.get(alias.getIconName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess(IDecodeEvent iDecodeEvent)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
{
|
||||
return new ArrayList<>(mElements.values());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.event.filter.lastheard;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventFilterProvider;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class LastHeardModel extends AbstractTableModel implements Listener<IDecodeEvent>, EventFilterProvider<IDecodeEvent>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LastHeardModel.class);
|
||||
|
||||
public static final int COLUMN_TIME = 0;
|
||||
public static final int COLUMN_DURATION = 1;
|
||||
public static final int COLUMN_EVENT = 2;
|
||||
public static final int COLUMN_FROM_ID = 3;
|
||||
public static final int COLUMN_FROM_ALIAS = 4;
|
||||
public static final int COLUMN_TO_ID = 5;
|
||||
public static final int COLUMN_TO_ALIAS = 6;
|
||||
public static final int COLUMN_CHANNEL = 7;
|
||||
public static final int COLUMN_FREQUENCY = 8;
|
||||
public static final int COLUMN_DETAILS = 9;
|
||||
|
||||
protected int mMaxMessages = 500;
|
||||
|
||||
protected List<IDecodeEvent> mEvents = new ArrayList<>();
|
||||
protected FilterSet<IDecodeEvent> mLastSeenFilterSet;
|
||||
|
||||
protected String[] mHeaders = new String[]{"Time", "Duration", "Event", "From", "Alias", "To", "Alias", "Channel", "Frequency", "Details"};
|
||||
|
||||
public LastHeardModel(AliasModel aliasModel, IconModel iconModel)
|
||||
{
|
||||
mLastSeenFilterSet = new LastHeardFilterSet(aliasModel, iconModel);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives preference update notifications via the event bus
|
||||
* @param preferenceType that was updated
|
||||
*/
|
||||
@Subscribe
|
||||
public void preferenceUpdated(PreferenceType preferenceType)
|
||||
{
|
||||
if(preferenceType == PreferenceType.DECODE_EVENT)
|
||||
{
|
||||
for(int row = 0; row < mEvents.size(); row++)
|
||||
{
|
||||
fireTableCellUpdated(row, COLUMN_TIME);
|
||||
}
|
||||
}
|
||||
else if(preferenceType == PreferenceType.TALKGROUP_FORMAT)
|
||||
{
|
||||
for(int row = 0; row < mEvents.size(); row++)
|
||||
{
|
||||
fireTableCellUpdated(row, COLUMN_FROM_ID);
|
||||
fireTableCellUpdated(row, COLUMN_TO_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the complete list of events managed by this model.
|
||||
*/
|
||||
public List<IDecodeEvent> getEvents()
|
||||
{
|
||||
return new ArrayList<>(mEvents);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
Iterator<IDecodeEvent> it = mEvents.iterator();
|
||||
|
||||
while(it.hasNext())
|
||||
{
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
mEvents.clear();
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all events from this model and loads the events argument
|
||||
*/
|
||||
public void clearAndSet(List<IDecodeEvent> events)
|
||||
{
|
||||
mEvents.clear();
|
||||
mEvents.addAll(events);
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
dispose();
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
public int getMaxMessageCount()
|
||||
{
|
||||
return mMaxMessages;
|
||||
}
|
||||
|
||||
public void setMaxMessageCount(int count)
|
||||
{
|
||||
mMaxMessages = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds, updates or deletes the event from the model. Producers can send
|
||||
* the same call event multiple times to indicate that information in the
|
||||
* event is updated. Producers can also mark the event as invalid and the
|
||||
* event will be removed from the model.
|
||||
*/
|
||||
public void receive(final IDecodeEvent event)
|
||||
{
|
||||
if (mLastSeenFilterSet != null && !mLastSeenFilterSet.passes(event))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(!mEvents.contains(event))
|
||||
{
|
||||
mEvents.add(0, event);
|
||||
fireTableRowsInserted(0, 0);
|
||||
prune();
|
||||
}
|
||||
else
|
||||
{
|
||||
int row = mEvents.indexOf(event);
|
||||
fireTableRowsUpdated(row, row);
|
||||
}
|
||||
}
|
||||
|
||||
private void prune()
|
||||
{
|
||||
while(mEvents.size() > mMaxMessages)
|
||||
{
|
||||
int index = mEvents.size() - 1;
|
||||
mEvents.remove(index);
|
||||
fireTableRowsDeleted(index, index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterSet<IDecodeEvent> getFilterSet() {
|
||||
return mLastSeenFilterSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFilterSet(FilterSet<IDecodeEvent> filterSet) {
|
||||
this.mLastSeenFilterSet = filterSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
return mEvents.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount()
|
||||
{
|
||||
return mHeaders.length;
|
||||
}
|
||||
|
||||
public String getColumnName(int column)
|
||||
{
|
||||
return mHeaders[column];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex)
|
||||
{
|
||||
synchronized(mEvents)
|
||||
{
|
||||
IDecodeEvent event = mEvents.get(rowIndex);
|
||||
|
||||
if(event != null)
|
||||
{
|
||||
switch(columnIndex)
|
||||
{
|
||||
case COLUMN_TIME:
|
||||
return event.getTimeStart();
|
||||
case COLUMN_DURATION:
|
||||
return event.getDuration();
|
||||
case COLUMN_EVENT:
|
||||
return event.getEventDescription();
|
||||
case COLUMN_FROM_ID:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_FROM_ALIAS:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_TO_ID:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_TO_ALIAS:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_CHANNEL:
|
||||
IChannelDescriptor channelDescriptor = event.getChannelDescriptor();
|
||||
|
||||
if(channelDescriptor != null)
|
||||
{
|
||||
if(event.hasTimeslot())
|
||||
{
|
||||
return channelDescriptor.toString() + " TS:" + event.getTimeslot();
|
||||
}
|
||||
else
|
||||
{
|
||||
return channelDescriptor.toString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(event.hasTimeslot())
|
||||
{
|
||||
return "TS:" + event.getTimeslot();
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
case COLUMN_FREQUENCY:
|
||||
return event.getChannelDescriptor();
|
||||
case COLUMN_DETAILS:
|
||||
return event.getDetails();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass(int columnIndex)
|
||||
{
|
||||
switch(columnIndex)
|
||||
{
|
||||
case COLUMN_DURATION:
|
||||
case COLUMN_TIME:
|
||||
return Long.class;
|
||||
case COLUMN_DETAILS:
|
||||
case COLUMN_EVENT:
|
||||
return String.class;
|
||||
case COLUMN_FREQUENCY:
|
||||
case COLUMN_FROM_ALIAS:
|
||||
case COLUMN_FROM_ID:
|
||||
case COLUMN_TO_ALIAS:
|
||||
case COLUMN_TO_ID:
|
||||
return IdentifierCollection.class;
|
||||
case COLUMN_CHANNEL:
|
||||
return String.class;
|
||||
}
|
||||
return super.getColumnClass(columnIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.event.filter.lastheard;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.filter.AllPassFilter;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.module.ProcessingChain;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventHistory;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventModel;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventClearButton;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventClearHandler;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventFilterButton;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventFilterProvider;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import java.awt.*;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class LastHeardPanel extends JPanel implements Listener<ProcessingChain>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LastHeardPanel.class);
|
||||
private static final String TABLE_PREFERENCE_KEY = "decode.lastseen.panel";
|
||||
|
||||
private JTable mTable;
|
||||
private JTableColumnWidthMonitor mTableColumnWidthMonitor;
|
||||
private LastHeardModel mLastHeardModel;
|
||||
private DecodeEventHistory mCurrentEventHistory;
|
||||
private JScrollPane mEmptyScroller;
|
||||
private IconModel mIconModel;
|
||||
private AliasModel mAliasModel;
|
||||
private UserPreferences mUserPreferences;
|
||||
private TimestampCellRenderer mTimestampCellRenderer;
|
||||
private LastSeenManagementPanel mLastSeenManagementPanel;
|
||||
|
||||
/**
|
||||
* View for call event table
|
||||
* @param iconModel to display alias icons in table rows
|
||||
*/
|
||||
public LastHeardPanel(IconModel iconModel, UserPreferences userPreferences, AliasModel aliasModel)
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
mIconModel = iconModel;
|
||||
mAliasModel = aliasModel;
|
||||
mLastHeardModel = new LastHeardModel(aliasModel, iconModel);
|
||||
mUserPreferences = userPreferences;
|
||||
mTimestampCellRenderer = new TimestampCellRenderer();
|
||||
mTable = new JTable(mLastHeardModel);
|
||||
mTable.setAutoCreateRowSorter(true);
|
||||
mTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
|
||||
mLastSeenManagementPanel = new LastSeenManagementPanel();
|
||||
mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
|
||||
updateCellRenderers();
|
||||
|
||||
add(mLastSeenManagementPanel, "span,growx");
|
||||
|
||||
mEmptyScroller = new JScrollPane(mTable);
|
||||
add(mEmptyScroller);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives preference update notifications via the event bus
|
||||
* @param preferenceType that was updated
|
||||
*/
|
||||
@Subscribe
|
||||
public void preferenceUpdated(PreferenceType preferenceType)
|
||||
{
|
||||
if(preferenceType == PreferenceType.DECODE_EVENT || preferenceType == PreferenceType.TALKGROUP_FORMAT)
|
||||
{
|
||||
EventQueue.invokeLater(() -> mTimestampCellRenderer.updatePreferences());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCellRenderers()
|
||||
{
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TIME)
|
||||
.setCellRenderer(mTimestampCellRenderer);
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_DURATION)
|
||||
.setCellRenderer(new DurationCellRenderer());
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FROM_ID)
|
||||
.setCellRenderer(new IdentifierCellRenderer(Role.FROM));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FROM_ALIAS)
|
||||
.setCellRenderer(new AliasedIdentifierCellRenderer(Role.FROM));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TO_ID)
|
||||
.setCellRenderer(new IdentifierCellRenderer(Role.TO));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TO_ALIAS)
|
||||
.setCellRenderer(new AliasedIdentifierCellRenderer(Role.TO));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_CHANNEL)
|
||||
.setCellRenderer(new ChannelDescriptorCellRenderer());
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FREQUENCY)
|
||||
.setCellRenderer(new FrequencyCellRenderer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(final ProcessingChain processingChain)
|
||||
{
|
||||
if(mCurrentEventHistory != null)
|
||||
{
|
||||
mCurrentEventHistory.removeListener(mLastHeardModel);
|
||||
}
|
||||
|
||||
EventQueue.invokeLater(() -> {
|
||||
if(processingChain != null)
|
||||
{
|
||||
mCurrentEventHistory = processingChain.getDecodeEventHistory();
|
||||
mLastHeardModel.clearAndSet(mCurrentEventHistory.getItems());
|
||||
processingChain.getDecodeEventHistory().addListener(mLastHeardModel);
|
||||
mLastSeenManagementPanel.enableButtons();
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrentEventHistory = null;
|
||||
mLastHeardModel.clearAndSet(Collections.emptyList());
|
||||
mLastSeenManagementPanel.disableButtons();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public class LastSeenManagementPanel extends JPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private EventFilterButton<IDecodeEvent> mFilterButton;
|
||||
private EventClearButton mEventClearButton;
|
||||
|
||||
public LastSeenManagementPanel()
|
||||
{
|
||||
setLayout(new MigLayout("insets 2 2 5 5", "[]5[left,grow]", ""));
|
||||
|
||||
initializeFilterButton();
|
||||
initializeClearButton();
|
||||
disableButtons();
|
||||
|
||||
add(mFilterButton);
|
||||
add(mEventClearButton);
|
||||
}
|
||||
|
||||
public void enableButtons()
|
||||
{
|
||||
mFilterButton.setEnabled(true);
|
||||
mEventClearButton.setEnabled(true);
|
||||
}
|
||||
|
||||
public void disableButtons()
|
||||
{
|
||||
mFilterButton.setEnabled(false);
|
||||
mEventClearButton.setEnabled(false);
|
||||
}
|
||||
|
||||
private void initializeFilterButton()
|
||||
{
|
||||
EventFilterProvider<IDecodeEvent> filterProvider = (EventFilterProvider<IDecodeEvent>) mTable.getModel();
|
||||
FilterSet<IDecodeEvent> filterSet = new FilterSet<>(new AllPassFilter<>());
|
||||
if (filterProvider != null) {
|
||||
filterSet = filterProvider.getFilterSet();
|
||||
}
|
||||
mFilterButton = new EventFilterButton<>("Last Heard To Channel Filter Editor", filterSet);
|
||||
}
|
||||
|
||||
private void initializeClearButton() {
|
||||
mEventClearButton = new EventClearButton(
|
||||
((LastHeardModel) mTable.getModel()).getMaxMessageCount()
|
||||
);
|
||||
mEventClearButton.setEventClearHandler(new EventClearHandler() {
|
||||
@Override
|
||||
public void onHistoryLimitChanged(int newHistoryLimit) {
|
||||
((LastHeardModel) mTable.getModel()).setMaxMessageCount(newHistoryLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClearHistoryClicked() {
|
||||
((LastHeardModel) mTable.getModel()).clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom cell renderer for displaying identifiers from an identifier collection
|
||||
*/
|
||||
public class IdentifierCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
protected Role mRole;
|
||||
|
||||
/**
|
||||
* Constructs an instance of the cell renderer.
|
||||
*
|
||||
* @param role of the identifier
|
||||
*/
|
||||
public IdentifierCellRenderer(Role role)
|
||||
{
|
||||
mRole = role;
|
||||
setHorizontalAlignment(JLabel.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||
{
|
||||
JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
if(value instanceof IdentifierCollection)
|
||||
{
|
||||
List<Identifier> identifiers = ((IdentifierCollection)value).getIdentifiers(mRole);
|
||||
label.setText(format(identifiers));
|
||||
}
|
||||
else
|
||||
{
|
||||
label.setText(null);
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a list of identifiers as a comma separated list of values
|
||||
* @param identifiers to format
|
||||
* @return formatted list or null
|
||||
*/
|
||||
protected String format(List<Identifier> identifiers)
|
||||
{
|
||||
if(identifiers == null || identifiers.isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(Identifier identifier: identifiers)
|
||||
{
|
||||
if(sb.length() > 0)
|
||||
{
|
||||
sb.append(",");
|
||||
}
|
||||
|
||||
if(identifier.getForm() == Form.TALKGROUP)
|
||||
{
|
||||
sb.append(mUserPreferences.getTalkgroupFormatPreference().format(identifier));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(identifier);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cell renderer for identifier aliases
|
||||
*/
|
||||
public class AliasedIdentifierCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
private Role mRole;
|
||||
|
||||
/**
|
||||
* Constructs an instance of the cell renderer.
|
||||
*
|
||||
* @param role of the identifier
|
||||
*/
|
||||
public AliasedIdentifierCellRenderer(Role role)
|
||||
{
|
||||
mRole = role;
|
||||
setHorizontalAlignment(JLabel.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||
{
|
||||
JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
Color color = mTable.getForeground();
|
||||
ImageIcon icon = null;
|
||||
String text = null;
|
||||
|
||||
if(value instanceof IdentifierCollection)
|
||||
{
|
||||
IdentifierCollection identifierCollection = (IdentifierCollection)value;
|
||||
List<Identifier> identifiers = identifierCollection.getIdentifiers(mRole);
|
||||
|
||||
if(identifiers != null && !identifiers.isEmpty())
|
||||
{
|
||||
AliasList aliasList = mAliasModel.getAliasList(identifierCollection);
|
||||
|
||||
if(aliasList != null)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(Identifier identifier: identifiers)
|
||||
{
|
||||
List<Alias> aliases = aliasList.getAliases(identifier);
|
||||
|
||||
if(!aliases.isEmpty())
|
||||
{
|
||||
if(sb.length() > 0)
|
||||
{
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
color = aliases.get(0).getDisplayColor();
|
||||
icon = mIconModel.getIcon(aliases.get(0).getIconName(), IconModel.DEFAULT_ICON_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
text = sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label.setText(text);
|
||||
label.setForeground(color);
|
||||
label.setIcon(icon);
|
||||
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
public class TimestampCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
private SimpleDateFormat mTimestampFormatter;
|
||||
|
||||
public TimestampCellRenderer()
|
||||
{
|
||||
setHorizontalAlignment(JLabel.CENTER);
|
||||
updatePreferences();
|
||||
}
|
||||
|
||||
public void updatePreferences()
|
||||
{
|
||||
mTimestampFormatter = mUserPreferences.getDecodeEventPreference().getTimestampFormat().getFormatter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||
{
|
||||
JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
if(value instanceof Long)
|
||||
{
|
||||
label.setText(mTimestampFormatter.format(new Date((long)value)));
|
||||
}
|
||||
else
|
||||
{
|
||||
label.setText(null);
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
public class DurationCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
private DecimalFormat mDecimalFormat = new DecimalFormat("0.0");
|
||||
|
||||
public DurationCellRenderer()
|
||||
{
|
||||
setHorizontalAlignment(JLabel.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||
{
|
||||
JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
String formatted = null;
|
||||
|
||||
if(value instanceof Long)
|
||||
{
|
||||
long duration = (long)value;
|
||||
|
||||
if(duration > 0)
|
||||
{
|
||||
formatted = mDecimalFormat.format((double)duration / 1e3d);
|
||||
}
|
||||
}
|
||||
|
||||
label.setText(formatted);
|
||||
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frequency value cell renderer
|
||||
*/
|
||||
public class FrequencyCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
private DecimalFormat mFrequencyFormatter = new DecimalFormat("0.00000");
|
||||
|
||||
public FrequencyCellRenderer()
|
||||
{
|
||||
setHorizontalAlignment(JLabel.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||
{
|
||||
JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
String formatted = null;
|
||||
|
||||
if(value instanceof IChannelDescriptor)
|
||||
{
|
||||
IChannelDescriptor channelDescriptor = (IChannelDescriptor)value;
|
||||
|
||||
long frequency = channelDescriptor.getDownlinkFrequency();
|
||||
|
||||
if(frequency > 0)
|
||||
{
|
||||
formatted = mFrequencyFormatter.format(frequency / 1e6d);
|
||||
}
|
||||
}
|
||||
|
||||
label.setText(formatted);
|
||||
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel descriptor value cell renderer
|
||||
*/
|
||||
public class ChannelDescriptorCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
public ChannelDescriptorCellRenderer()
|
||||
{
|
||||
setHorizontalAlignment(JLabel.CENTER);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.github.dsheirer.module.decode.event.filter.lastseen;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
|
||||
public class LastSeenFilterSet extends FilterSet<IDecodeEvent>
|
||||
{
|
||||
|
||||
public LastSeenFilterSet(AliasModel aliasModel, IconModel iconModel)
|
||||
{
|
||||
super("Last Seen From Radio");
|
||||
|
||||
addFilter(new LastSeenIconFilter("Icon", aliasModel, iconModel));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.event.filter.lastseen;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.module.HistoryModule;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventHistoryPreloadData;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEventListener;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
/**
|
||||
* Decode event history module. Maintains a history of decode events and constrains the total history size.
|
||||
*/
|
||||
public class LastSeenHistory extends HistoryModule<IDecodeEvent> implements IDecodeEventListener
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public LastSeenHistory(int historySize)
|
||||
{
|
||||
super(historySize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the IDecodeEventListener interface - delegates to receive(event) method.
|
||||
*/
|
||||
@Override
|
||||
public Listener<IDecodeEvent> getDecodeEventListener()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process preload data
|
||||
*/
|
||||
@Subscribe
|
||||
public void process(DecodeEventHistoryPreloadData preloadData)
|
||||
{
|
||||
for(IDecodeEvent decodeEvent: preloadData.getData())
|
||||
{
|
||||
receive(decodeEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a request for decode event history and posts the response back
|
||||
* to the processing chain event bus so that any of the modules can receive that history.
|
||||
*
|
||||
* Note: this is principally used by the DMR decoder for Capacity+ REST channel rotation to transfer the decode
|
||||
* event history from a traffic channel conversion to the new rest channel.
|
||||
*
|
||||
* @param request for decode event history
|
||||
*/
|
||||
@Subscribe
|
||||
public void process(LastSeenHistoryRequest request)
|
||||
{
|
||||
getInterModuleEventBus().post(new LastSeenHistoryResponse(this));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.event.filter.lastseen;
|
||||
|
||||
import io.github.dsheirer.module.ModuleEventBusMessage;
|
||||
|
||||
/**
|
||||
* Request for decode event history from a processing chain for the specified channel.
|
||||
*/
|
||||
public class LastSeenHistoryRequest extends ModuleEventBusMessage
|
||||
{
|
||||
/**
|
||||
* Constructs an instance
|
||||
*/
|
||||
public LastSeenHistoryRequest()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2020 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
package io.github.dsheirer.module.decode.event.filter.lastseen;
|
||||
|
||||
import io.github.dsheirer.module.ModuleEventBusMessage;
|
||||
|
||||
/**
|
||||
* Response to decode event history request from a processing chain for the specified channel.
|
||||
*/
|
||||
public class LastSeenHistoryResponse extends ModuleEventBusMessage
|
||||
{
|
||||
private LastSeenHistory mLastSeenHistory;
|
||||
|
||||
/**
|
||||
* Constructs an instance
|
||||
* @param history that has a decode event history
|
||||
*/
|
||||
public LastSeenHistoryResponse(LastSeenHistory history)
|
||||
{
|
||||
mLastSeenHistory = history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode event history module
|
||||
*/
|
||||
public LastSeenHistory getDecodeEventHistory()
|
||||
{
|
||||
return mLastSeenHistory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package io.github.dsheirer.module.decode.event.filter.lastseen;
|
||||
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.filter.Filter;
|
||||
import io.github.dsheirer.filter.FilterElement;
|
||||
import io.github.dsheirer.icon.Icon;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class LastSeenIconFilter extends Filter<IDecodeEvent>
|
||||
{
|
||||
private AliasModel mAliasModel;
|
||||
private Map<String, FilterElement<String>> mElements = new HashMap<>();
|
||||
|
||||
public LastSeenIconFilter(String name, AliasModel aliasModel, IconModel iconModel)
|
||||
{
|
||||
super(name);
|
||||
this.mAliasModel = aliasModel;
|
||||
|
||||
ObservableList<Icon> icons = iconModel.iconsProperty();
|
||||
for (Icon icon : icons) {
|
||||
mElements.put(icon.getName(), new FilterElement<>(icon.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean passes(IDecodeEvent iDecodeEvent)
|
||||
{
|
||||
if (mEnabled && canProcess(iDecodeEvent))
|
||||
{
|
||||
return matchAliasToFilter(iDecodeEvent)
|
||||
.map(FilterElement::isEnabled)
|
||||
.orElse(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Optional<FilterElement<String>> matchAliasToFilter(IDecodeEvent iDecodeEvent)
|
||||
{
|
||||
IdentifierCollection identifierCollection = iDecodeEvent.getIdentifierCollection();
|
||||
List<Identifier> identifiers = identifierCollection.getIdentifiers();
|
||||
if (identifiers != null && !identifiers.isEmpty())
|
||||
{
|
||||
AliasList aliasList = mAliasModel.getAliasList(identifierCollection);
|
||||
if (aliasList != null)
|
||||
{
|
||||
for(Identifier identifier: identifiers)
|
||||
{
|
||||
if (identifier.getForm() == Form.RADIO)
|
||||
{
|
||||
List<Alias> aliases = aliasList.getAliases(identifier);
|
||||
if (!aliases.isEmpty())
|
||||
{
|
||||
for (Alias alias : aliases) {
|
||||
if (alias.getIconName() == null)
|
||||
{
|
||||
// Return any to match aliases with no icon defined.
|
||||
return Optional.of(mElements.get(IconModel.DEFAULT_ICON));
|
||||
}
|
||||
else
|
||||
{
|
||||
return Optional.of(mElements.get(alias.getIconName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProcess(IDecodeEvent iDecodeEvent)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FilterElement<?>> getFilterElements()
|
||||
{
|
||||
return new ArrayList<>(mElements.values());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.event.filter.lastseen;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventFilterProvider;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class LastSeenModel extends AbstractTableModel implements Listener<IDecodeEvent>, EventFilterProvider<IDecodeEvent>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final int COLUMN_TIME = 0;
|
||||
public static final int COLUMN_DURATION = 1;
|
||||
public static final int COLUMN_EVENT = 2;
|
||||
public static final int COLUMN_FROM_ID = 3;
|
||||
public static final int COLUMN_FROM_ALIAS = 4;
|
||||
public static final int COLUMN_TO_ID = 5;
|
||||
public static final int COLUMN_TO_ALIAS = 6;
|
||||
public static final int COLUMN_CHANNEL = 7;
|
||||
public static final int COLUMN_FREQUENCY = 8;
|
||||
public static final int COLUMN_DETAILS = 9;
|
||||
|
||||
protected int mMaxMessages = 500;
|
||||
|
||||
protected List<IDecodeEvent> mEvents = new ArrayList<>();
|
||||
protected FilterSet<IDecodeEvent> mLastSeenFilterSet;
|
||||
|
||||
protected String[] mHeaders = new String[]{"Time", "Duration", "Event", "From", "Alias", "To", "Alias", "Channel", "Frequency", "Details"};
|
||||
|
||||
public LastSeenModel(AliasModel aliasModel, IconModel iconModel)
|
||||
{
|
||||
mLastSeenFilterSet = new LastSeenFilterSet(aliasModel, iconModel);
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives preference update notifications via the event bus
|
||||
* @param preferenceType that was updated
|
||||
*/
|
||||
@Subscribe
|
||||
public void preferenceUpdated(PreferenceType preferenceType)
|
||||
{
|
||||
if(preferenceType == PreferenceType.DECODE_EVENT)
|
||||
{
|
||||
for(int row = 0; row < mEvents.size(); row++)
|
||||
{
|
||||
fireTableCellUpdated(row, COLUMN_TIME);
|
||||
}
|
||||
}
|
||||
else if(preferenceType == PreferenceType.TALKGROUP_FORMAT)
|
||||
{
|
||||
for(int row = 0; row < mEvents.size(); row++)
|
||||
{
|
||||
fireTableCellUpdated(row, COLUMN_FROM_ID);
|
||||
fireTableCellUpdated(row, COLUMN_TO_ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Access the complete list of events managed by this model.
|
||||
*/
|
||||
public List<IDecodeEvent> getEvents()
|
||||
{
|
||||
return new ArrayList<>(mEvents);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
Iterator<IDecodeEvent> it = mEvents.iterator();
|
||||
|
||||
while(it.hasNext())
|
||||
{
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
mEvents.clear();
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all events from this model and loads the events argument
|
||||
*/
|
||||
public void clearAndSet(List<IDecodeEvent> events)
|
||||
{
|
||||
mEvents.clear();
|
||||
mEvents.addAll(events);
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
dispose();
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
public int getMaxMessageCount()
|
||||
{
|
||||
return mMaxMessages;
|
||||
}
|
||||
|
||||
public void setMaxMessageCount(int count)
|
||||
{
|
||||
mMaxMessages = count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds, updates or deletes the event from the model. Producers can send
|
||||
* the same call event multiple times to indicate that information in the
|
||||
* event is updated. Producers can also mark the event as invalid and the
|
||||
* event will be removed from the model.
|
||||
*/
|
||||
public void receive(final IDecodeEvent event)
|
||||
{
|
||||
if (mLastSeenFilterSet != null && !mLastSeenFilterSet.passes(event))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(!mEvents.contains(event))
|
||||
{
|
||||
mEvents.add(0, event);
|
||||
fireTableRowsInserted(0, 0);
|
||||
prune();
|
||||
}
|
||||
else
|
||||
{
|
||||
int row = mEvents.indexOf(event);
|
||||
fireTableRowsUpdated(row, row);
|
||||
}
|
||||
}
|
||||
|
||||
private void prune()
|
||||
{
|
||||
while(mEvents.size() > mMaxMessages)
|
||||
{
|
||||
int index = mEvents.size() - 1;
|
||||
mEvents.remove(index);
|
||||
fireTableRowsDeleted(index, index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterSet<IDecodeEvent> getFilterSet() {
|
||||
return mLastSeenFilterSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFilterSet(FilterSet<IDecodeEvent> filterSet) {
|
||||
this.mLastSeenFilterSet = filterSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount()
|
||||
{
|
||||
return mEvents.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount()
|
||||
{
|
||||
return mHeaders.length;
|
||||
}
|
||||
|
||||
public String getColumnName(int column)
|
||||
{
|
||||
return mHeaders[column];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex)
|
||||
{
|
||||
synchronized(mEvents)
|
||||
{
|
||||
IDecodeEvent event = mEvents.get(rowIndex);
|
||||
|
||||
if(event != null)
|
||||
{
|
||||
switch(columnIndex)
|
||||
{
|
||||
case COLUMN_TIME:
|
||||
return event.getTimeStart();
|
||||
case COLUMN_DURATION:
|
||||
return event.getDuration();
|
||||
case COLUMN_EVENT:
|
||||
return event.getEventDescription();
|
||||
case COLUMN_FROM_ID:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_FROM_ALIAS:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_TO_ID:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_TO_ALIAS:
|
||||
return event.getIdentifierCollection();
|
||||
case COLUMN_CHANNEL:
|
||||
IChannelDescriptor channelDescriptor = event.getChannelDescriptor();
|
||||
|
||||
if(channelDescriptor != null)
|
||||
{
|
||||
if(event.hasTimeslot())
|
||||
{
|
||||
return channelDescriptor.toString() + " TS:" + event.getTimeslot();
|
||||
}
|
||||
else
|
||||
{
|
||||
return channelDescriptor.toString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(event.hasTimeslot())
|
||||
{
|
||||
return "TS:" + event.getTimeslot();
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
case COLUMN_FREQUENCY:
|
||||
return event.getChannelDescriptor();
|
||||
case COLUMN_DETAILS:
|
||||
return event.getDetails();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass(int columnIndex)
|
||||
{
|
||||
switch(columnIndex)
|
||||
{
|
||||
case COLUMN_DURATION:
|
||||
case COLUMN_TIME:
|
||||
return Long.class;
|
||||
case COLUMN_DETAILS:
|
||||
case COLUMN_EVENT:
|
||||
return String.class;
|
||||
case COLUMN_FREQUENCY:
|
||||
case COLUMN_FROM_ALIAS:
|
||||
case COLUMN_FROM_ID:
|
||||
case COLUMN_TO_ALIAS:
|
||||
case COLUMN_TO_ID:
|
||||
return IdentifierCollection.class;
|
||||
case COLUMN_CHANNEL:
|
||||
return String.class;
|
||||
}
|
||||
return super.getColumnClass(columnIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
/*
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
*
|
||||
*
|
||||
*/
|
||||
package io.github.dsheirer.module.decode.event.filter.lastseen;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
import io.github.dsheirer.alias.Alias;
|
||||
import io.github.dsheirer.alias.AliasList;
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
import io.github.dsheirer.channel.IChannelDescriptor;
|
||||
import io.github.dsheirer.eventbus.MyEventBus;
|
||||
import io.github.dsheirer.filter.AllPassFilter;
|
||||
import io.github.dsheirer.filter.FilterSet;
|
||||
import io.github.dsheirer.icon.IconModel;
|
||||
import io.github.dsheirer.identifier.Form;
|
||||
import io.github.dsheirer.identifier.Identifier;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.identifier.Role;
|
||||
import io.github.dsheirer.module.ProcessingChain;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventHistory;
|
||||
import io.github.dsheirer.module.decode.event.DecodeEventModel;
|
||||
import io.github.dsheirer.module.decode.event.IDecodeEvent;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventClearButton;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventClearHandler;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventFilterButton;
|
||||
import io.github.dsheirer.module.decode.event.filter.EventFilterProvider;
|
||||
import io.github.dsheirer.preference.PreferenceType;
|
||||
import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import java.awt.*;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class LastSeenPanel extends JPanel implements Listener<ProcessingChain>
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final static Logger mLog = LoggerFactory.getLogger(LastSeenPanel.class);
|
||||
private static final String TABLE_PREFERENCE_KEY = "decode.lastseen.panel";
|
||||
|
||||
private JTable mTable;
|
||||
private JTableColumnWidthMonitor mTableColumnWidthMonitor;
|
||||
private LastSeenModel mLastSeenModel;
|
||||
private DecodeEventHistory mCurrentEventHistory;
|
||||
private JScrollPane mEmptyScroller;
|
||||
private IconModel mIconModel;
|
||||
private AliasModel mAliasModel;
|
||||
private UserPreferences mUserPreferences;
|
||||
private TimestampCellRenderer mTimestampCellRenderer;
|
||||
private LastSeenManagementPanel mLastSeenManagementPanel;
|
||||
|
||||
/**
|
||||
* View for call event table
|
||||
* @param iconModel to display alias icons in table rows
|
||||
*/
|
||||
public LastSeenPanel(IconModel iconModel, UserPreferences userPreferences, AliasModel aliasModel)
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().register(this);
|
||||
|
||||
setLayout(new MigLayout("insets 0 0 0 0", "[grow,fill]", "[grow,fill]"));
|
||||
mIconModel = iconModel;
|
||||
mAliasModel = aliasModel;
|
||||
mLastSeenModel = new LastSeenModel(aliasModel, iconModel);
|
||||
mUserPreferences = userPreferences;
|
||||
mTimestampCellRenderer = new TimestampCellRenderer();
|
||||
mTable = new JTable(mLastSeenModel);
|
||||
mTable.setAutoCreateRowSorter(true);
|
||||
mTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
|
||||
mLastSeenManagementPanel = new LastSeenManagementPanel();
|
||||
mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
|
||||
updateCellRenderers();
|
||||
|
||||
add(mLastSeenManagementPanel, "span,growx");
|
||||
|
||||
mEmptyScroller = new JScrollPane(mTable);
|
||||
add(mEmptyScroller);
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
MyEventBus.getGlobalEventBus().unregister(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives preference update notifications via the event bus
|
||||
* @param preferenceType that was updated
|
||||
*/
|
||||
@Subscribe
|
||||
public void preferenceUpdated(PreferenceType preferenceType)
|
||||
{
|
||||
if(preferenceType == PreferenceType.DECODE_EVENT || preferenceType == PreferenceType.TALKGROUP_FORMAT)
|
||||
{
|
||||
EventQueue.invokeLater(() -> mTimestampCellRenderer.updatePreferences());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCellRenderers()
|
||||
{
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TIME)
|
||||
.setCellRenderer(mTimestampCellRenderer);
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_DURATION)
|
||||
.setCellRenderer(new DurationCellRenderer());
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FROM_ID)
|
||||
.setCellRenderer(new IdentifierCellRenderer(Role.FROM));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FROM_ALIAS)
|
||||
.setCellRenderer(new AliasedIdentifierCellRenderer(Role.FROM));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TO_ID)
|
||||
.setCellRenderer(new IdentifierCellRenderer(Role.TO));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_TO_ALIAS)
|
||||
.setCellRenderer(new AliasedIdentifierCellRenderer(Role.TO));
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_CHANNEL)
|
||||
.setCellRenderer(new ChannelDescriptorCellRenderer());
|
||||
mTable.getColumnModel().getColumn(DecodeEventModel.COLUMN_FREQUENCY)
|
||||
.setCellRenderer(new FrequencyCellRenderer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(final ProcessingChain processingChain)
|
||||
{
|
||||
if(mCurrentEventHistory != null)
|
||||
{
|
||||
mCurrentEventHistory.removeListener(mLastSeenModel);
|
||||
}
|
||||
|
||||
EventQueue.invokeLater(() -> {
|
||||
if(processingChain != null)
|
||||
{
|
||||
mCurrentEventHistory = processingChain.getDecodeEventHistory();
|
||||
mLastSeenModel.clearAndSet(mCurrentEventHistory.getItems());
|
||||
processingChain.getDecodeEventHistory().addListener(mLastSeenModel);
|
||||
mLastSeenManagementPanel.enableButtons();
|
||||
}
|
||||
else
|
||||
{
|
||||
mCurrentEventHistory = null;
|
||||
mLastSeenModel.clearAndSet(Collections.emptyList());
|
||||
mLastSeenManagementPanel.disableButtons();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public class LastSeenManagementPanel extends JPanel
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private EventFilterButton<IDecodeEvent> mFilterButton;
|
||||
private EventClearButton mEventClearButton;
|
||||
|
||||
public LastSeenManagementPanel()
|
||||
{
|
||||
setLayout(new MigLayout("insets 2 2 5 5", "[]5[left,grow]", ""));
|
||||
|
||||
initializeFilterButton();
|
||||
initializeClearButton();
|
||||
disableButtons();
|
||||
|
||||
add(mFilterButton);
|
||||
add(mEventClearButton);
|
||||
}
|
||||
|
||||
public void enableButtons()
|
||||
{
|
||||
mFilterButton.setEnabled(true);
|
||||
mEventClearButton.setEnabled(true);
|
||||
}
|
||||
|
||||
public void disableButtons()
|
||||
{
|
||||
mFilterButton.setEnabled(false);
|
||||
mEventClearButton.setEnabled(false);
|
||||
}
|
||||
|
||||
private void initializeFilterButton()
|
||||
{
|
||||
EventFilterProvider<IDecodeEvent> filterProvider = (EventFilterProvider<IDecodeEvent>) mTable.getModel();
|
||||
FilterSet<IDecodeEvent> filterSet = new FilterSet<>(new AllPassFilter<>());
|
||||
if (filterProvider != null) {
|
||||
filterSet = filterProvider.getFilterSet();
|
||||
}
|
||||
mFilterButton = new EventFilterButton<>("Last Seen From Radio Filter Editor", filterSet);
|
||||
}
|
||||
|
||||
private void initializeClearButton() {
|
||||
mEventClearButton = new EventClearButton(
|
||||
((LastSeenModel) mTable.getModel()).getMaxMessageCount()
|
||||
);
|
||||
mEventClearButton.setEventClearHandler(new EventClearHandler() {
|
||||
@Override
|
||||
public void onHistoryLimitChanged(int newHistoryLimit) {
|
||||
((LastSeenModel) mTable.getModel()).setMaxMessageCount(newHistoryLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClearHistoryClicked() {
|
||||
((LastSeenModel) mTable.getModel()).clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom cell renderer for displaying identifiers from an identifier collection
|
||||
*/
|
||||
public class IdentifierCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
protected Role mRole;
|
||||
|
||||
/**
|
||||
* Constructs an instance of the cell renderer.
|
||||
*
|
||||
* @param role of the identifier
|
||||
*/
|
||||
public IdentifierCellRenderer(Role role)
|
||||
{
|
||||
mRole = role;
|
||||
setHorizontalAlignment(JLabel.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||
{
|
||||
JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
if(value instanceof IdentifierCollection)
|
||||
{
|
||||
List<Identifier> identifiers = ((IdentifierCollection)value).getIdentifiers(mRole);
|
||||
label.setText(format(identifiers));
|
||||
}
|
||||
else
|
||||
{
|
||||
label.setText(null);
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a list of identifiers as a comma separated list of values
|
||||
* @param identifiers to format
|
||||
* @return formatted list or null
|
||||
*/
|
||||
protected String format(List<Identifier> identifiers)
|
||||
{
|
||||
if(identifiers == null || identifiers.isEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(Identifier identifier: identifiers)
|
||||
{
|
||||
if(sb.length() > 0)
|
||||
{
|
||||
sb.append(",");
|
||||
}
|
||||
|
||||
if(identifier.getForm() == Form.TALKGROUP)
|
||||
{
|
||||
sb.append(mUserPreferences.getTalkgroupFormatPreference().format(identifier));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.append(identifier);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cell renderer for identifier aliases
|
||||
*/
|
||||
public class AliasedIdentifierCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
private Role mRole;
|
||||
|
||||
/**
|
||||
* Constructs an instance of the cell renderer.
|
||||
*
|
||||
* @param role of the identifier
|
||||
*/
|
||||
public AliasedIdentifierCellRenderer(Role role)
|
||||
{
|
||||
mRole = role;
|
||||
setHorizontalAlignment(JLabel.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||
{
|
||||
JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
Color color = mTable.getForeground();
|
||||
ImageIcon icon = null;
|
||||
String text = null;
|
||||
|
||||
if(value instanceof IdentifierCollection)
|
||||
{
|
||||
IdentifierCollection identifierCollection = (IdentifierCollection)value;
|
||||
List<Identifier> identifiers = identifierCollection.getIdentifiers(mRole);
|
||||
|
||||
if(identifiers != null && !identifiers.isEmpty())
|
||||
{
|
||||
AliasList aliasList = mAliasModel.getAliasList(identifierCollection);
|
||||
|
||||
if(aliasList != null)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(Identifier identifier: identifiers)
|
||||
{
|
||||
List<Alias> aliases = aliasList.getAliases(identifier);
|
||||
|
||||
if(!aliases.isEmpty())
|
||||
{
|
||||
if(sb.length() > 0)
|
||||
{
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(Joiner.on(", ").skipNulls().join(aliases));
|
||||
color = aliases.get(0).getDisplayColor();
|
||||
icon = mIconModel.getIcon(aliases.get(0).getIconName(), IconModel.DEFAULT_ICON_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
text = sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label.setText(text);
|
||||
label.setForeground(color);
|
||||
label.setIcon(icon);
|
||||
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
public class TimestampCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
private SimpleDateFormat mTimestampFormatter;
|
||||
|
||||
public TimestampCellRenderer()
|
||||
{
|
||||
setHorizontalAlignment(JLabel.CENTER);
|
||||
updatePreferences();
|
||||
}
|
||||
|
||||
public void updatePreferences()
|
||||
{
|
||||
mTimestampFormatter = mUserPreferences.getDecodeEventPreference().getTimestampFormat().getFormatter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||
{
|
||||
JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
if(value instanceof Long)
|
||||
{
|
||||
label.setText(mTimestampFormatter.format(new Date((long)value)));
|
||||
}
|
||||
else
|
||||
{
|
||||
label.setText(null);
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
public class DurationCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
private DecimalFormat mDecimalFormat = new DecimalFormat("0.0");
|
||||
|
||||
public DurationCellRenderer()
|
||||
{
|
||||
setHorizontalAlignment(JLabel.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||
{
|
||||
JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
String formatted = null;
|
||||
|
||||
if(value instanceof Long)
|
||||
{
|
||||
long duration = (long)value;
|
||||
|
||||
if(duration > 0)
|
||||
{
|
||||
formatted = mDecimalFormat.format((double)duration / 1e3d);
|
||||
}
|
||||
}
|
||||
|
||||
label.setText(formatted);
|
||||
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frequency value cell renderer
|
||||
*/
|
||||
public class FrequencyCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
private DecimalFormat mFrequencyFormatter = new DecimalFormat("0.00000");
|
||||
|
||||
public FrequencyCellRenderer()
|
||||
{
|
||||
setHorizontalAlignment(JLabel.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||
{
|
||||
JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
String formatted = null;
|
||||
|
||||
if(value instanceof IChannelDescriptor)
|
||||
{
|
||||
IChannelDescriptor channelDescriptor = (IChannelDescriptor)value;
|
||||
|
||||
long frequency = channelDescriptor.getDownlinkFrequency();
|
||||
|
||||
if(frequency > 0)
|
||||
{
|
||||
formatted = mFrequencyFormatter.format(frequency / 1e6d);
|
||||
}
|
||||
}
|
||||
|
||||
label.setText(formatted);
|
||||
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel descriptor value cell renderer
|
||||
*/
|
||||
public class ChannelDescriptorCellRenderer extends DefaultTableCellRenderer
|
||||
{
|
||||
public ChannelDescriptorCellRenderer()
|
||||
{
|
||||
setHorizontalAlignment(JLabel.CENTER);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user