From 5d580544e7b6ce5ddebdbfdb92b61fa8b6997b40 Mon Sep 17 00:00:00 2001 From: Denny Sheirer Date: Mon, 4 May 2020 10:20:49 +0000 Subject: [PATCH] #766 Resolves issue with playlist alias editor clone alias feature causing multiple copies of alias. Changed clone feature to only use a shallow copy (no ids or actions). Removed alias event framework from code since all change notifications can now be detected by the alias model's aliasList property. (#791) Co-authored-by: Denny --- .../io/github/dsheirer/alias/AliasEvent.java | 55 ---- .../github/dsheirer/alias/AliasFactory.java | 55 +++- .../io/github/dsheirer/alias/AliasList.java | 266 +++++------------- .../io/github/dsheirer/alias/AliasModel.java | 85 +++--- .../alias/action/AliasActionListener.java | 32 --- .../alias/AliasConfigurationEditor.java | 142 ++++++---- .../gui/playlist/alias/AliasItemEditor.java | 38 +-- .../alias/AliasViewByRecordingEditor.java | 5 - .../streaming/StreamAliasSelectionEditor.java | 5 - .../dsheirer/playlist/PlaylistManager.java | 7 +- 10 files changed, 248 insertions(+), 442 deletions(-) delete mode 100644 src/main/java/io/github/dsheirer/alias/AliasEvent.java delete mode 100644 src/main/java/io/github/dsheirer/alias/action/AliasActionListener.java diff --git a/src/main/java/io/github/dsheirer/alias/AliasEvent.java b/src/main/java/io/github/dsheirer/alias/AliasEvent.java deleted file mode 100644 index f0370ae6..00000000 --- a/src/main/java/io/github/dsheirer/alias/AliasEvent.java +++ /dev/null @@ -1,55 +0,0 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ -package io.github.dsheirer.alias; - -public class AliasEvent -{ - private Alias mAlias; - private Event mEvent; - - /** - * AliasEvent - event describing any changes to an alias - * @param alias - alias that changed - * @param event - change event - */ - public AliasEvent( Alias alias, Event event ) - { - mAlias = alias; - mEvent = event; - } - - public Alias getAlias() - { - return mAlias; - } - - public Event getEvent() - { - return mEvent; - } - - /** - * Channel events to describe the specific event - */ - public enum Event - { - ADD, - CHANGE, - DELETE; - } -} diff --git a/src/main/java/io/github/dsheirer/alias/AliasFactory.java b/src/main/java/io/github/dsheirer/alias/AliasFactory.java index 2618e47d..095d85ee 100644 --- a/src/main/java/io/github/dsheirer/alias/AliasFactory.java +++ b/src/main/java/io/github/dsheirer/alias/AliasFactory.java @@ -51,6 +51,9 @@ import io.github.dsheirer.alias.id.tone.TonesID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; +import java.util.List; + public class AliasFactory { private static final Logger mLog = LoggerFactory.getLogger(AliasFactory.class); @@ -180,32 +183,56 @@ public class AliasFactory return null; } - public static Alias copyOf(Alias original) + public static Alias shallowCopyOf(Alias original) { Alias copy = new Alias(original.getName()); copy.setAliasListName(original.getAliasListName()); copy.setGroup(original.getGroup()); copy.setColor(original.getColor()); copy.setIconName(original.getIconName()); + return copy; + } + + public static List copyAliasIDs(Alias original) + { + List aliasIDS = new ArrayList<>(); for(AliasID id : original.getAliasIdentifiers()) { - AliasID copyID = copyOf(id); - - if(copyID != null) - { - copy.addAliasID(copyID); - } + aliasIDS.add(copyOf(id)); } - for(AliasAction action : original.getAliasActions()) - { - AliasAction copyAction = copyOf(action); + return aliasIDS; + } - if(copyAction != null) - { - copy.addAliasAction(copyAction); - } + public static List copyAliasActions(Alias original) + { + List actions = new ArrayList<>(); + + for(AliasAction aliasAction: original.getAliasActions()) + { + actions.add(copyOf(aliasAction)); + } + + return actions; + } + + public static Alias copyOf(Alias original) + { + Alias copy = shallowCopyOf(original); + + List aliasIDS = copyAliasIDs(original); + + for(AliasID aliasID: aliasIDS) + { + copy.addAliasID(aliasID); + } + + List actions = copyAliasActions(original); + + for(AliasAction action: actions) + { + copy.addAliasAction(action); } return copy; diff --git a/src/main/java/io/github/dsheirer/alias/AliasList.java b/src/main/java/io/github/dsheirer/alias/AliasList.java index 6c2293ea..ce96e121 100644 --- a/src/main/java/io/github/dsheirer/alias/AliasList.java +++ b/src/main/java/io/github/dsheirer/alias/AliasList.java @@ -45,19 +45,27 @@ import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; import io.github.dsheirer.identifier.tone.ToneIdentifier; import io.github.dsheirer.identifier.tone.ToneSequence; import io.github.dsheirer.protocol.Protocol; -import io.github.dsheirer.sample.Listener; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; /** * List of aliases that share the same alias list name and provides convenient methods for looking up alias * objects that match an identifier. */ -public class AliasList implements Listener +public class AliasList { private final static Logger mLog = LoggerFactory.getLogger(AliasList.class); private Map mTalkgroupProtocolMap = new EnumMap<>(Protocol.class); @@ -96,10 +104,7 @@ public class AliasList implements Listener { if(alias != null) { - for(AliasID aliasID : alias.getAliasIdentifiers()) - { - addAliasID(aliasID, alias); - } + alias.getAliasIdentifiers().stream().forEach(aliasID -> addAliasID(aliasID, alias)); } if(alias.hasActions()) @@ -107,7 +112,10 @@ public class AliasList implements Listener mHasAliasActions = true; } - mAliases.add(alias); + if(!mAliases.contains(alias)) + { + mAliases.add(alias); + } } /** @@ -174,7 +182,7 @@ public class AliasList implements Listener radioRangeAliasList.add(radioRange, alias); break; case ESN: - String esn = ((Esn) id).getEsn(); + String esn = ((Esn)id).getEsn(); if(esn != null && !esn.isEmpty()) { @@ -184,7 +192,7 @@ public class AliasList implements Listener case STATUS: int userStatus = ((UserStatusID)id).getStatus(); - if(mUserStatusMap.containsKey(userStatus)) + if(mUserStatusMap.containsKey(userStatus) && !mUserStatusMap.get(userStatus).equals(alias)) { id.setOverlap(true); @@ -203,7 +211,7 @@ public class AliasList implements Listener case UNIT_STATUS: int unitStatus = ((UnitStatusID)id).getStatus(); - if(mUnitStatusMap.containsKey(unitStatus)) + if(mUnitStatusMap.containsKey(unitStatus) && !mUnitStatusMap.get(unitStatus).equals(alias)) { id.setOverlap(true); @@ -224,7 +232,7 @@ public class AliasList implements Listener if(toneSequence != null) { - if(mToneSequenceMap.containsKey(toneSequence)) + if(mToneSequenceMap.containsKey(toneSequence) && !mToneSequenceMap.get(toneSequence).equals(alias)) { id.setOverlap(true); @@ -232,7 +240,7 @@ public class AliasList implements Listener for(AliasID aliasID: existing.getAliasIdentifiers()) { - if(aliasID instanceof TonesID && ((TonesID)aliasID).equals(id)) + if(aliasID instanceof TonesID && aliasID.equals(id)) { aliasID.setOverlap(true); } @@ -253,112 +261,50 @@ public class AliasList implements Listener } } - /** - * Removes the alias and alias identifier to the internal type mapping. - */ - private void removeAliasID(AliasID id, Alias alias) - { - try - { - switch(id.getType()) - { - case TALKGROUP: - Talkgroup talkgroup = (Talkgroup)id; - - TalkgroupAliasList talkgroupAliasList = mTalkgroupProtocolMap.get(talkgroup.getProtocol()); - - if(talkgroupAliasList != null) - { - talkgroupAliasList.remove(talkgroup, alias); - } - break; - case TALKGROUP_RANGE: - TalkgroupRange talkgroupRange = (TalkgroupRange)id; - - TalkgroupAliasList talkgroupRangeAliasList = mTalkgroupProtocolMap.get(talkgroupRange.getProtocol()); - - if(talkgroupRangeAliasList != null) - { - talkgroupRangeAliasList.remove(talkgroupRange, alias); - } - break; - case RADIO_ID: - Radio radio = (Radio)id; - - RadioAliasList radioAliasList = mRadioProtocolMap.get(radio.getProtocol()); - - if(radioAliasList != null) - { - radioAliasList.remove(radio, alias); - } - break; - case RADIO_ID_RANGE: - RadioRange radioRange = (RadioRange)id; - - RadioAliasList radioRangeAliasList = mRadioProtocolMap.get(radioRange.getProtocol()); - - if(radioRangeAliasList != null) - { - radioRangeAliasList.remove(radioRange, alias); - } - break; - case ESN: - String esn = ((Esn) id).getEsn(); - - if(esn != null && !esn.isEmpty()) - { - String key = esn.toLowerCase(); - - if(mESNMap.containsKey(key) && mESNMap.get(key) == alias) - { - mESNMap.remove(key); - } - } - break; - case STATUS: - int status = ((UserStatusID)id).getStatus(); - - if(mUserStatusMap.containsKey(status) && mUserStatusMap.get(status) == alias) - { - mUserStatusMap.remove(status); - } - break; - case UNIT_STATUS: - int unitStatus = ((UnitStatusID)id).getStatus(); - - if(mUnitStatusMap.containsKey(unitStatus) && mUnitStatusMap.get(unitStatus) == alias) - { - mUnitStatusMap.remove(unitStatus); - } - break; - case TONES: - ToneSequence toneSequence = ((TonesID)id).getToneSequence(); - - if(toneSequence != null && mToneSequenceMap.containsKey(toneSequence) && - mToneSequenceMap.get(toneSequence) == alias) - { - mToneSequenceMap.remove(toneSequence); - } - break; - } - } - catch(Exception e) - { - mLog.error("Couldn't remove alias ID " + id + " for alias " + alias, e); - } - } - /** * Removes the alias from this list */ public void removeAlias(Alias alias) { - for(AliasID aliasID: alias.getAliasIdentifiers()) + //Note: because the alias' identifiers could have changed from when we initially added the alias, we have to + //inspect every collection and map to remove the alias completely. + mAliases.remove(alias); + + mTalkgroupProtocolMap.values().stream().forEach(talkgroupAliasList -> talkgroupAliasList.remove(alias)); + mRadioProtocolMap.values().stream().forEach(radioAliasList -> radioAliasList.remove(alias)); + + Collection collection = Collections.singleton(alias); + mESNMap.values().removeAll(collection); + mUnitStatusMap.values().removeAll(collection); + mUserStatusMap.values().removeAll(collection); + mToneSequenceMap.values().removeAll(collection); + + validate(); + } + + /** + * Identifies all aliases with an alias identifier that has the overlap flag set, resets the flag, and then readds + * each alias back to this alias list so that overlap can be detected again. + */ + public void validate() + { + Set overlapAliases = new HashSet<>(); + + List aliases = new ArrayList<>(mAliases); + + for(Alias alias: aliases) { - removeAliasID(aliasID, alias); + for(AliasID aliasID: alias.getAliasIdentifiers()) + { + if(aliasID.overlapProperty().get()) + { + aliasID.setOverlap(false); + overlapAliases.add(alias); + } + } } - mAliases.remove(alias); + overlapAliases.stream().forEach(alias -> addAlias(alias)); } /** @@ -402,32 +348,15 @@ public class AliasList implements Listener } /** - * Receive alias change event notifications and modify this list accordingly + * Updates the alias by removing it from this list and then adding it back to this list when the list name matches. */ - @Override - public void receive(AliasEvent event) + public void updateAlias(Alias alias) { - if(hasName()) - { - Alias alias = event.getAlias(); + removeAlias(alias); - switch(event.getEvent()) - { - case ADD: - if(alias.getAliasListName() != null && getName().equalsIgnoreCase(alias.getAliasListName())) - { - addAlias(alias); - } - break; - case DELETE: - if(alias.getAliasListName() != null && getName().equalsIgnoreCase(alias.getAliasListName())) - { - removeAlias(alias); - } - break; - default: - break; - } + if(hasName() && getName().equals(alias.getAliasListName())) + { + addAlias(alias); } } @@ -659,34 +588,6 @@ public class AliasList implements Listener return channels; } - /** - * Identifies all aliases that have alias identifiers that match the alias ID and are in the same alias list. - * @param alias for the list to search - * @param aliasID to find matches - * @return list of one or more aliases that have matching alias identifiers - */ - public List getOverlappingAliases(Alias alias, AliasID aliasID) - { - List aliases = new ArrayList<>(); - - for(Alias otherAlias: mAliases) - { - if(otherAlias != alias) - { - for(AliasID otherId: otherAlias.getAliasIdentifiers()) - { - if(otherId.matches(aliasID) || otherId.overlaps(aliasID)) - { - aliases.add(otherAlias); - continue; - } - } - } - } - - return aliases; - } - /** * Listing of talkgroups and ranges for a specific protocol */ @@ -759,21 +660,13 @@ public class AliasList implements Listener mTalkgroupRangeAliasMap.put(talkgroupRange, alias); } - public void remove(Talkgroup talkgroup, Alias alias) + /** + * Removes the alias from both the talkgroup and the talkgroup range maps. + */ + public void remove(Alias alias) { - if(mTalkgroupAliasMap.containsKey(talkgroup.getValue()) && mTalkgroupAliasMap.get(talkgroup.getValue()) == alias) - { - mTalkgroupAliasMap.remove(talkgroup.getValue()); - } - } - - public void remove(TalkgroupRange talkgroupRange, Alias alias) - { - //Only remove the entry if both the key and the value match - if(mTalkgroupRangeAliasMap.containsKey(talkgroupRange) && mTalkgroupRangeAliasMap.get(talkgroupRange) == alias) - { - mTalkgroupRangeAliasMap.remove(talkgroupRange); - } + mTalkgroupAliasMap.values().removeAll(Collections.singleton(alias)); + mTalkgroupRangeAliasMap.values().removeAll(Collections.singleton(alias)); } } @@ -849,22 +742,13 @@ public class AliasList implements Listener mRadioRangeAliasMap.put(radioRange, alias); } - public void remove(Radio radio, Alias alias) + /** + * Removes the alias from both the radio and the radio range maps. + */ + public void remove(Alias alias) { - //Only remove the entry if both the key and the value match - if(mRadioAliasMap.containsKey(radio.getValue()) && mRadioAliasMap.get(radio.getValue()) == alias) - { - mRadioAliasMap.remove(radio.getValue()); - } - } - - public void remove(RadioRange radioRange, Alias alias) - { - //Only remove the entry if both the key and the value match - if(mRadioRangeAliasMap.containsKey(radioRange) && mRadioRangeAliasMap.get(radioRange) == alias) - { - mRadioRangeAliasMap.remove(radioRange); - } + mRadioAliasMap.values().removeAll(Collections.singleton(alias)); + mRadioRangeAliasMap.values().removeAll(Collections.singleton(alias)); } } } diff --git a/src/main/java/io/github/dsheirer/alias/AliasModel.java b/src/main/java/io/github/dsheirer/alias/AliasModel.java index e14e6457..e215f029 100644 --- a/src/main/java/io/github/dsheirer/alias/AliasModel.java +++ b/src/main/java/io/github/dsheirer/alias/AliasModel.java @@ -26,8 +26,6 @@ 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; @@ -51,7 +49,6 @@ public class AliasModel public static final String NO_ALIAS_LIST = "(No Alias List)"; private ObservableList mAliases = FXCollections.observableArrayList(Alias.extractor()); private ObservableList mAliasListNames = FXCollections.observableArrayList(); - private Broadcaster mAliasEventBroadcaster = new Broadcaster<>(); private Map mAliasListMap = new HashMap<>(); public AliasModel() @@ -135,7 +132,8 @@ public class AliasModel } AliasList mapValue = mAliasListMap.get(name); - if (mapValue != null) { + if (mapValue != null) + { return mapValue; } @@ -151,9 +149,6 @@ public class AliasModel mAliasListMap.put(name, aliasList); - //Register the new alias list to receive updates from this model - addListener(aliasList); - return aliasList; } @@ -199,18 +194,19 @@ public class AliasModel /** * Adds the alias to the model */ - public int addAlias(Alias alias) + public void addAlias(Alias alias) { - if(alias != null) + if(mAliases.contains(alias)) { - mAliases.add(alias); - addAliasList(alias.getAliasListName()); - int index = mAliases.size() - 1; - broadcast(new AliasEvent(alias, AliasEvent.Event.ADD)); - return index; + removeAlias(alias); } - return -1; + mAliases.add(alias); + } + + private boolean hasAliasList(String aliasListName) + { + return aliasListName != null && mAliasListMap.containsKey(aliasListName); } public void addAliasList(String aliasListName) @@ -220,7 +216,6 @@ public class AliasModel if(!mAliasListNames.contains(aliasListName)) { mAliasListNames.add(aliasListName); - FXCollections.sort(mAliasListNames); } } else if(!mAliasListNames.contains(NO_ALIAS_LIST)) @@ -232,14 +227,18 @@ public class AliasModel } /** - * Removes the channel from the model and broadcasts a channel remove event + * Removes the alias from this model and an alias list (if one exists) */ public void removeAlias(Alias alias) { if(alias != null) { mAliases.remove(alias); - broadcast(new AliasEvent(alias, AliasEvent.Event.DELETE)); + + if(hasAliasList(alias.getAliasListName())) + { + getAliasList(alias.getAliasListName()).removeAlias(alias); + } } } @@ -328,32 +327,8 @@ public class AliasModel } } - public void broadcast(AliasEvent event) - { - Alias alias = event.getAlias(); - - //Validate the alias following a user action that changed the alias or any alias IDs - if(alias != null) - { - alias.validate(); - } - - mAliasEventBroadcaster.broadcast(event); - } - - public void addListener(Listener listener) - { - mAliasEventBroadcaster.addListener(listener); - } - - public void removeListener(Listener listener) - { - mAliasEventBroadcaster.removeListener(listener); - } - - /** - * Observable list change listener for both channels and traffic channels lists + * Monitors alias additions and removals and updates alias lists */ public class AliasListChangeListener implements ListChangeListener { @@ -366,21 +341,27 @@ public class AliasModel { for(Alias alias: change.getAddedSubList()) { - mAliasEventBroadcaster.broadcast(new AliasEvent(alias, AliasEvent.Event.ADD)); + addAliasList(alias.getAliasListName()); + + if(hasAliasList(alias.getAliasListName())) + { + getAliasList(alias.getAliasListName()).addAlias(alias); + } } } 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)); + if(hasAliasList(alias.getAliasListName())) + { + AliasList aliasList = getAliasList(alias.getName()); + + if(alias != null) + { + aliasList.removeAlias(alias); + } + } } } } diff --git a/src/main/java/io/github/dsheirer/alias/action/AliasActionListener.java b/src/main/java/io/github/dsheirer/alias/action/AliasActionListener.java deleted file mode 100644 index 36e863d7..00000000 --- a/src/main/java/io/github/dsheirer/alias/action/AliasActionListener.java +++ /dev/null @@ -1,32 +0,0 @@ -/******************************************************************************* - * 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 - ******************************************************************************/ -package io.github.dsheirer.alias.action; - -import io.github.dsheirer.alias.AliasEvent; - -/** - * Interface for receiving alias events. Each listener will receive - * notification of all alias events. - */ -public interface AliasActionListener -{ - /** - * Notifies the listener that an alias event has occurred - */ - public void aliasChanged( AliasEvent event ); -} diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasConfigurationEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasConfigurationEditor.java index 4a18b7b2..d436fed1 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasConfigurationEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasConfigurationEditor.java @@ -74,6 +74,7 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.function.Predicate; /** * Editor for aliases @@ -97,7 +98,8 @@ public class AliasConfigurationEditor extends SplitPane private ComboBox mAliasListNameComboBox; private Button mNewAliasListButton; private FilteredList mAliasFilteredList; - private AliasFilterMonitor mAliasFilterMonitor; + private SortedList mAliasSortedList; + private AliasPredicate mAliasPredicate; public AliasConfigurationEditor(PlaylistManager playlistManager, UserPreferences userPreferences) { @@ -124,7 +126,6 @@ public class AliasConfigurationEditor extends SplitPane */ public void show(Alias alias) { - mLog.debug("Showing: " + alias.getName()); if(alias != null) { String aliasList = alias.getAliasListName(); @@ -224,20 +225,40 @@ public class AliasConfigurationEditor extends SplitPane if(mSearchField == null) { mSearchField = TextFields.createClearableTextField(); - + mSearchField.textProperty().addListener((observable, oldValue, newValue) -> update()); } return mSearchField; } + private void update() + { + getAliasFilteredList().setPredicate(null); + getAliasPredicate().setAliasListName(getAliasListNameComboBox().getSelectionModel().getSelectedItem()); + getAliasPredicate().setSearchText(getSearchField().getText()); + getAliasFilteredList().setPredicate(getAliasPredicate()); + } + + private AliasPredicate getAliasPredicate() + { + if(mAliasPredicate == null) + { + mAliasPredicate = new AliasPredicate(); + mAliasPredicate.setAliasListName(getAliasListNameComboBox().getSelectionModel().getSelectedItem()); + } + + return mAliasPredicate; + } + private ComboBox getAliasListNameComboBox() { if(mAliasListNameComboBox == null) { mAliasListNameComboBox = new ComboBox<>(mPlaylistManager.getAliasModel().aliasListNames()); mAliasListNameComboBox.getSelectionModel().selectedItemProperty() - .addListener((observable, oldValue, newValue) -> { + .addListener((ObservableValue observable, String oldValue, String newValue) -> { getNewAliasButton().setDisable(newValue == null || newValue.contentEquals(AliasModel.NO_ALIAS_LIST)); + update(); }); if(mAliasListNameComboBox.getItems().size() > 1) @@ -362,15 +383,7 @@ public class AliasConfigurationEditor extends SplitPane recordColumn, streamColumn, idsColumn, errorsColumn); mAliasTableView.setPlaceholder(getPlaceholderLabel()); - - //Sorting and filtering for the table - mAliasFilteredList = new FilteredList<>(mPlaylistManager.getAliasModel().aliasList(), - alias -> alias.matchesAliasList(getAliasListNameComboBox().getSelectionModel().getSelectedItem())); - mAliasFilterMonitor = new AliasFilterMonitor(); - - SortedList sortedList = new SortedList<>(mAliasFilteredList); - sortedList.comparatorProperty().bind(mAliasTableView.comparatorProperty()); - mAliasTableView.setItems(sortedList); + mAliasTableView.setItems(getAliasSortedList()); mAliasTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); mAliasTableView.getSelectionModel().selectedItemProperty() .addListener((observable, oldValue, newValue) -> { @@ -381,6 +394,27 @@ public class AliasConfigurationEditor extends SplitPane return mAliasTableView; } + private FilteredList getAliasFilteredList() + { + if(mAliasFilteredList == null) + { + mAliasFilteredList = new FilteredList<>(mPlaylistManager.getAliasModel().aliasList(), getAliasPredicate()); + } + + return mAliasFilteredList; + } + + private SortedList getAliasSortedList() + { + if(mAliasSortedList == null) + { + mAliasSortedList = new SortedList<>(getAliasFilteredList()); + mAliasSortedList.comparatorProperty().bind(getAliasTableView().comparatorProperty()); + } + + return mAliasSortedList; + } + private Label getPlaceholderLabel() { if(mPlaceholderLabel == null) @@ -417,7 +451,7 @@ public class AliasConfigurationEditor extends SplitPane mNewAliasButton.setAlignment(Pos.CENTER); mNewAliasButton.setMaxWidth(Double.MAX_VALUE); mNewAliasButton.setOnAction(event -> { - Alias alias = new Alias(); + Alias alias = new Alias("New Alias"); alias.setAliasListName(getAliasListNameComboBox().getSelectionModel().getSelectedItem()); mPlaylistManager.getAliasModel().addAlias(alias); @@ -477,7 +511,7 @@ public class AliasConfigurationEditor extends SplitPane mCloneAliasButton.setMaxWidth(Double.MAX_VALUE); mCloneAliasButton.setOnAction(event -> { Alias original = getAliasTableView().getSelectionModel().getSelectedItem(); - Alias copy = AliasFactory.copyOf(original); + Alias copy = AliasFactory.shallowCopyOf(original); mPlaylistManager.getAliasModel().addAlias(copy); getAliasTableView().getSelectionModel().clearSelection(); getAliasTableView().getSelectionModel().select(copy); @@ -765,47 +799,57 @@ public class AliasConfigurationEditor extends SplitPane } /** - * Updates the filtered set of aliases any time there is a change in the selected alias list name box, or when the - * user types text in the search box. + * Alias filter predicate */ - public class AliasFilterMonitor + public class AliasPredicate implements Predicate { - public AliasFilterMonitor() - { - getAliasListNameComboBox().getSelectionModel().selectedItemProperty() - .addListener((observable, oldValue, newValue) -> { - refresh(); - }); + private String mAliasListName; + private String mSearchText; - getSearchField().textProperty().addListener((observable, oldValue, newValue) -> { - refresh(); - }); + @Override + public boolean test(Alias alias) + { + if(mAliasListName == null) + { + return false; + } + else if(mAliasListName.equals(alias.getAliasListName())) + { + if(alias.getName() == null) + { + return true; + } + else if(alias.getName().toLowerCase().contains(mSearchText)) + { + return true; + } + else if(alias.getGroup() != null && alias.getGroup().toLowerCase().contains(mSearchText)) + { + return true; + } + } + + return false; } - public void refresh() + public void setAliasListName(String aliasListName) { - final String aliasListName = getAliasListNameComboBox().getSelectionModel().getSelectedItem(); - final String filter = getSearchField().getText(); + if(aliasListName != null) + { + mAliasListName = aliasListName; + } + } - mAliasFilteredList.setPredicate(alias -> { - if(filter == null || filter.isEmpty()) - { - return alias.matchesAliasList(aliasListName); - } - - String lowerFilter = filter.toLowerCase(); - - if(alias.getName() != null && alias.getName().toLowerCase().contains(lowerFilter)) - { - return alias.matchesAliasList(aliasListName); - } - else if(alias.getGroup() != null && alias.getGroup().toLowerCase().contains(lowerFilter)) - { - return alias.matchesAliasList(aliasListName); - } - - return false; - }); + public void setSearchText(String searchText) + { + if(searchText != null) + { + mSearchText = searchText.toLowerCase(); + } + else + { + mSearchText = null; + } } } } diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java index 5e15e74a..84a8053a 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasItemEditor.java @@ -104,7 +104,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -360,49 +359,15 @@ public class AliasItemEditor extends Editor alias.addAliasID(selected); } - AliasList aliasList = mPlaylistManager.getAliasModel().getAliasList(alias.getAliasListName()); - aliasList.removeAlias(alias); - - //Cleanup any alias identifiers that are marked as overlapping. To do this, we remove all of the - //affected aliases, reset all of the overlap flags, and then add the aliases back to the alias list. - Set aliasesToReset = new HashSet<>(); - for(AliasID aliasID: alias.getAliasIdentifiers()) - { - if(aliasID.overlapProperty().get()) - { - aliasID.overlapProperty().set(false); - List overlapAliases = aliasList.getOverlappingAliases(alias, aliasID); - aliasesToReset.addAll(overlapAliases); - } - } - - //Remove overlap aliases, reset them, and readd back to the alias list - for(Alias resetAlias: aliasesToReset) - { - aliasList.removeAlias(resetAlias); - for(AliasID aliasID: resetAlias.getAliasIdentifiers()) - { - aliasID.setOverlap(false); - } - } - //Remove and replace the remaining non-audio identifiers alias.removeNonAudioIdentifiers(); for(AliasID aliasID: getIdentifiersList().getItems()) { - aliasID.setOverlap(false); //Create a copy of the identifier so that the alias and the editor don't have the same instance alias.addAliasID(AliasFactory.copyOf(aliasID)); } - //Add the aliases back to the alias list - for(Alias resetAlias: aliasesToReset) - { - aliasList.addAlias(resetAlias); - } - aliasList.addAlias(alias); - //Remove and replace alias actions alias.removeAllActions(); for(AliasAction aliasAction: getActionsList().getItems()) @@ -411,6 +376,9 @@ public class AliasItemEditor extends Editor } } + AliasList aliasList = mPlaylistManager.getAliasModel().getAliasList(alias.getAliasListName()); + aliasList.updateAlias(alias); + //Reset the alias to refresh the editor. setItem(alias); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasViewByRecordingEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasViewByRecordingEditor.java index b3cf0655..fd1dce41 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasViewByRecordingEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/AliasViewByRecordingEditor.java @@ -20,7 +20,6 @@ package io.github.dsheirer.gui.playlist.alias; import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasEvent; import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.playlist.PlaylistManager; import javafx.collections.ListChangeListener; @@ -315,7 +314,6 @@ public class AliasViewByRecordingEditor extends VBox if(selectedAlias != null) { selectedAlias.setRecordable(true); - mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE)); updateListFilters(); } }); @@ -342,7 +340,6 @@ public class AliasViewByRecordingEditor extends VBox for(Alias selectedAlias: selectedAliases) { selectedAlias.setRecordable(true); - mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE)); } updateListFilters(); @@ -368,7 +365,6 @@ public class AliasViewByRecordingEditor extends VBox if(selectedAlias != null) { selectedAlias.setRecordable(false); - mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE)); updateListFilters(); } }); @@ -395,7 +391,6 @@ public class AliasViewByRecordingEditor extends VBox for(Alias selectedAlias: selectedAliases) { selectedAlias.setRecordable(false); - mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE)); } updateListFilters(); diff --git a/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamAliasSelectionEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamAliasSelectionEditor.java index d2865d7c..54838849 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamAliasSelectionEditor.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/streaming/StreamAliasSelectionEditor.java @@ -23,7 +23,6 @@ package io.github.dsheirer.gui.playlist.streaming; import io.github.dsheirer.alias.Alias; -import io.github.dsheirer.alias.AliasEvent; import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; import io.github.dsheirer.audio.broadcast.BroadcastConfiguration; import io.github.dsheirer.playlist.PlaylistManager; @@ -312,7 +311,6 @@ public class StreamAliasSelectionEditor extends VBox if(selectedAlias != null && stream != null) { selectedAlias.addAliasID(new BroadcastChannel(stream)); - mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE)); updateListFilters(); } }); @@ -344,7 +342,6 @@ public class StreamAliasSelectionEditor extends VBox for(Alias selectedAlias: selectedAliases) { selectedAlias.addAliasID(new BroadcastChannel(stream)); - mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE)); } updateListFilters(); @@ -372,7 +369,6 @@ public class StreamAliasSelectionEditor extends VBox if(selectedAlias != null && stream != null) { selectedAlias.removeBroadcastChannel(stream); - mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE)); updateListFilters(); } }); @@ -400,7 +396,6 @@ public class StreamAliasSelectionEditor extends VBox for(Alias selectedAlias: selectedAliases) { selectedAlias.removeBroadcastChannel(stream); - mPlaylistManager.getAliasModel().broadcast(new AliasEvent(selectedAlias, AliasEvent.Event.CHANGE)); } updateListFilters(); diff --git a/src/main/java/io/github/dsheirer/playlist/PlaylistManager.java b/src/main/java/io/github/dsheirer/playlist/PlaylistManager.java index 65e58d29..77edd599 100644 --- a/src/main/java/io/github/dsheirer/playlist/PlaylistManager.java +++ b/src/main/java/io/github/dsheirer/playlist/PlaylistManager.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule; import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import io.github.dsheirer.alias.Alias; import io.github.dsheirer.alias.AliasModel; import io.github.dsheirer.audio.broadcast.BroadcastModel; import io.github.dsheirer.controller.channel.Channel.ChannelType; @@ -41,6 +42,7 @@ import io.github.dsheirer.service.radioreference.RadioReference; import io.github.dsheirer.source.SourceManager; import io.github.dsheirer.source.tuner.TunerModel; import io.github.dsheirer.util.ThreadPool; +import javafx.collections.ListChangeListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -108,10 +110,7 @@ public class PlaylistManager implements Listener //save the playlist when there are any changes mChannelModel.addListener(this); - mAliasModel.addListener(t -> { - //Save the playlist for all alias events - schedulePlaylistSave(); - }); + mAliasModel.aliasList().addListener((ListChangeListener)c -> schedulePlaylistSave()); mChannelMapModel.addListener(t -> { //Save the playlist for all channel map events