#5 Playlist Editor With Radio Reference Support (#760)

* WIP - Adds support for aliasing P25 Phase 2 tones.Playlist editor - adds editor support for alias actions

* Playlist editor implementation and integration into application is complete. Adds JavaFX window monitoring to persist window size/location across application runs.

Co-authored-by: Denny <denny@denny-desktop>
This commit is contained in:
Denny Sheirer
2020-04-26 11:53:43 +00:00
committed by GitHub
parent 39f6d6c831
commit cc52ae5bd7
290 changed files with 31251 additions and 15306 deletions

View File

@@ -1,59 +1,73 @@
/*
* ******************************************************************************
* sdrtrunk
* Copyright (C) 2014-2018 Dennis Sheirer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* * ******************************************************************************
* * 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/>
* * *****************************************************************************
*
* 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.alias;
import io.github.dsheirer.alias.id.AliasID;
import io.github.dsheirer.alias.id.AliasIDType;
import io.github.dsheirer.alias.id.broadcast.BroadcastChannel;
import io.github.dsheirer.identifier.IdentifierCollection;
import io.github.dsheirer.identifier.configuration.AliasListConfigurationIdentifier;
import io.github.dsheirer.sample.Broadcaster;
import io.github.dsheirer.sample.Listener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Alias Model contains all aliases and is responsible for creation and management of alias lists. Alias lists are a
* set of aliases that all share a common alias list name and can be attached to a decoding channel for aliasing
* identifiers produced by channel decoder(s).
*/
public class AliasModel extends AbstractTableModel
public class AliasModel
{
private static final long serialVersionUID = 1L;
public static final int COLUMN_LIST = 0;
public static final int COLUMN_GROUP = 1;
public static final int COLUMN_NAME = 2;
public static final int COLUMN_ICON = 3;
public static final int COLUMN_COLOR = 4;
private List<Alias> mAliases = new CopyOnWriteArrayList<>();
private final static Logger mLog = LoggerFactory.getLogger(AliasModel.class);
public static final String NO_ALIAS_LIST = "(No Alias List)";
private ObservableList<Alias> mAliases = FXCollections.observableArrayList(Alias.extractor());
private ObservableList<String> mAliasListNames = FXCollections.observableArrayList();
private Broadcaster<AliasEvent> mAliasEventBroadcaster = new Broadcaster<>();
private Map<String,AliasList> mAliasListMap = new HashMap<>();
public AliasModel()
{
//Register a listener to detect alias changes and broadcast change events to cause playlist save requests
mAliases.addListener(new AliasListChangeListener());
}
public ObservableList<Alias> aliasList()
{
return mAliases;
}
public ObservableList<String> aliasListNames()
{
return mAliasListNames;
}
/**
@@ -64,14 +78,17 @@ public class AliasModel extends AbstractTableModel
return Collections.unmodifiableList(mAliases);
}
public Alias getAliasAtIndex(int row)
/**
* Removes all aliases from the list and broadcasts the alias delete event for each
*/
public void clear()
{
if(mAliases.size() >= row)
{
return mAliases.get(row);
}
List<Alias> aliasToRemove = new ArrayList<>(mAliases);
return null;
for(Alias alias: aliasToRemove)
{
removeAlias(alias);
}
}
/**
@@ -126,7 +143,7 @@ public class AliasModel extends AbstractTableModel
for(Alias alias : mAliases)
{
if(alias.hasList() && alias.getList().equalsIgnoreCase(name))
if(alias.hasList() && alias.getAliasListName().equalsIgnoreCase(name))
{
aliasList.addAlias(alias);
}
@@ -145,19 +162,7 @@ public class AliasModel extends AbstractTableModel
*/
public List<String> getListNames()
{
List<String> listNames = new ArrayList<>();
for(Alias alias : mAliases)
{
if(alias.hasList() && !listNames.contains(alias.getList()))
{
listNames.add(alias.getList());
}
}
Collections.sort(listNames);
return listNames;
return mAliasListNames;
}
/**
@@ -180,33 +185,6 @@ public class AliasModel extends AbstractTableModel
return groupNames;
}
/**
* Returns a list of alias group names for all aliases that have a matching
* list name value
*/
public List<String> getGroupNames(String listName)
{
List<String> groupNames = new ArrayList<>();
if(listName != null)
{
for(Alias alias : mAliases)
{
if(alias.hasList() &&
alias.hasGroup() &&
listName.equals(alias.getList()) &&
!groupNames.contains(alias.getGroup()))
{
groupNames.add(alias.getGroup());
}
}
}
Collections.sort(groupNames);
return groupNames;
}
/**
* Bulk loading of aliases
*/
@@ -226,19 +204,33 @@ public class AliasModel extends AbstractTableModel
if(alias != null)
{
mAliases.add(alias);
addAliasList(alias.getAliasListName());
int index = mAliases.size() - 1;
fireTableRowsInserted(index, index);
broadcast(new AliasEvent(alias, AliasEvent.Event.ADD));
return index;
}
return -1;
}
public void addAliasList(String aliasListName)
{
if(aliasListName != null && !aliasListName.isEmpty())
{
if(!mAliasListNames.contains(aliasListName))
{
mAliasListNames.add(aliasListName);
FXCollections.sort(mAliasListNames);
}
}
else if(!mAliasListNames.contains(NO_ALIAS_LIST))
{
//This list allows users to view unassigned aliases so that they can move them to a valid alias list, but
// it is not assignable to a channel
mAliasListNames.add(NO_ALIAS_LIST);
}
}
/**
* Removes the channel from the model and broadcasts a channel remove event
*/
@@ -246,119 +238,94 @@ public class AliasModel extends AbstractTableModel
{
if(alias != null)
{
int index = mAliases.indexOf(alias);
mAliases.remove(alias);
fireTableRowsDeleted(index, index);
broadcast(new AliasEvent(alias, AliasEvent.Event.DELETE));
}
}
/**
* Renames any broadcast channels that have the previous name.
*
* @param previousName to rename
* @param newName to assign to the broadcast channel
* Retrieves all aliases that match the alias list and have at least one alias ID of the specified type
* @param aliasListName to search
* @param type to find
* @return list of aliases
*/
public void renameBroadcastChannel(String previousName, String newName)
public List<Alias> getAliases(String aliasListName, AliasIDType type)
{
if(previousName == null || previousName.isEmpty() || newName == null || newName.isEmpty())
{
return;
}
List<Alias> aliases = new ArrayList<>();
for(Alias alias : mAliases)
{
if(alias.hasBroadcastChannel(previousName))
if(alias.hasList() && alias.getAliasListName().equalsIgnoreCase(aliasListName))
{
for(BroadcastChannel broadcastChannel : alias.getBroadcastChannels())
for(AliasID aliasID: alias.getAliasIdentifiers())
{
if(broadcastChannel.getChannelName().contentEquals(previousName))
if(aliasID.getType() == type)
{
broadcastChannel.setChannelName(newName);
aliases.add(alias);
break;
}
}
}
}
return aliases;
}
@Override
public int getRowCount()
/**
* Indicates that one or more of the aliases managed by this model are configured to stream to the specified
* broadcast channel argument.
* @param broadcastChannel to check
* @return true if the broadcast channel is non-null, non-empty and at least one alias is configured to stream to
* the specified stream name.
*/
public boolean hasAliasesWithBroadcastChannel(String broadcastChannel)
{
return mAliases.size();
}
@Override
public int getColumnCount()
{
return 5;
}
@Override
public String getColumnName(int columnIndex)
{
switch(columnIndex)
if(broadcastChannel == null || broadcastChannel.isEmpty())
{
case COLUMN_LIST:
return "List";
case COLUMN_GROUP:
return "Group";
case COLUMN_NAME:
return "Name";
case COLUMN_ICON:
return "Icon";
case COLUMN_COLOR:
return "Color";
return false;
}
return null;
}
@Override
public Class<?> getColumnClass(int columnIndex)
{
if(columnIndex == COLUMN_COLOR)
for(Alias alias: mAliases)
{
return Integer.class;
if(alias.hasBroadcastChannel(broadcastChannel))
{
return true;
}
}
return String.class;
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return false;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex)
/**
* Updates all aliases configured to stream to the previousStreamName with the updatedStreamName
* @param previousStreamName to be removed
* @param updatedStreamName to be added
*/
public void updateBroadcastChannel(String previousStreamName, String updatedStreamName)
{
Alias alias = mAliases.get(rowIndex);
switch(columnIndex)
if(previousStreamName == null || previousStreamName.isEmpty() || updatedStreamName == null || updatedStreamName.isEmpty())
{
case COLUMN_LIST:
return alias.getList();
case COLUMN_GROUP:
return alias.getGroup();
case COLUMN_NAME:
return alias.getName();
case COLUMN_ICON:
return alias.getIconName();
case COLUMN_COLOR:
return alias.getColor();
return;
}
return null;
}
for(Alias alias: mAliases)
{
if(alias.hasBroadcastChannel(previousStreamName))
{
for(BroadcastChannel broadcastChannel: alias.getBroadcastChannels())
{
if(broadcastChannel.getChannelName().contentEquals(previousStreamName))
{
alias.removeAliasID(broadcastChannel);
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
{
throw new IllegalArgumentException("Not yet implemented");
if(!alias.hasBroadcastChannel(updatedStreamName))
{
alias.addAliasID(new BroadcastChannel(updatedStreamName));
}
}
}
}
}
}
public void broadcast(AliasEvent event)
@@ -371,13 +338,6 @@ public class AliasModel extends AbstractTableModel
alias.validate();
}
if(event.getEvent() == AliasEvent.Event.CHANGE)
{
int index = mAliases.indexOf(event.getAlias());
fireTableRowsUpdated(index, index);
}
mAliasEventBroadcaster.broadcast(event);
}
@@ -390,4 +350,40 @@ public class AliasModel extends AbstractTableModel
{
mAliasEventBroadcaster.removeListener(listener);
}
/**
* Observable list change listener for both channels and traffic channels lists
*/
public class AliasListChangeListener implements ListChangeListener<Alias>
{
@Override
public void onChanged(ListChangeListener.Change<? extends Alias> change)
{
while(change.next())
{
if(change.wasAdded())
{
for(Alias alias: change.getAddedSubList())
{
mAliasEventBroadcaster.broadcast(new AliasEvent(alias, AliasEvent.Event.ADD));
}
}
else if(change.wasRemoved())
{
for(Alias alias: change.getRemoved())
{
mAliasEventBroadcaster.broadcast(new AliasEvent(alias, AliasEvent.Event.DELETE));
}
}
else if(change.wasUpdated())
{
for(int x = change.getFrom(); x < change.getTo(); x++)
{
mAliasEventBroadcaster.broadcast(new AliasEvent(change.getList().get(x), AliasEvent.Event.CHANGE));
}
}
}
}
}
}