diff --git a/src/main/java/io/github/dsheirer/alias/AliasFactory.java b/src/main/java/io/github/dsheirer/alias/AliasFactory.java index 9c1a3892..6911a759 100644 --- a/src/main/java/io/github/dsheirer/alias/AliasFactory.java +++ b/src/main/java/io/github/dsheirer/alias/AliasFactory.java @@ -30,11 +30,13 @@ import io.github.dsheirer.alias.id.legacy.mobileID.Min; import io.github.dsheirer.alias.id.legacy.siteID.SiteID; import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID; import io.github.dsheirer.alias.id.priority.Priority; +import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio; import io.github.dsheirer.alias.id.radio.Radio; import io.github.dsheirer.alias.id.radio.RadioRange; import io.github.dsheirer.alias.id.record.Record; import io.github.dsheirer.alias.id.status.UnitStatusID; import io.github.dsheirer.alias.id.status.UserStatusID; +import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup; import io.github.dsheirer.alias.id.talkgroup.Talkgroup; import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange; import io.github.dsheirer.alias.id.tone.TonesID; @@ -77,6 +79,18 @@ public class AliasFactory Min copyMin = new Min(); copyMin.setMin(originalMin.getMin()); return copyMin; + case P25_FULLY_QUALIFIED_RADIO_ID: + P25FullyQualifiedRadio originalP25 = (P25FullyQualifiedRadio) id; + P25FullyQualifiedRadio copyP25 = new P25FullyQualifiedRadio(originalP25.getWacn(), + originalP25.getSystem(), originalP25.getValue()); + copyP25.setOverlap(originalP25.overlapProperty().get()); + return copyP25; + case P25_FULLY_QUALIFIED_TALKGROUP: + P25FullyQualifiedTalkgroup originalFqt = (P25FullyQualifiedTalkgroup) id; + P25FullyQualifiedTalkgroup copyFqt = new P25FullyQualifiedTalkgroup(originalFqt.getWacn(), + originalFqt.getSystem(), originalFqt.getValue()); + copyFqt.setOverlap(originalFqt.overlapProperty().get()); + return copyFqt; case PRIORITY: Priority originalPriority = (Priority)id; Priority copyPriority = new Priority(); diff --git a/src/main/java/io/github/dsheirer/alias/AliasList.java b/src/main/java/io/github/dsheirer/alias/AliasList.java index b931c628..f13ad04e 100644 --- a/src/main/java/io/github/dsheirer/alias/AliasList.java +++ b/src/main/java/io/github/dsheirer/alias/AliasList.java @@ -24,10 +24,12 @@ import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; import io.github.dsheirer.alias.id.dcs.Dcs; import io.github.dsheirer.alias.id.esn.Esn; import io.github.dsheirer.alias.id.priority.Priority; +import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio; import io.github.dsheirer.alias.id.radio.Radio; import io.github.dsheirer.alias.id.radio.RadioRange; import io.github.dsheirer.alias.id.status.UnitStatusID; import io.github.dsheirer.alias.id.status.UserStatusID; +import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup; import io.github.dsheirer.alias.id.talkgroup.Talkgroup; import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange; import io.github.dsheirer.alias.id.tone.TonesID; @@ -37,9 +39,11 @@ import io.github.dsheirer.identifier.dcs.DCSIdentifier; import io.github.dsheirer.identifier.esn.ESNIdentifier; import io.github.dsheirer.identifier.patch.PatchGroup; import io.github.dsheirer.identifier.patch.PatchGroupIdentifier; +import io.github.dsheirer.identifier.radio.FullyQualifiedRadioIdentifier; import io.github.dsheirer.identifier.radio.RadioIdentifier; import io.github.dsheirer.identifier.status.UnitStatusIdentifier; import io.github.dsheirer.identifier.status.UserStatusIdentifier; +import io.github.dsheirer.identifier.talkgroup.FullyQualifiedTalkgroupIdentifier; import io.github.dsheirer.identifier.talkgroup.TalkgroupIdentifier; import io.github.dsheirer.identifier.tone.ToneIdentifier; import io.github.dsheirer.identifier.tone.ToneSequence; @@ -155,6 +159,32 @@ public class AliasList talkgroupRangeAliasList.add(talkgroupRange, alias); break; + case P25_FULLY_QUALIFIED_RADIO_ID: + P25FullyQualifiedRadio qualifiedRadio = (P25FullyQualifiedRadio) id; + + RadioAliasList p25RadioAliasList = mRadioProtocolMap.get(qualifiedRadio.getProtocol()); + + if(p25RadioAliasList == null) + { + p25RadioAliasList = new RadioAliasList(); + mRadioProtocolMap.put(qualifiedRadio.getProtocol(), p25RadioAliasList); + } + + p25RadioAliasList.add(qualifiedRadio, alias); + break; + case P25_FULLY_QUALIFIED_TALKGROUP: + P25FullyQualifiedTalkgroup qualifiedTalkgroup = (P25FullyQualifiedTalkgroup) id; + + TalkgroupAliasList p25TalkgroupAliasList = mTalkgroupProtocolMap.get(qualifiedTalkgroup.getProtocol()); + + if(p25TalkgroupAliasList == null) + { + p25TalkgroupAliasList = new TalkgroupAliasList(); + mTalkgroupProtocolMap.put(qualifiedTalkgroup.getProtocol(), p25TalkgroupAliasList); + } + + p25TalkgroupAliasList.add(qualifiedTalkgroup, alias); + break; case RADIO_ID: Radio radio = (Radio)id; @@ -444,7 +474,7 @@ public class AliasList return toList(radioAliasList.getAlias(radio)); } break; - case DCS: + case ESN: if(identifier instanceof ESNIdentifier) { return toList(getESNAlias(((ESNIdentifier)identifier).getValue())); @@ -623,6 +653,7 @@ public class AliasList */ public class TalkgroupAliasList { + private Map mFullyQualifiedTalkgroupAliasMap = new HashMap<>(); private Map mTalkgroupAliasMap = new TreeMap<>(); private Map mTalkgroupRangeAliasMap = new HashMap<>(); @@ -632,6 +663,11 @@ public class AliasList public Alias getAlias(TalkgroupIdentifier identifier) { + if(identifier instanceof FullyQualifiedTalkgroupIdentifier fqti) + { + return mFullyQualifiedTalkgroupAliasMap.get(fqti.toString()); + } + int value = identifier.getValue(); Alias mapValue = mTalkgroupAliasMap.get(value); @@ -653,26 +689,55 @@ public class AliasList public void add(Talkgroup talkgroup, Alias alias) { - //Detect talkgroup collisions and set overlap flag for both - if(mTalkgroupAliasMap.containsKey(talkgroup.getValue())) + if(talkgroup instanceof P25FullyQualifiedTalkgroup fqt) { - Alias existing = mTalkgroupAliasMap.get(talkgroup.getValue()); - - if(!existing.equals(alias)) + //Detect collisions + if(mFullyQualifiedTalkgroupAliasMap.containsKey(fqt.getHashKey())) { - talkgroup.setOverlap(true); + Alias existing = mFullyQualifiedTalkgroupAliasMap.get(fqt.getHashKey()); - for(AliasID aliasID: existing.getAliasIdentifiers()) + if(!existing.equals(alias)) { - if(aliasID instanceof Talkgroup && ((Talkgroup)aliasID).getValue() == talkgroup.getValue()) + fqt.setOverlap(true); + + for(AliasID aliasID: existing.getAliasIdentifiers()) { - aliasID.setOverlap(true); + if(aliasID instanceof P25FullyQualifiedTalkgroup existingFqt && + existingFqt.getHashKey().contentEquals(fqt.getHashKey())) + { + aliasID.setOverlap(true); + } } } } + else + { + mFullyQualifiedTalkgroupAliasMap.put(fqt.getHashKey(), alias); + } } + else + { + //Detect talkgroup collisions and set overlap flag for both + if(mTalkgroupAliasMap.containsKey(talkgroup.getValue())) + { + Alias existing = mTalkgroupAliasMap.get(talkgroup.getValue()); - mTalkgroupAliasMap.put(talkgroup.getValue(), alias); + if(!existing.equals(alias)) + { + talkgroup.setOverlap(true); + + for(AliasID aliasID: existing.getAliasIdentifiers()) + { + if(aliasID instanceof Talkgroup && ((Talkgroup)aliasID).getValue() == talkgroup.getValue()) + { + aliasID.setOverlap(true); + } + } + } + } + + mTalkgroupAliasMap.put(talkgroup.getValue(), alias); + } } public void add(TalkgroupRange talkgroupRange, Alias alias) @@ -705,6 +770,7 @@ public class AliasList */ public class RadioAliasList { + private Map mFullyQualifiedRadioAliasMap = new HashMap<>(); private Map mRadioAliasMap = new TreeMap<>(); private Map mRadioRangeAliasMap = new HashMap<>(); @@ -714,6 +780,11 @@ public class AliasList public Alias getAlias(RadioIdentifier identifier) { + if(identifier instanceof FullyQualifiedRadioIdentifier fqri) + { + return mFullyQualifiedRadioAliasMap.get(fqri.toString()); + } + int value = identifier.getValue(); Alias mapValue = mRadioAliasMap.get(value); @@ -735,26 +806,55 @@ public class AliasList public void add(Radio radio, Alias alias) { - //Detect collisions - if(mRadioAliasMap.containsKey(radio.getValue())) + if(radio instanceof P25FullyQualifiedRadio fqr) { - Alias existing = mRadioAliasMap.get(radio.getValue()); - - if(!existing.equals(alias)) + //Detect collisions + if(mFullyQualifiedRadioAliasMap.containsKey(fqr.getHashKey())) { - radio.setOverlap(true); + Alias existing = mFullyQualifiedRadioAliasMap.get(fqr.getHashKey()); - for(AliasID aliasID: existing.getAliasIdentifiers()) + if(!existing.equals(alias)) { - if(aliasID instanceof Radio && ((Radio)aliasID).getValue() == radio.getValue()) + fqr.setOverlap(true); + + for(AliasID aliasID: existing.getAliasIdentifiers()) { - aliasID.setOverlap(true); + if(aliasID instanceof P25FullyQualifiedRadio existingFqr && + existingFqr.getHashKey().contentEquals(fqr.getHashKey())) + { + aliasID.setOverlap(true); + } } } } + else + { + mFullyQualifiedRadioAliasMap.put(fqr.getHashKey(), alias); + } } + else + { + //Detect collisions + if(mRadioAliasMap.containsKey(radio.getValue())) + { + Alias existing = mRadioAliasMap.get(radio.getValue()); - mRadioAliasMap.put(radio.getValue(), alias); + if(!existing.equals(alias)) + { + radio.setOverlap(true); + + for(AliasID aliasID: existing.getAliasIdentifiers()) + { + if(aliasID instanceof Radio existingRadio && (existingRadio.getValue() == radio.getValue())) + { + aliasID.setOverlap(true); + } + } + } + } + + mRadioAliasMap.put(radio.getValue(), alias); + } } public void add(RadioRange radioRange, Alias alias) diff --git a/src/main/java/io/github/dsheirer/alias/id/AliasID.java b/src/main/java/io/github/dsheirer/alias/id/AliasID.java index a5422391..79539e35 100644 --- a/src/main/java/io/github/dsheirer/alias/id/AliasID.java +++ b/src/main/java/io/github/dsheirer/alias/id/AliasID.java @@ -35,11 +35,13 @@ import io.github.dsheirer.alias.id.legacy.talkgroup.LegacyTalkgroupID; import io.github.dsheirer.alias.id.legacy.uniqueID.UniqueID; import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID; import io.github.dsheirer.alias.id.priority.Priority; +import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio; import io.github.dsheirer.alias.id.radio.Radio; import io.github.dsheirer.alias.id.radio.RadioRange; import io.github.dsheirer.alias.id.record.Record; import io.github.dsheirer.alias.id.status.UnitStatusID; import io.github.dsheirer.alias.id.status.UserStatusID; +import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup; import io.github.dsheirer.alias.id.talkgroup.Talkgroup; import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange; import io.github.dsheirer.alias.id.tone.TonesID; @@ -61,6 +63,8 @@ import javafx.util.Callback; @JsonSubTypes.Type(value = Min.class, name = "min"), @JsonSubTypes.Type(value = MPT1327ID.class, name = "mpt1327ID"), @JsonSubTypes.Type(value = NonRecordable.class, name = "nonRecordable"), + @JsonSubTypes.Type(value = P25FullyQualifiedRadio.class, name = "p25FullyQualifiedRadio"), + @JsonSubTypes.Type(value = P25FullyQualifiedTalkgroup.class, name = "p25FullyQualifiedTalkgroup"), @JsonSubTypes.Type(value = Priority.class, name = "priority"), @JsonSubTypes.Type(value = Radio.class, name = "radio"), @JsonSubTypes.Type(value = RadioRange.class, name = "radioRange"), diff --git a/src/main/java/io/github/dsheirer/alias/id/AliasIDType.java b/src/main/java/io/github/dsheirer/alias/id/AliasIDType.java index 06061a9c..292e7a8d 100644 --- a/src/main/java/io/github/dsheirer/alias/id/AliasIDType.java +++ b/src/main/java/io/github/dsheirer/alias/id/AliasIDType.java @@ -29,6 +29,8 @@ public enum AliasIDType LOJACK("LoJack"), LTR_NET_UID("LTR-Net UID"), MIN("Passport MIN"), + P25_FULLY_QUALIFIED_RADIO_ID("P25 Fully Qualified Radio ID"), + P25_FULLY_QUALIFIED_TALKGROUP("P25 Fully Qualified Talkgroup"), PRIORITY("Audio Priority"), RADIO_ID("Radio ID"), RADIO_ID_RANGE("Radio ID Range"), @@ -56,7 +58,7 @@ public enum AliasIDType //Values used by the View-By alias editor public static EnumSet VIEW_BY_VALUES = EnumSet.of(TALKGROUP, TALKGROUP_RANGE, RADIO_ID, RADIO_ID_RANGE, - UNIT_STATUS, STATUS, TONES); + P25_FULLY_QUALIFIED_RADIO_ID, P25_FULLY_QUALIFIED_TALKGROUP, UNIT_STATUS, STATUS, TONES); public String toString() { diff --git a/src/main/java/io/github/dsheirer/alias/id/radio/P25FullyQualifiedRadio.java b/src/main/java/io/github/dsheirer/alias/id/radio/P25FullyQualifiedRadio.java new file mode 100644 index 00000000..eeb72683 --- /dev/null +++ b/src/main/java/io/github/dsheirer/alias/id/radio/P25FullyQualifiedRadio.java @@ -0,0 +1,127 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 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.id.radio; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import io.github.dsheirer.alias.id.AliasIDType; +import io.github.dsheirer.protocol.Protocol; + +/** + * Fully qualified radio. Note: this is only for P25. + */ +public class P25FullyQualifiedRadio extends Radio +{ + private int mWacn; + private int mSystem; + + public P25FullyQualifiedRadio() + { + //No-arg JAXB constructor + } + + /** + * Constructs an instance + * @param wacn for the radio + * @param system for the radio + * @param id for the radio + */ + public P25FullyQualifiedRadio(int wacn, int system, int id) + { + super(Protocol.APCO25, id); + mWacn = wacn; + mSystem = system; + } + + @Override + public AliasIDType getType() + { + return AliasIDType.P25_FULLY_QUALIFIED_RADIO_ID; + } + + /** + * WACN for this radio + * @return wacn + */ + @JacksonXmlProperty(isAttribute = true, localName = "wacn") + public int getWacn() + { + return mWacn; + } + + /** + * Sets the WACN for this radio + * @param wacn for this radio + */ + public void setWacn(int wacn) + { + mWacn = wacn; + } + + /** + * System for this radio + * @return system + */ + @JacksonXmlProperty(isAttribute = true, localName = "system") + public int getSystem() + { + return mSystem; + } + + /** + * Sets the system for this radio + * @param system for this radio + */ + public void setSystem(int system) + { + mSystem = system; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append("Fully Qualified Radio ID:").append(getWacn()); + sb.append(".").append(getSystem()); + sb.append(".").append(getValue()); + sb.append(" Protocol:").append((getProtocol())); + + if(!isValid()) + { + sb.append(" **NOT VALID**"); + } + + return sb.toString(); + } + + /** + * Hashable string key for use in a lookup map + */ + @JsonIgnore + public String getHashKey() + { + StringBuilder sb = new StringBuilder(); + sb.append(getWacn()); + sb.append(".").append(getSystem()); + sb.append(".").append(getValue()); + return sb.toString(); + } +} diff --git a/src/main/java/io/github/dsheirer/alias/id/talkgroup/P25FullyQualifiedTalkgroup.java b/src/main/java/io/github/dsheirer/alias/id/talkgroup/P25FullyQualifiedTalkgroup.java new file mode 100644 index 00000000..cb03b794 --- /dev/null +++ b/src/main/java/io/github/dsheirer/alias/id/talkgroup/P25FullyQualifiedTalkgroup.java @@ -0,0 +1,128 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 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.id.talkgroup; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; +import io.github.dsheirer.alias.id.AliasIDType; +import io.github.dsheirer.protocol.Protocol; + +/** + * Fully qualified talkgroup. Note: this is only for P25. + */ +public class P25FullyQualifiedTalkgroup extends Talkgroup +{ + private int mWacn; + private int mSystem; + + public P25FullyQualifiedTalkgroup() + { + //No-arg JAXB constructor + } + + /** + * Constructs an instance + * @param wacn for the radio + * @param system for the radio + * @param talkgroup value + */ + public P25FullyQualifiedTalkgroup(int wacn, int system, int talkgroup) + { + super(Protocol.APCO25, talkgroup); + mWacn = wacn; + mSystem = system; + } + + @Override + public AliasIDType getType() + { + return AliasIDType.P25_FULLY_QUALIFIED_TALKGROUP; + } + + /** + * WACN for this radio + * @return wacn + */ + @JacksonXmlProperty(isAttribute = true, localName = "wacn") + public int getWacn() + { + return mWacn; + } + + /** + * Sets the WACN for this radio + * @param wacn for this radio + */ + public void setWacn(int wacn) + { + mWacn = wacn; + } + + /** + * System for this radio + * @return system + */ + @JacksonXmlProperty(isAttribute = true, localName = "system") + public int getSystem() + { + return mSystem; + } + + /** + * Sets the system for this radio + * @param system for this radio + */ + public void setSystem(int system) + { + mSystem = system; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + sb.append("Fully Qualified Radio ID:").append(getWacn()); + sb.append(".").append(getSystem()); + sb.append(".").append(getValue()); + sb.append(" Protocol:").append((getProtocol())); + + if(!isValid()) + { + sb.append(" **NOT VALID**"); + } + + return sb.toString(); + } + + /** + * Hashable string key for use in a lookup map + * @return + */ + @JsonIgnore + public String getHashKey() + { + StringBuilder sb = new StringBuilder(); + sb.append(getWacn()); + sb.append(".").append(getSystem()); + sb.append(".").append(getValue()); + return sb.toString(); + } +} 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 fcc64e30..bb9eff8d 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 @@ -37,11 +37,13 @@ import io.github.dsheirer.alias.id.broadcast.BroadcastChannel; import io.github.dsheirer.alias.id.dcs.Dcs; import io.github.dsheirer.alias.id.esn.Esn; import io.github.dsheirer.alias.id.lojack.LoJackFunctionAndID; +import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio; import io.github.dsheirer.alias.id.radio.Radio; import io.github.dsheirer.alias.id.radio.RadioFormatter; import io.github.dsheirer.alias.id.radio.RadioRange; import io.github.dsheirer.alias.id.status.UnitStatusID; import io.github.dsheirer.alias.id.status.UserStatusID; +import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup; import io.github.dsheirer.alias.id.talkgroup.Talkgroup; import io.github.dsheirer.alias.id.talkgroup.TalkgroupFormatter; import io.github.dsheirer.alias.id.talkgroup.TalkgroupRange; @@ -684,8 +686,10 @@ public class AliasItemEditor extends Editor Menu p25Menu = new ProtocolMenu(Protocol.APCO25); p25Menu.getItems().add(new AddTalkgroupItem(Protocol.APCO25)); p25Menu.getItems().add(new AddTalkgroupRangeItem(Protocol.APCO25)); + p25Menu.getItems().add(new AddP25FullyQualifiedTalkgroupItem()); p25Menu.getItems().add(new AddRadioIdItem(Protocol.APCO25)); p25Menu.getItems().add(new AddRadioIdRangeItem(Protocol.APCO25)); + p25Menu.getItems().add(new AddP25FullyQualifiedRadioIdItem()); p25Menu.getItems().add(new SeparatorMenuItem()); p25Menu.getItems().add(new AddUserStatusItem()); p25Menu.getItems().add(new AddUnitStatusItem()); @@ -1322,6 +1326,44 @@ public class AliasItemEditor extends Editor } } + /** + * Menu Item for adding a new protocol-specific Fully Qualified Radio ID alias identifier + */ + public class AddP25FullyQualifiedRadioIdItem extends MenuItem + { + public AddP25FullyQualifiedRadioIdItem() + { + super("Fully Qualified Radio ID"); + setOnAction(event -> { + P25FullyQualifiedRadio radioId = new P25FullyQualifiedRadio(); + radioId.setProtocol(Protocol.APCO25); + getIdentifiersList().getItems().add(radioId); + getIdentifiersList().getSelectionModel().select(radioId); + getIdentifiersList().scrollTo(radioId); + modifiedProperty().set(true); + }); + } + } + + /** + * Menu Item for adding a new protocol-specific Fully Qualified Talkgroup alias identifier + */ + public class AddP25FullyQualifiedTalkgroupItem extends MenuItem + { + public AddP25FullyQualifiedTalkgroupItem() + { + super("Fully Qualified Talkgroup"); + setOnAction(event -> { + P25FullyQualifiedTalkgroup talkgroup = new P25FullyQualifiedTalkgroup(); + talkgroup.setProtocol(Protocol.APCO25); + getIdentifiersList().getItems().add(talkgroup); + getIdentifiersList().getSelectionModel().select(talkgroup); + getIdentifiersList().scrollTo(talkgroup); + modifiedProperty().set(true); + }); + } + } + /** * Menu Item for adding a new protocol-specific Radio ID range alias identifier */ @@ -1505,7 +1547,25 @@ public class AliasItemEditor extends Editor if(item != null) { - if(item instanceof Talkgroup) + if(item instanceof P25FullyQualifiedTalkgroup fqt) + { + StringBuilder sb = new StringBuilder(); + sb.append("APCO-25 Fully Qualified Talkgroup:").append(fqt.getWacn()); + sb.append(".").append(fqt.getSystem()); + sb.append(".").append(fqt.getValue()); + + if(fqt.overlapProperty().get()) + { + sb.append(" - Error: Overlap"); + } + + if(!fqt.isValid()) + { + sb.append(" **NOT VALID**"); + } + setText(sb.toString()); + } + else if(item instanceof Talkgroup) { Talkgroup talkgroup = (Talkgroup)item; Protocol protocol = talkgroup.getProtocol(); @@ -1552,6 +1612,24 @@ public class AliasItemEditor extends Editor } setText(sb.toString()); } + else if(item instanceof P25FullyQualifiedRadio fqr) + { + StringBuilder sb = new StringBuilder(); + sb.append("APCO-25 Fully Qualified Radio ID:").append(fqr.getWacn()); + sb.append(".").append(fqr.getSystem()); + sb.append(".").append(fqr.getValue()); + + if(fqr.overlapProperty().get()) + { + sb.append(" - Error: Overlap"); + } + + if(!fqr.isValid()) + { + sb.append(" **NOT VALID**"); + } + setText(sb.toString()); + } else if(item instanceof Radio) { Radio radio = (Radio)item; diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/IdentifierEditorFactory.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/IdentifierEditorFactory.java index cb7f775e..354e0ee4 100644 --- a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/IdentifierEditorFactory.java +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/IdentifierEditorFactory.java @@ -41,6 +41,10 @@ public class IdentifierEditorFactory return new EsnEditor(); case LOJACK: return new LojackEditor(); + case P25_FULLY_QUALIFIED_RADIO_ID: + return new P25FullyQualifiedRadioIdEditor(userPreferences); + case P25_FULLY_QUALIFIED_TALKGROUP: + return new P25FullyQualifiedTalkgroupEditor(userPreferences); case RADIO_ID: return new RadioIdEditor(userPreferences); case RADIO_ID_RANGE: diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/P25FullyQualifiedRadioIdEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/P25FullyQualifiedRadioIdEditor.java new file mode 100644 index 00000000..ae80f177 --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/P25FullyQualifiedRadioIdEditor.java @@ -0,0 +1,270 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 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.gui.playlist.alias.identifier; + +import io.github.dsheirer.alias.id.radio.P25FullyQualifiedRadio; +import io.github.dsheirer.gui.control.HexFormatter; +import io.github.dsheirer.gui.control.IntegerFormatter; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.identifier.IntegerFormat; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.geometry.HPos; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.control.TextFormatter; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.GridPane; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Editor for P25 fully qualified radio alias identifiers + */ +public class P25FullyQualifiedRadioIdEditor extends IdentifierEditor +{ + private static final Logger mLog = LoggerFactory.getLogger(P25FullyQualifiedRadioIdEditor.class); + + private UserPreferences mUserPreferences; + private Label mProtocolLabel; + private TextField mWacnField; + private TextField mSystemField; + private TextField mRadioField; + private TextFormatter mWacnTextFormatter; + private TextFormatter mSystemTextFormatter; + private TextFormatter mRadioTextFormatter; + private WacnValueChangeListener mWacnValueChangeListener = new WacnValueChangeListener(); + private SystemValueChangeListener mSystemValueChangeListener = new SystemValueChangeListener(); + private RadioValueChangeListener mRadioValueChangeListener = new RadioValueChangeListener(); + + /** + * Constructs an instance + * @param userPreferences for determining user preferred talkgroup formats + */ + public P25FullyQualifiedRadioIdEditor(UserPreferences userPreferences) + { + mUserPreferences = userPreferences; + + GridPane gridPane = new GridPane(); + gridPane.setHgap(5); + gridPane.setVgap(3); + + GridPane.setConstraints(getProtocolLabel(), 0, 0); + gridPane.getChildren().add(getProtocolLabel()); + + Label valueLabel = new Label("WACN"); + GridPane.setHalignment(valueLabel, HPos.RIGHT); + GridPane.setConstraints(valueLabel, 1, 0); + gridPane.getChildren().add(valueLabel); + + GridPane.setConstraints(getWacnField(), 2, 0); + gridPane.getChildren().add(getWacnField()); + + Label systemLabel = new Label("System"); + GridPane.setHalignment(systemLabel, HPos.RIGHT); + GridPane.setConstraints(systemLabel, 3, 0); + gridPane.getChildren().add(systemLabel); + + GridPane.setConstraints(getSystemField(), 4, 0); + gridPane.getChildren().add(getSystemField()); + + Label radioLabel = new Label("Radio ID"); + GridPane.setHalignment(radioLabel, HPos.RIGHT); + GridPane.setConstraints(radioLabel, 5, 0); + gridPane.getChildren().add(radioLabel); + + GridPane.setConstraints(getRadioField(), 6, 0); + gridPane.getChildren().add(getRadioField()); + + getChildren().add(gridPane); + } + + @Override + public void setItem(P25FullyQualifiedRadio item) + { + super.setItem(item); + + P25FullyQualifiedRadio fqr = getItem(); + + getProtocolLabel().setDisable(fqr == null); + getWacnField().setDisable(fqr == null); + getSystemField().setDisable(fqr == null); + getRadioField().setDisable(fqr == null); + + if(fqr != null) + { + getProtocolLabel().setText(fqr.getProtocol().toString()); + updateTextFormatter(); + } + else + { + getWacnField().setText(null); + getSystemField().setText(null); + getRadioField().setText(null); + } + + modifiedProperty().set(false); + } + + private void updateTextFormatter() + { + if(mWacnTextFormatter != null) + { + mWacnTextFormatter.valueProperty().removeListener(mWacnValueChangeListener); + } + if(mSystemTextFormatter != null) + { + mSystemTextFormatter.valueProperty().removeListener(mSystemValueChangeListener); + } + if(mRadioTextFormatter != null) + { + mRadioTextFormatter.valueProperty().removeListener(mRadioValueChangeListener); + } + + IntegerFormat format = mUserPreferences.getTalkgroupFormatPreference().getTalkgroupFormat(getItem().getProtocol()); + + if(format == IntegerFormat.DECIMAL && (mRadioTextFormatter == null || !(mRadioTextFormatter instanceof IntegerFormatter))) + { + mWacnTextFormatter = new IntegerFormatter(0,0xFFFFF); + mSystemTextFormatter = new IntegerFormatter(0,0xFFF); + mRadioTextFormatter = new IntegerFormatter(0,0xFFFFFF); + + mWacnField.setTooltip(new Tooltip("Format: 0 - 1048575")); + mSystemField.setTooltip(new Tooltip("Format: 0 - 4095")); + mRadioField.setTooltip(new Tooltip("Format: 0 - 16777215")); + } + else if(format == IntegerFormat.HEXADECIMAL && (mRadioTextFormatter == null || !(mRadioTextFormatter instanceof HexFormatter))) + { + mWacnTextFormatter = new HexFormatter(0,0xFFFFF); + mSystemTextFormatter = new HexFormatter(0,0xFFF); + mRadioTextFormatter = new HexFormatter(0,0xFFFFFF); + + mWacnField.setTooltip(new Tooltip("Format: 0 - FFFFF")); + mSystemField.setTooltip(new Tooltip("Format: 0 - FFF")); + mRadioField.setTooltip(new Tooltip("Format: 0 - FFFFFF")); + } + + mWacnField.setTextFormatter(mWacnTextFormatter); + mSystemField.setTextFormatter(mSystemTextFormatter); + mRadioField.setTextFormatter(mRadioTextFormatter); + + mWacnTextFormatter.setValue(getItem() != null ? getItem().getWacn() : null); + mSystemTextFormatter.setValue(getItem() != null ? getItem().getSystem() : null); + mRadioTextFormatter.setValue(getItem() != null ? getItem().getValue() : null); + + mWacnTextFormatter.valueProperty().addListener(mWacnValueChangeListener); + mSystemTextFormatter.valueProperty().addListener(mSystemValueChangeListener); + mRadioTextFormatter.valueProperty().addListener(mRadioValueChangeListener); + } + + @Override + public void save() + { + //no-op + } + + @Override + public void dispose() + { + //no-op + } + + private Label getProtocolLabel() + { + if(mProtocolLabel == null) + { + mProtocolLabel = new Label(); + } + + return mProtocolLabel; + } + + private TextField getWacnField() + { + if(mWacnField == null) + { + mWacnField = new TextField(); + mWacnField.setTextFormatter(mWacnTextFormatter); + } + + return mWacnField; + } + + private TextField getSystemField() + { + if(mSystemField == null) + { + mSystemField = new TextField(); + mSystemField.setTextFormatter(mSystemTextFormatter); + } + + return mSystemField; + } + + private TextField getRadioField() + { + if(mRadioField == null) + { + mRadioField = new TextField(); + mRadioField.setTextFormatter(mRadioTextFormatter); + } + + return mRadioField; + } + + public class WacnValueChangeListener implements ChangeListener + { + @Override + public void changed(ObservableValue observable, Integer oldValue, Integer newValue) + { + if(getItem() != null) + { + getItem().setWacn(newValue != null ? newValue : 0); + modifiedProperty().set(true); + } + } + } + + public class SystemValueChangeListener implements ChangeListener + { + @Override + public void changed(ObservableValue observable, Integer oldValue, Integer newValue) + { + if(getItem() != null) + { + getItem().setSystem(newValue != null ? newValue : 0); + modifiedProperty().set(true); + } + } + } + + public class RadioValueChangeListener implements ChangeListener + { + @Override + public void changed(ObservableValue observable, Integer oldValue, Integer newValue) + { + if(getItem() != null) + { + getItem().setValue(newValue != null ? newValue : 0); + modifiedProperty().set(true); + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/P25FullyQualifiedTalkgroupEditor.java b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/P25FullyQualifiedTalkgroupEditor.java new file mode 100644 index 00000000..9a59a51c --- /dev/null +++ b/src/main/java/io/github/dsheirer/gui/playlist/alias/identifier/P25FullyQualifiedTalkgroupEditor.java @@ -0,0 +1,270 @@ +/* + * ***************************************************************************** + * Copyright (C) 2014-2023 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.gui.playlist.alias.identifier; + +import io.github.dsheirer.alias.id.talkgroup.P25FullyQualifiedTalkgroup; +import io.github.dsheirer.gui.control.HexFormatter; +import io.github.dsheirer.gui.control.IntegerFormatter; +import io.github.dsheirer.preference.UserPreferences; +import io.github.dsheirer.preference.identifier.IntegerFormat; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.geometry.HPos; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.control.TextFormatter; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.GridPane; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Editor for P25 fully qualified talkgroup alias identifiers + */ +public class P25FullyQualifiedTalkgroupEditor extends IdentifierEditor +{ + private static final Logger mLog = LoggerFactory.getLogger(P25FullyQualifiedTalkgroupEditor.class); + + private UserPreferences mUserPreferences; + private Label mProtocolLabel; + private TextField mWacnField; + private TextField mSystemField; + private TextField mTalkgroupField; + private TextFormatter mWacnTextFormatter; + private TextFormatter mSystemTextFormatter; + private TextFormatter mTalkgroupTextFormatter; + private WacnValueChangeListener mWacnValueChangeListener = new WacnValueChangeListener(); + private SystemValueChangeListener mSystemValueChangeListener = new SystemValueChangeListener(); + private TalkgroupValueChangeListener mTalkgroupValueChangeListener = new TalkgroupValueChangeListener(); + + /** + * Constructs an instance + * @param userPreferences for determining user preferred talkgroup formats + */ + public P25FullyQualifiedTalkgroupEditor(UserPreferences userPreferences) + { + mUserPreferences = userPreferences; + + GridPane gridPane = new GridPane(); + gridPane.setHgap(5); + gridPane.setVgap(3); + + GridPane.setConstraints(getProtocolLabel(), 0, 0); + gridPane.getChildren().add(getProtocolLabel()); + + Label valueLabel = new Label("WACN"); + GridPane.setHalignment(valueLabel, HPos.RIGHT); + GridPane.setConstraints(valueLabel, 1, 0); + gridPane.getChildren().add(valueLabel); + + GridPane.setConstraints(getWacnField(), 2, 0); + gridPane.getChildren().add(getWacnField()); + + Label systemLabel = new Label("System"); + GridPane.setHalignment(systemLabel, HPos.RIGHT); + GridPane.setConstraints(systemLabel, 3, 0); + gridPane.getChildren().add(systemLabel); + + GridPane.setConstraints(getSystemField(), 4, 0); + gridPane.getChildren().add(getSystemField()); + + Label radioLabel = new Label("Talkgroup"); + GridPane.setHalignment(radioLabel, HPos.RIGHT); + GridPane.setConstraints(radioLabel, 5, 0); + gridPane.getChildren().add(radioLabel); + + GridPane.setConstraints(getTalkgroupField(), 6, 0); + gridPane.getChildren().add(getTalkgroupField()); + + getChildren().add(gridPane); + } + + @Override + public void setItem(P25FullyQualifiedTalkgroup item) + { + super.setItem(item); + + P25FullyQualifiedTalkgroup fqt = getItem(); + + getProtocolLabel().setDisable(fqt == null); + getWacnField().setDisable(fqt == null); + getSystemField().setDisable(fqt == null); + getTalkgroupField().setDisable(fqt == null); + + if(fqt != null) + { + getProtocolLabel().setText(fqt.getProtocol().toString()); + updateTextFormatter(); + } + else + { + getWacnField().setText(null); + getSystemField().setText(null); + getTalkgroupField().setText(null); + } + + modifiedProperty().set(false); + } + + private void updateTextFormatter() + { + if(mWacnTextFormatter != null) + { + mWacnTextFormatter.valueProperty().removeListener(mWacnValueChangeListener); + } + if(mSystemTextFormatter != null) + { + mSystemTextFormatter.valueProperty().removeListener(mSystemValueChangeListener); + } + if(mTalkgroupTextFormatter != null) + { + mTalkgroupTextFormatter.valueProperty().removeListener(mTalkgroupValueChangeListener); + } + + IntegerFormat format = mUserPreferences.getTalkgroupFormatPreference().getTalkgroupFormat(getItem().getProtocol()); + + if(format == IntegerFormat.DECIMAL && (mTalkgroupTextFormatter == null || !(mTalkgroupTextFormatter instanceof IntegerFormatter))) + { + mWacnTextFormatter = new IntegerFormatter(0,0xFFFFF); + mSystemTextFormatter = new IntegerFormatter(0,0xFFF); + mTalkgroupTextFormatter = new IntegerFormatter(0,0xFFFF); + + mWacnField.setTooltip(new Tooltip("Format: 0 - 1048575")); + mSystemField.setTooltip(new Tooltip("Format: 0 - 4095")); + mTalkgroupField.setTooltip(new Tooltip("Format: 0 - 65535")); + } + else if(format == IntegerFormat.HEXADECIMAL && (mTalkgroupTextFormatter == null || !(mTalkgroupTextFormatter instanceof HexFormatter))) + { + mWacnTextFormatter = new HexFormatter(0,0xFFFFF); + mSystemTextFormatter = new HexFormatter(0,0xFFF); + mTalkgroupTextFormatter = new HexFormatter(0,0xFFFFFF); + + mWacnField.setTooltip(new Tooltip("Format: 0 - FFFFF")); + mSystemField.setTooltip(new Tooltip("Format: 0 - FFF")); + mTalkgroupField.setTooltip(new Tooltip("Format: 0 - FFFF")); + } + + mWacnField.setTextFormatter(mWacnTextFormatter); + mSystemField.setTextFormatter(mSystemTextFormatter); + mTalkgroupField.setTextFormatter(mTalkgroupTextFormatter); + + mWacnTextFormatter.setValue(getItem() != null ? getItem().getWacn() : null); + mSystemTextFormatter.setValue(getItem() != null ? getItem().getSystem() : null); + mTalkgroupTextFormatter.setValue(getItem() != null ? getItem().getValue() : null); + + mWacnTextFormatter.valueProperty().addListener(mWacnValueChangeListener); + mSystemTextFormatter.valueProperty().addListener(mSystemValueChangeListener); + mTalkgroupTextFormatter.valueProperty().addListener(mTalkgroupValueChangeListener); + } + + @Override + public void save() + { + //no-op + } + + @Override + public void dispose() + { + //no-op + } + + private Label getProtocolLabel() + { + if(mProtocolLabel == null) + { + mProtocolLabel = new Label(); + } + + return mProtocolLabel; + } + + private TextField getWacnField() + { + if(mWacnField == null) + { + mWacnField = new TextField(); + mWacnField.setTextFormatter(mWacnTextFormatter); + } + + return mWacnField; + } + + private TextField getSystemField() + { + if(mSystemField == null) + { + mSystemField = new TextField(); + mSystemField.setTextFormatter(mSystemTextFormatter); + } + + return mSystemField; + } + + private TextField getTalkgroupField() + { + if(mTalkgroupField == null) + { + mTalkgroupField = new TextField(); + mTalkgroupField.setTextFormatter(mTalkgroupTextFormatter); + } + + return mTalkgroupField; + } + + public class WacnValueChangeListener implements ChangeListener + { + @Override + public void changed(ObservableValue observable, Integer oldValue, Integer newValue) + { + if(getItem() != null) + { + getItem().setWacn(newValue != null ? newValue : 0); + modifiedProperty().set(true); + } + } + } + + public class SystemValueChangeListener implements ChangeListener + { + @Override + public void changed(ObservableValue observable, Integer oldValue, Integer newValue) + { + if(getItem() != null) + { + getItem().setSystem(newValue != null ? newValue : 0); + modifiedProperty().set(true); + } + } + } + + public class TalkgroupValueChangeListener implements ChangeListener + { + @Override + public void changed(ObservableValue observable, Integer oldValue, Integer newValue) + { + if(getItem() != null) + { + getItem().setValue(newValue != null ? newValue : 0); + modifiedProperty().set(true); + } + } + } +} diff --git a/src/main/java/io/github/dsheirer/identifier/Form.java b/src/main/java/io/github/dsheirer/identifier/Form.java index 989228d4..b6cfc28c 100644 --- a/src/main/java/io/github/dsheirer/identifier/Form.java +++ b/src/main/java/io/github/dsheirer/identifier/Form.java @@ -37,7 +37,6 @@ public enum Form DTMF, ENCRYPTION_KEY, ESN, - FULLY_QUALIFIED_IDENTIFIER, IPV4_ADDRESS, KNOX_TONE, LOCATION, diff --git a/src/main/java/io/github/dsheirer/record/wave/AudioMetadataUtils.java b/src/main/java/io/github/dsheirer/record/wave/AudioMetadataUtils.java index 44e02615..89715ec3 100644 --- a/src/main/java/io/github/dsheirer/record/wave/AudioMetadataUtils.java +++ b/src/main/java/io/github/dsheirer/record/wave/AudioMetadataUtils.java @@ -1,6 +1,6 @@ /* * ***************************************************************************** - * Copyright (C) 2014-2022 Dennis Sheirer + * Copyright (C) 2014-2023 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 @@ -35,9 +35,6 @@ import io.github.dsheirer.identifier.configuration.SiteConfigurationIdentifier; import io.github.dsheirer.identifier.configuration.SystemConfigurationIdentifier; import io.github.dsheirer.identifier.decoder.DecoderLogicalChannelNameIdentifier; import io.github.dsheirer.properties.SystemProperties; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.Charset; @@ -47,6 +44,8 @@ import java.util.Date; import java.util.EnumMap; import java.util.List; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class AudioMetadataUtils {