From 619cb548dfaddb62297e492860f9eef250d21f40 Mon Sep 17 00:00:00 2001 From: Bryan Biedenkapp Date: Fri, 25 Oct 2024 15:26:32 -0400 Subject: [PATCH] add support for per TGID RID permission lists; add call router support to check for source RID permission to TGID; --- .../schema/talkgroup_rules.yaml_schema.json | 9 + configs/talkgroup_rules.example.yml | 12 + src/common/lookups/TalkgroupRulesLookup.cpp | 3 +- src/common/lookups/TalkgroupRulesLookup.h | 29 ++ src/fne/network/FNENetwork.h | 1 + src/fne/network/callhandler/TagDMRData.cpp | 29 ++ src/fne/network/callhandler/TagNXDNData.cpp | 29 ++ src/fne/network/callhandler/TagP25Data.cpp | 31 +- src/tged/TGEditRIDListWnd.h | 301 ++++++++++++++++++ src/tged/TGEditWnd.h | 13 + src/tged/TGListWnd.h | 7 +- 11 files changed, 460 insertions(+), 4 deletions(-) create mode 100644 src/tged/TGEditRIDListWnd.h diff --git a/configs/schema/talkgroup_rules.yaml_schema.json b/configs/schema/talkgroup_rules.yaml_schema.json index eb26fb6c..950b69bc 100644 --- a/configs/schema/talkgroup_rules.yaml_schema.json +++ b/configs/schema/talkgroup_rules.yaml_schema.json @@ -86,6 +86,15 @@ "type": "number", "uniqueItems": true } + }, + "rid_permitted": { + "description": "List of radio IDs permitted to transmit on the talkgroup.", + "type": "array", + "items": { + "description": "Radio ID.", + "type": "number", + "uniqueItems": true + } } } }, diff --git a/configs/talkgroup_rules.example.yml b/configs/talkgroup_rules.example.yml index 92f76794..b9179fe1 100644 --- a/configs/talkgroup_rules.example.yml +++ b/configs/talkgroup_rules.example.yml @@ -31,6 +31,8 @@ groupVoice: # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming). # If this list is empty *all* peers are preferred. (Trunking Only) preferred: [] + # List of radio IDs permitted to transmit on the talkgroup. + rid_permitted: [] # # Source Configuration # @@ -66,6 +68,8 @@ groupVoice: # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming). # If this list is empty *all* peers are preferred. (Trunking Only) preferred: [] + # List of radio IDs permitted to transmit on the talkgroup. + rid_permitted: [] # # Source Configuration # @@ -107,6 +111,8 @@ groupVoice: # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming). # If this list is empty *all* peers are preferred. (Trunking Only) preferred: [] + # List of radio IDs permitted to transmit on the talkgroup. + rid_permitted: [] # # Source Configuration # @@ -140,6 +146,8 @@ groupVoice: # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming). # If this list is empty *all* peers are preferred. (Trunking Only) preferred: [] + # List of radio IDs permitted to transmit on the talkgroup. + rid_permitted: [] # # Source Configuration # @@ -173,6 +181,8 @@ groupVoice: # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming). # If this list is empty *all* peers are preferred. (Trunking Only) preferred: [] + # List of radio IDs permitted to transmit on the talkgroup. + rid_permitted: [] # # Source Configuration # @@ -206,6 +216,8 @@ groupVoice: # sites not listed here will be non-preferred and will cause a AFF_GRP_RSP DENY, typically triggering roaming). # If this list is empty *all* peers are preferred. (Trunking Only) preferred: [] + # List of radio IDs permitted to transmit on the talkgroup. + rid_permitted: [] # # Source Configuration # diff --git a/src/common/lookups/TalkgroupRulesLookup.cpp b/src/common/lookups/TalkgroupRulesLookup.cpp index b7ac7f4d..d2722a64 100644 --- a/src/common/lookups/TalkgroupRulesLookup.cpp +++ b/src/common/lookups/TalkgroupRulesLookup.cpp @@ -318,6 +318,7 @@ bool TalkgroupRulesLookup::load() uint32_t rewrCount = groupVoice.config().rewrite().size(); uint32_t alwyCount = groupVoice.config().alwaysSend().size(); uint32_t prefCount = groupVoice.config().preferred().size(); + uint32_t permRIDCount = groupVoice.config().permittedRIDs().size(); if (incCount > 0 && excCount > 0) { ::LogWarning(LOG_HOST, "Talkgroup (%s) defines both inclusions and exclusions! Inclusion rules take precedence and exclusion rules will be ignored.", groupName.c_str()); @@ -327,7 +328,7 @@ bool TalkgroupRulesLookup::load() ::LogWarning(LOG_HOST, "Talkgroup (%s) is marked as affiliation required and has a defined always send list! Always send peers take rule precedence and defined peers will always receive traffic.", groupName.c_str()); } - ::LogInfoEx(LOG_HOST, "Talkgroup NAME: %s SRC_TGID: %u SRC_TS: %u ACTIVE: %u PARROT: %u AFFILIATED: %u INCLUSIONS: %u EXCLUSIONS: %u REWRITES: %u ALWAYS: %u PREFERRED: %u", groupName.c_str(), tgId, tgSlot, active, parrot, affil, incCount, excCount, rewrCount, alwyCount, prefCount); + ::LogInfoEx(LOG_HOST, "Talkgroup NAME: %s SRC_TGID: %u SRC_TS: %u ACTIVE: %u PARROT: %u AFFILIATED: %u INCLUSIONS: %u EXCLUSIONS: %u REWRITES: %u ALWAYS: %u PREFERRED: %u PERMITTED RIDS: %u", groupName.c_str(), tgId, tgSlot, active, parrot, affil, incCount, excCount, rewrCount, alwyCount, prefCount, permRIDCount); } size_t size = m_groupVoice.size(); diff --git a/src/common/lookups/TalkgroupRulesLookup.h b/src/common/lookups/TalkgroupRulesLookup.h index 564baec9..8fb905f3 100644 --- a/src/common/lookups/TalkgroupRulesLookup.h +++ b/src/common/lookups/TalkgroupRulesLookup.h @@ -193,6 +193,7 @@ namespace lookups m_rewrite(), m_alwaysSend(), m_preferred(), + m_permittedRIDs(), m_nonPreferred(false) { /* stub */ @@ -247,6 +248,14 @@ namespace lookups m_preferred.push_back(peerId); } } + + yaml::Node& permittedRIDList = node["rid_permitted"]; + if (permittedRIDList.size() > 0U) { + for (size_t i = 0; i < permittedRIDList.size(); i++) { + uint32_t radioId = permittedRIDList[i].as(0U); + m_permittedRIDs.push_back(radioId); + } + } } /** @@ -264,6 +273,7 @@ namespace lookups m_rewrite = data.m_rewrite; m_alwaysSend = data.m_alwaysSend; m_preferred = data.m_preferred; + m_permittedRIDs = data.m_permittedRIDs; m_nonPreferred = data.m_nonPreferred; } @@ -295,6 +305,11 @@ namespace lookups * @returns uint8_t Total count of preferred peer rules. */ uint8_t preferredSize() const { return m_preferred.size(); } + /** + * @brief Gets the count of permitted RIDs. + * @returns uint8_t Total count of permitted RID rules. + */ + uint8_t permittedRIDsSize() const { return m_permittedRIDs.size(); } /** * @brief Return the YAML structure for this TalkgroupRuleConfig. @@ -352,6 +367,15 @@ namespace lookups } } node["preferred"] = preferredList; + + yaml::Node permittedRIDList; + if (m_permittedRIDs.size() > 0U) { + for (auto rid : m_permittedRIDs) { + yaml::Node& newRid = permittedRIDList.push_back(); + newRid = __INT_STR(rid); + } + } + node["rid_permitted"] = permittedRIDList; } public: @@ -388,6 +412,11 @@ namespace lookups */ __PROPERTY_PLAIN(std::vector, preferred); + /** + * @brief List of radios IDs permitted to transmit on the talkgroup. + */ + __PROPERTY_PLAIN(std::vector, permittedRIDs); + /** * @brief Flag indicating whether or not the talkgroup is a non-preferred. */ diff --git a/src/fne/network/FNENetwork.h b/src/fne/network/FNENetwork.h index 010825e2..039fd14e 100644 --- a/src/fne/network/FNENetwork.h +++ b/src/fne/network/FNENetwork.h @@ -75,6 +75,7 @@ namespace network #define INFLUXDB_ERRSTR_INV_TALKGROUP "illegal/invalid talkgroup" #define INFLUXDB_ERRSTR_DISABLED_TALKGROUP "disabled talkgroup" #define INFLUXDB_ERRSTR_INV_SLOT "invalid slot for talkgroup" + #define INFLUXDB_ERRSTR_RID_NOT_PERMITTED "RID not permitted for talkgroup" // --------------------------------------------------------------------------- // Class Prototypes diff --git a/src/fne/network/callhandler/TagDMRData.cpp b/src/fne/network/callhandler/TagDMRData.cpp index 1298d9ff..fc94ad51 100644 --- a/src/fne/network/callhandler/TagDMRData.cpp +++ b/src/fne/network/callhandler/TagDMRData.cpp @@ -738,6 +738,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .request(m_network->m_influxServer); } + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getSlotNo()); return false; } @@ -790,6 +791,7 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .request(m_network->m_influxServer); } + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); return false; } @@ -810,10 +812,12 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .request(m_network->m_influxServer); } + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); return false; } + // is the TGID active? if (!tg.config().active()) { // report error event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -829,9 +833,34 @@ bool TagDMRData::validate(uint32_t peerId, data::NetData& data, uint32_t streamI .request(m_network->m_influxServer); } + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); return false; } + + // does the TGID have a permitted RID list? + if (tg.config().permittedRIDs().size() > 0) { + // does the transmitting RID have permission? + std::vector permittedRIDs = tg.config().permittedRIDs(); + if (std::find(permittedRIDs.begin(), permittedRIDs.end(), data.getSrcId()) == permittedRIDs.end()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(data.getSrcId())) + .tag("dstId", std::to_string(data.getDstId())) + .field("message", INFLUXDB_ERRSTR_RID_NOT_PERMITTED) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .request(m_network->m_influxServer); + } + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_DMR, NET_ICC::REJECT_TRAFFIC, data.getDstId(), data.getSlotNo()); + return false; + } + } } return true; diff --git a/src/fne/network/callhandler/TagNXDNData.cpp b/src/fne/network/callhandler/TagNXDNData.cpp index 0fab289b..aecd8918 100644 --- a/src/fne/network/callhandler/TagNXDNData.cpp +++ b/src/fne/network/callhandler/TagNXDNData.cpp @@ -552,6 +552,7 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .request(m_network->m_influxServer); } + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); return false; } @@ -580,6 +581,7 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .request(m_network->m_influxServer); } + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); return false; } @@ -605,10 +607,12 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .request(m_network->m_influxServer); } + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); return false; } + // is the TGID active? if (!tg.config().active()) { // report error event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -623,10 +627,35 @@ bool TagNXDNData::validate(uint32_t peerId, lc::RTCH& lc, uint8_t messageType, u .request(m_network->m_influxServer); } + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); return false; } + // does the TGID have a permitted RID list? + if (tg.config().permittedRIDs().size() > 0) { + // does the transmitting RID have permission? + std::vector permittedRIDs = tg.config().permittedRIDs(); + if (std::find(permittedRIDs.begin(), permittedRIDs.end(), lc.getSrcId()) == permittedRIDs.end()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(lc.getSrcId())) + .tag("dstId", std::to_string(lc.getDstId())) + .field("message", INFLUXDB_ERRSTR_RID_NOT_PERMITTED) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .request(m_network->m_influxServer); + } + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_NXDN, NET_ICC::REJECT_TRAFFIC, lc.getDstId()); + return false; + } + } + return true; } diff --git a/src/fne/network/callhandler/TagP25Data.cpp b/src/fne/network/callhandler/TagP25Data.cpp index 9a99d5a8..f81a234b 100644 --- a/src/fne/network/callhandler/TagP25Data.cpp +++ b/src/fne/network/callhandler/TagP25Data.cpp @@ -1034,6 +1034,7 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .request(m_network->m_influxServer); } + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); return false; } @@ -1066,7 +1067,8 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .request(m_network->m_influxServer); } - m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); return false; } } @@ -1115,10 +1117,12 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .request(m_network->m_influxServer); } + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); return false; } + // is the TGID active? if (!tg.config().active()) { // report error event to InfluxDB if (m_network->m_enableInfluxDB) { @@ -1133,10 +1137,35 @@ bool TagP25Data::validate(uint32_t peerId, lc::LC& control, DUID::E duid, const .request(m_network->m_influxServer); } + // report In-Call Control to the peer sending traffic m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); return false; } + // does the TGID have a permitted RID list? + if (tg.config().permittedRIDs().size() > 0) { + // does the transmitting RID have permission? + std::vector permittedRIDs = tg.config().permittedRIDs(); + if (std::find(permittedRIDs.begin(), permittedRIDs.end(), control.getSrcId()) == permittedRIDs.end()) { + // report error event to InfluxDB + if (m_network->m_enableInfluxDB) { + influxdb::QueryBuilder() + .meas("call_error_event") + .tag("peerId", std::to_string(peerId)) + .tag("streamId", std::to_string(streamId)) + .tag("srcId", std::to_string(control.getSrcId())) + .tag("dstId", std::to_string(control.getDstId())) + .field("message", INFLUXDB_ERRSTR_RID_NOT_PERMITTED) + .timestamp(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + .request(m_network->m_influxServer); + } + + // report In-Call Control to the peer sending traffic + m_network->writePeerICC(peerId, NET_SUBFUNC::PROTOCOL_SUBFUNC_P25, NET_ICC::REJECT_TRAFFIC, control.getDstId()); + return false; + } + } + return true; } diff --git a/src/tged/TGEditRIDListWnd.h b/src/tged/TGEditRIDListWnd.h new file mode 100644 index 00000000..1bbc9410 --- /dev/null +++ b/src/tged/TGEditRIDListWnd.h @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Digital Voice Modem - Talkgroup Editor + * GPLv2 Open Source. Use is subject to license terms. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright (C) 2024 Bryan Biedenkapp, N2PLL + * + */ +/** + * @file TGEditRIDListWnd.h + * @ingroup tged + */ +#if !defined(__TG_EDIT_RID_LIST_WND_H__) +#define __TG_EDIT_RID_LIST_WND_H__ + +#include "common/Log.h" + +#include "tged/CloseWndBase.h" +#include "tged/TGEdMain.h" + +#include +using namespace finalcut; + +// --------------------------------------------------------------------------- +// Class Declaration +// --------------------------------------------------------------------------- + +/** + * @brief This class implements the line edit control for radio IDs. + * @ingroup tged + */ +class HOST_SW_API RIDLineEdit final : public FLineEdit { +public: + /** + * @brief Initializes a new instance of the RIDLineEdit class. + * @param widget + */ + explicit RIDLineEdit(FWidget* widget = nullptr) : FLineEdit{widget} + { + setInputFilter("[[:digit:]]"); + } + + /* + ** Event Handlers + */ + + /** + * @brief Event that occurs on keyboard key press. + * @param e Keyboard Event. + */ + void onKeyPress(finalcut::FKeyEvent* e) override + { + const auto key = e->key(); + if (key == FKey::Up) { + emitCallback("up-pressed"); + e->accept(); + return; + } else if (key == FKey::Down) { + emitCallback("down-pressed"); + e->accept(); + return; + } + + if (key == FKey::Insert) { + emitCallback("insert-pressed"); + e->accept(); + return; + } else if (key == FKey::Return) { + emitCallback("return-pressed"); + e->accept(); + return; + } + + FLineEdit::onKeyPress(e); + } +}; + +/** + * @brief This class implements the talkgroup radio ID list editor window. + * @ingroup tged + */ +class HOST_SW_API TGEditRIDListWnd final : public CloseWndBase { +public: + /** + * @brief Initializes a new instance of the TGEditWnd class. + * @param rule + * @param peerList + * @param title + * @param widget + */ + explicit TGEditRIDListWnd(lookups::TalkgroupRuleGroupVoice rule, std::vector ridList, + std::string title = "Radio ID List", FWidget *widget = nullptr) : CloseWndBase{widget} + { + m_rule = rule; + this->ridList = ridList; + m_title = title; + } + + /** + * @brief List of radio IDs. + */ + std::vector ridList; + +private: + bool m_skipSaving; + std::string m_title; + + lookups::TalkgroupRuleGroupVoice m_rule; + + FListBox m_listBox{this}; + + FButton m_add{"&Add", this}; + FButton m_delete{"&Delete", this}; + + FLabel m_entryLabel{"Radio ID: ", this}; + RIDLineEdit m_entry{this}; + + /** + * @brief Initializes the window layout. + */ + void initLayout() override + { + FDialog::setText(m_title); + FDialog::setSize(FSize{40, 21}); + + m_enableSetButton = false; + CloseWndBase::initLayout(); + loadList(); + } + + /** + * @brief Initializes window controls. + */ + void initControls() override + { + m_closeButton.setText("&OK"); + + m_add.setGeometry(FPoint(2, int(getHeight() - 4)), FSize(9, 1)); + m_add.setBackgroundColor(FColor::DarkGreen); + m_add.setFocusBackgroundColor(FColor::DarkGreen); + m_add.addCallback("clicked", [&]() { addEntry(); }); + + m_delete.setGeometry(FPoint(13, int(getHeight() - 4)), FSize(10, 1)); + m_delete.setBackgroundColor(FColor::DarkRed); + m_delete.setFocusBackgroundColor(FColor::DarkRed); + m_delete.addCallback("clicked", [&]() { deleteEntry(); }); + + m_entryLabel.setGeometry(FPoint(2, int(getHeight() - 6)), FSize(10, 1)); + m_entry.setGeometry(FPoint(12, int(getHeight() - 6)), FSize(11, 1)); + m_entry.setShadow(false); + m_entry.addCallback("up-pressed", [&]() { + uint32_t tgId = ::atoi(m_entry.getText().c_str()); + tgId++; + if (tgId > 16777217U) { + tgId = 16777217U; + } + + m_entry.setText(std::to_string(tgId)); + redraw(); + }); + m_entry.addCallback("down-pressed", [&]() { + uint32_t tgId = ::atoi(m_entry.getText().c_str()); + tgId--; + if (tgId < 1U) { + tgId = 1U; + } + + m_entry.setText(std::to_string(tgId)); + redraw(); + }); + m_entry.addCallback("insert-pressed", [&]() { addEntry(); }); + m_entry.addCallback("return-pressed", [&]() { + size_t curItem = m_listBox.currentItem(); + auto item = m_listBox.getItem(curItem); + LogMessage(LOG_HOST, "Updating %s radio ID %s to %s for TG %s (%u)", m_title.c_str(), item.getText().c_str(), m_entry.getText().c_str(), + m_rule.name().c_str(), m_rule.source().tgId()); + item.setText(m_entry.getText()); + + m_listBox.remove(curItem); + m_listBox.insert(item); + + //setFocusWidget(&m_listBox); + redraw(); + }); + + m_listBox.setGeometry(FPoint{1, 1}, FSize{getWidth() - 1, getHeight() - 7}); + m_listBox.setMultiSelection(false); + m_listBox.addCallback("row-selected", [&]() { + size_t curItem = m_listBox.currentItem(); + auto item = m_listBox.getItem(curItem); + m_entry.setText(item.getText().c_str()); + + setFocusWidget(&m_listBox); + redraw(); + }); + + CloseWndBase::initControls(); + + setFocusWidget(&m_listBox); + redraw(); + } + + /** + * @brief Populates the radio list. + */ + void loadList() + { + m_listBox.clear(); + for (auto entry : ridList) { + m_listBox.insert(std::to_string(entry)); + } + + redraw(); + } + + /** + * @brief + */ + void addEntry() + { + if (m_entry.getText() == "") { + m_listBox.insert(std::to_string(0U)); + } else { + m_listBox.insert(m_entry.getText()); + } + + //setFocusWidget(&m_listBox); + redraw(); + } + + /** + * @brief + */ + void deleteEntry() + { + m_entry.setText(""); + + size_t curItem = m_listBox.currentItem(); + auto item = m_listBox.getItem(curItem); + LogMessage(LOG_HOST, "Removing %s radio ID %s from TG %s (%u)", m_title.c_str(), item.getText().c_str(), + m_rule.name().c_str(), m_rule.source().tgId()); + m_listBox.remove(curItem); + + //setFocusWidget(&m_listBox); + redraw(); + } + + /* + ** Event Handlers + */ + + /** + * @brief Event that occurs on keyboard key press. + * @param e Keyboard Event. + */ + void onKeyPress(finalcut::FKeyEvent* e) override + { + const auto key = e->key(); + if (key == FKey::Insert) { + addEntry(); + redraw(); + } else if (key == FKey::Del_char) { + deleteEntry(); + redraw(); + } else if (key == FKey::Enter || key == FKey::Return) { + this->close(); + } else if (key == FKey::Escape) { + m_skipSaving = true; + this->close(); + } + } + + /** + * @brief Event that occurs when the window is closed. + * @param e Close event. + */ + void onClose(FCloseEvent* e) override + { + if (m_skipSaving) { + m_skipSaving = false; + CloseWndBase::onClose(e); + return; + } + + ridList.clear(); + for (uint32_t i = 0U; i < m_listBox.getCount(); i++) { + auto item = m_listBox.getItem(i + 1U); + if (item.getText() != "") { + uint32_t peerId = ::atoi(item.getText().c_str()); + LogMessage(LOG_HOST, "%s radio ID %s for TG %s (%u)", m_title.c_str(), item.getText().c_str(), + m_rule.name().c_str(), m_rule.source().tgId()); + ridList.push_back(peerId); + } + } + + CloseWndBase::onClose(e); + } +}; + +#endif // __TG_EDIT_RID_LIST_WND_H__ \ No newline at end of file diff --git a/src/tged/TGEditWnd.h b/src/tged/TGEditWnd.h index cafb3a3c..398c60ef 100644 --- a/src/tged/TGEditWnd.h +++ b/src/tged/TGEditWnd.h @@ -19,6 +19,7 @@ #include "tged/CloseWndBase.h" #include "tged/TGEdMain.h" #include "tged/TGEditPeerListWnd.h" +#include "tged/TGEditRIDListWnd.h" #include using namespace finalcut; @@ -123,6 +124,7 @@ private: FButton m_preferredList{"&Preferred...", this}; FButton m_rewriteList{"&Rewrites...", this}; + FButton m_permittedRIDList{"&Permitted Radios...", this}; /** * @brief Initializes the window layout. @@ -341,6 +343,17 @@ private: // TODO }); + m_permittedRIDList.setGeometry(FPoint(20, 14), FSize(16, 1)); + m_permittedRIDList.addCallback("clicked", [&]() { + TGEditRIDListWnd wnd{m_rule, m_rule.config().permittedRIDs(), "Permitted Radios", this}; + wnd.show(); + + auto config = m_rule.config(); + config.permittedRIDs(wnd.ridList); + m_rule.config(config); + LogMessage(LOG_HOST, "Updated %s (%u) permitted radio list, %u permitted.", m_rule.name().c_str(), m_rule.source().tgId(), m_rule.config().permittedRIDsSize()); + }); + CloseWndBase::initControls(); } diff --git a/src/tged/TGListWnd.h b/src/tged/TGListWnd.h index 5dd2391c..623e37f2 100644 --- a/src/tged/TGListWnd.h +++ b/src/tged/TGListWnd.h @@ -100,13 +100,14 @@ public: oss << std::setw(5) << std::setfill('0') << entry.source().tgId(); // build list view entry - const std::array columns = { + const std::array columns = { entry.name(), entry.nameAlias(), oss.str(), (entry.config().active()) ? "X" : "", (entry.config().affiliated()) ? "X" : "", std::to_string(entry.config().inclusionSize()), std::to_string(entry.config().exclusionSize()), - std::to_string(entry.config().alwaysSendSize()) + std::to_string(entry.config().alwaysSendSize()), + std::to_string(entry.config().permittedRIDsSize()) }; const finalcut::FStringList line(columns.cbegin(), columns.cend()); @@ -182,6 +183,7 @@ private: m_listView.addColumn("Inclusions", 5); m_listView.addColumn("Exclusions", 5); m_listView.addColumn("Always", 5); + m_listView.addColumn("Permitted RIDs", 5); // set right alignment for TGID m_listView.setColumnAlignment(3, finalcut::Align::Right); @@ -190,6 +192,7 @@ private: m_listView.setColumnAlignment(6, finalcut::Align::Right); m_listView.setColumnAlignment(7, finalcut::Align::Right); m_listView.setColumnAlignment(8, finalcut::Align::Right); + m_listView.setColumnAlignment(9, finalcut::Align::Right); // set type of sorting m_listView.setColumnSortType(1, finalcut::SortType::Name);