adjust color palette for TUI; add alpha version for the TUI talkgroup rules file editor;

pull/72/head
Bryan Biedenkapp 1 year ago
parent a677f444d4
commit ae39ae816a

@ -250,9 +250,9 @@ endif (ENABLE_TESTS)
install(TARGETS dvmhost DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(TARGETS dvmhost DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
install(TARGETS dvmcmd DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(TARGETS dvmcmd DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
install(TARGETS dvmfne DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(TARGETS dvmfne DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR)) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
install(TARGETS dvmmon DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(TARGETS dvmmon DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR)) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
install(TARGETS dvmbridge DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(TARGETS dvmbridge DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
install(FILES configs/config.example.yml configs/fne-config.example.yml configs/monitor-config.example.yml configs/iden_table.dat configs/RSSI.dat configs/rid_acl.example.dat configs/talkgroup_rules.example.yml configs/bridge-config.example.yml DESTINATION ${CMAKE_INSTALL_PREFIX}/etc) install(FILES configs/config.example.yml configs/fne-config.example.yml configs/monitor-config.example.yml configs/iden_table.dat configs/RSSI.dat configs/rid_acl.example.dat configs/talkgroup_rules.example.yml configs/bridge-config.example.yml DESTINATION ${CMAKE_INSTALL_PREFIX}/etc)
install(PROGRAMS tools/start-dvm.sh tools/stop-dvm.sh tools/dvm-watchdog.sh tools/stop-watchdog.sh tools/fne-watchdog.sh tools/start-dvm-fne.sh DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) install(PROGRAMS tools/start-dvm.sh tools/stop-dvm.sh tools/dvm-watchdog.sh tools/stop-watchdog.sh tools/fne-watchdog.sh tools/start-dvm-fne.sh DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
@ -267,12 +267,13 @@ install(CODE "execute_process(COMMAND bash \"-c\" \"sed -i 's/file: talkgroup_ru
# #
if (NOT TARGET strip) if (NOT TARGET strip)
if (CROSS_COMPILE_ARM) if (CROSS_COMPILE_ARM)
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR)) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
add_custom_target(strip add_custom_target(strip
COMMAND arm-linux-gnueabihf-strip -s dvmhost COMMAND arm-linux-gnueabihf-strip -s dvmhost
COMMAND arm-linux-gnueabihf-strip -s dvmfne COMMAND arm-linux-gnueabihf-strip -s dvmfne
COMMAND arm-linux-gnueabihf-strip -s dvmcmd COMMAND arm-linux-gnueabihf-strip -s dvmcmd
COMMAND arm-linux-gnueabihf-strip -s dvmmon COMMAND arm-linux-gnueabihf-strip -s dvmmon
COMMAND arm-linux-gnueabihf-strip -s tged
COMMAND arm-linux-gnueabihf-strip -s dvmbridge) COMMAND arm-linux-gnueabihf-strip -s dvmbridge)
else() else()
add_custom_target(strip add_custom_target(strip
@ -280,14 +281,15 @@ if (NOT TARGET strip)
COMMAND arm-linux-gnueabihf-strip -s dvmfne COMMAND arm-linux-gnueabihf-strip -s dvmfne
COMMAND arm-linux-gnueabihf-strip -s dvmcmd COMMAND arm-linux-gnueabihf-strip -s dvmcmd
COMMAND arm-linux-gnueabihf-strip -s dvmbridge) COMMAND arm-linux-gnueabihf-strip -s dvmbridge)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR)) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
elseif (CROSS_COMPILE_AARCH64) elseif (CROSS_COMPILE_AARCH64)
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR)) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
add_custom_target(strip add_custom_target(strip
COMMAND aarch64-linux-gnu-strip -s dvmhost COMMAND aarch64-linux-gnu-strip -s dvmhost
COMMAND aarch64-linux-gnu-strip -s dvmfne COMMAND aarch64-linux-gnu-strip -s dvmfne
COMMAND aarch64-linux-gnu-strip -s dvmcmd COMMAND aarch64-linux-gnu-strip -s dvmcmd
COMMAND aarch64-linux-gnu-strip -s dvmmon COMMAND aarch64-linux-gnu-strip -s dvmmon
COMMAND aarch64-linux-gnu-strip -s tged
COMMAND aarch64-linux-gnu-strip -s dvmbridge) COMMAND aarch64-linux-gnu-strip -s dvmbridge)
else() else()
add_custom_target(strip add_custom_target(strip
@ -295,7 +297,7 @@ if (NOT TARGET strip)
COMMAND aarch64-linux-gnu-strip -s dvmfne COMMAND aarch64-linux-gnu-strip -s dvmfne
COMMAND aarch64-linux-gnu-strip -s dvmcmd COMMAND aarch64-linux-gnu-strip -s dvmcmd
COMMAND aarch64-linux-gnu-strip -s dvmbridge) COMMAND aarch64-linux-gnu-strip -s dvmbridge)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR)) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
elseif (CROSS_COMPILE_RPI_ARM) elseif (CROSS_COMPILE_RPI_ARM)
if (NOT WITH_RPI_ARM_TOOLS) if (NOT WITH_RPI_ARM_TOOLS)
add_custom_target(strip add_custom_target(strip
@ -311,12 +313,13 @@ if (NOT TARGET strip)
COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmbridge) COMMAND ${RPI_ARM_TOOLS}/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip -s dvmbridge)
endif () endif ()
else() else()
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR)) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
add_custom_target(strip add_custom_target(strip
COMMAND strip -s dvmhost COMMAND strip -s dvmhost
COMMAND strip -s dvmfne COMMAND strip -s dvmfne
COMMAND strip -s dvmcmd COMMAND strip -s dvmcmd
COMMAND strip -s dvmmon COMMAND strip -s dvmmon
COMMAND strip -s tged
COMMAND strip -s dvmbridge) COMMAND strip -s dvmbridge)
else() else()
add_custom_target(strip add_custom_target(strip
@ -324,7 +327,7 @@ if (NOT TARGET strip)
COMMAND strip -s dvmfne COMMAND strip -s dvmfne
COMMAND strip -s dvmcmd COMMAND strip -s dvmcmd
COMMAND strip -s dvmbridge) COMMAND strip -s dvmbridge)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR)) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
endif (CROSS_COMPILE_ARM) endif (CROSS_COMPILE_ARM)
endif (NOT TARGET strip) endif (NOT TARGET strip)
@ -339,7 +342,7 @@ endif (NOT TARGET strip)
# #
if (NOT TARGET tarball) if (NOT TARGET tarball)
set(CMAKE_INSTALL_PREFIX_TARBALL "tar_build") set(CMAKE_INSTALL_PREFIX_TARBALL "tar_build")
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR)) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
add_custom_target(tarball add_custom_target(tarball
COMMAND rm -rf ${CMAKE_INSTALL_PREFIX_TARBALL} COMMAND rm -rf ${CMAKE_INSTALL_PREFIX_TARBALL}
COMMAND mkdir -p ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND mkdir -p ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
@ -348,6 +351,7 @@ if (NOT TARGET tarball)
COMMAND cp -v dvmhost ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v dvmhost ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmcmd ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v dvmcmd ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmmon ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v dvmmon ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v tged ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmfne ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v dvmfne ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp -v dvmbridge ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin COMMAND cp -v dvmbridge ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/bin
COMMAND cp ../tools/*.sh ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm COMMAND cp ../tools/*.sh ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm
@ -406,7 +410,7 @@ if (NOT TARGET tarball)
COMMAND if [ -e DVM-V24-stm32f103.bin ]\; then cp -v DVM-V24-stm32f103.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi COMMAND if [ -e DVM-V24-stm32f103.bin ]\; then cp -v DVM-V24-stm32f103.bin ${CMAKE_INSTALL_PREFIX_TARBALL}/dvm/fw\; fi
COMMAND cd ${CMAKE_INSTALL_PREFIX_TARBALL} && tar czvf ../dvmhost_${CPACK_DEBIAN_PACKAGE_VERSION}_${ARCH}.tar.gz * COMMAND cd ${CMAKE_INSTALL_PREFIX_TARBALL} && tar czvf ../dvmhost_${CPACK_DEBIAN_PACKAGE_VERSION}_${ARCH}.tar.gz *
COMMAND rm -rf ${CMAKE_INSTALL_PREFIX_TARBALL}) COMMAND rm -rf ${CMAKE_INSTALL_PREFIX_TARBALL})
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR)) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
endif (NOT TARGET tarball) endif (NOT TARGET tarball)
# #
@ -425,6 +429,7 @@ add_custom_target(old_install
COMMAND install -m 755 dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/bin COMMAND install -m 755 dvmhost ${CMAKE_LEGACY_INSTALL_PREFIX}/bin
COMMAND install -m 755 dvmcmd ${CMAKE_LEGACY_INSTALL_PREFIX}/bin COMMAND install -m 755 dvmcmd ${CMAKE_LEGACY_INSTALL_PREFIX}/bin
COMMAND install -m 755 dvmmon ${CMAKE_LEGACY_INSTALL_PREFIX}/bin COMMAND install -m 755 dvmmon ${CMAKE_LEGACY_INSTALL_PREFIX}/bin
COMMAND install -m 755 tged ${CMAKE_LEGACY_INSTALL_PREFIX}/bin
COMMAND install -m 755 dvmfne ${CMAKE_LEGACY_INSTALL_PREFIX}/bin COMMAND install -m 755 dvmfne ${CMAKE_LEGACY_INSTALL_PREFIX}/bin
COMMAND install -m 755 dvmbridge ${CMAKE_LEGACY_INSTALL_PREFIX}/bin COMMAND install -m 755 dvmbridge ${CMAKE_LEGACY_INSTALL_PREFIX}/bin
COMMAND install -m 644 ../configs/config.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/config.example.yml COMMAND install -m 644 ../configs/config.example.yml ${CMAKE_LEGACY_INSTALL_PREFIX}/config.example.yml

@ -82,12 +82,22 @@ target_include_directories(dvmfne PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host sr
# #
## dvmmon ## dvmmon
# #
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR)) if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
include(src/monitor/CMakeLists.txt) include(src/monitor/CMakeLists.txt)
add_executable(dvmmon ${common_INCLUDE} ${dvmmon_SRC}) add_executable(dvmmon ${common_INCLUDE} ${dvmmon_SRC})
target_link_libraries(dvmmon PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads) target_link_libraries(dvmmon PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads)
target_include_directories(dvmmon PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host src/monitor) target_include_directories(dvmmon PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host src/monitor)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_MONITOR)) endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
#
## tged
#
if (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
include(src/tged/CMakeLists.txt)
add_executable(tged ${common_INCLUDE} ${tged_SRC})
target_link_libraries(tged PRIVATE common ${OPENSSL_LIBRARIES} asio::asio finalcut Threads::Threads)
target_include_directories(tged PRIVATE ${OPENSSL_INCLUDE_DIR} src src/host src/tged)
endif (ENABLE_TUI_SUPPORT AND (NOT DISABLE_TUI_APPS))
# #
## dvmcmd ## dvmcmd

@ -21,10 +21,10 @@ if (ENABLE_TCP_SSL)
add_definitions(-DENABLE_TCP_SSL) add_definitions(-DENABLE_TCP_SSL)
endif (ENABLE_TCP_SSL) endif (ENABLE_TCP_SSL)
option(DISABLE_MONITOR "Disable dvmmon compilation" off) option(DISABLE_TUI_APPS "Disable extra TUI applications" off)
if (DISABLE_MONITOR) if (DISABLE_TUI_APPS)
message(CHECK_START "Disable dvmmon compilation - enabled") message(CHECK_START "Disable extra TUI applications - enabled")
endif (DISABLE_MONITOR) endif (DISABLE_TUI_APPS)
# Debug compilation features/options (these should not be enabled for production!) # Debug compilation features/options (these should not be enabled for production!)
option(DEBUG_DMR_PDU_DATA "" off) option(DEBUG_DMR_PDU_DATA "" off)

@ -113,7 +113,7 @@ protected:
m_connectedLabel.setGeometry(FPoint(36, int(getHeight()) - 3), FSize(20, 3)); m_connectedLabel.setGeometry(FPoint(36, int(getHeight()) - 3), FSize(20, 3));
if (m_setup->m_isConnected) { if (m_setup->m_isConnected) {
m_connectedLabel.setText("Modem Connected"); m_connectedLabel.setText("Modem Connected");
m_connectedLabel.setForegroundColor(FColor::Green3); m_connectedLabel.setForegroundColor(FColor::DarkGreen);
} }
else { else {
m_connectedLabel.setText("Modem Disconnected"); m_connectedLabel.setText("Modem Disconnected");

@ -247,7 +247,7 @@ int HostSetup::run(int argc, char** argv)
// show and start the application // show and start the application
setupWnd.show(); setupWnd.show();
finalcut::FApplication::setDarkTheme(); finalcut::FApplication::setColorTheme<dvmColorTheme>();
app.resetColors(); app.resetColors();
app.redraw(); app.redraw();
return app.exec(); return app.exec();

@ -25,6 +25,144 @@ using namespace finalcut;
// Class Declaration // Class Declaration
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/**
* @brief This class implements a color theme for a finalcut application.
* @ingroup setup
*/
class HOST_SW_API dvmColorTheme final : public FWidgetColors
{
public:
/**
* @brief Initializes a new instance of the dvmColorTheme class.
*/
dvmColorTheme()
{
dvmColorTheme::setColorTheme();
}
/**
* @brief Finalizes a instance of the dvmColorTheme class.
*/
~dvmColorTheme() noexcept override = default;
/**
* @brief Get the Class Name object
* @return FString
*/
auto getClassName() const -> FString override { return "dvmColorTheme"; }
/**
* @brief Set the Color Theme object
*/
void setColorTheme() override
{
term_fg = FColor::Cyan;
term_bg = FColor::Blue;
list_fg = FColor::Black;
list_bg = FColor::LightGray;
selected_list_fg = FColor::Red;
selected_list_bg = FColor::LightGray;
dialog_fg = FColor::Black;
dialog_resize_fg = FColor::LightBlue;
dialog_emphasis_fg = FColor::Blue;
dialog_bg = FColor::LightGray;
error_box_fg = FColor::LightRed;
error_box_emphasis_fg = FColor::Yellow;
error_box_bg = FColor::Black;
tooltip_fg = FColor::White;
tooltip_bg = FColor::Black;
shadow_fg = FColor::Black;
shadow_bg = FColor::LightGray; // only for transparent shadow
current_element_focus_fg = FColor::White;
current_element_focus_bg = FColor::Cyan;
current_element_fg = FColor::LightBlue;
current_element_bg = FColor::Cyan;
current_inc_search_element_fg = FColor::LightRed;
selected_current_element_focus_fg = FColor::LightRed;
selected_current_element_focus_bg = FColor::Cyan;
selected_current_element_fg = FColor::Red;
selected_current_element_bg = FColor::Cyan;
label_fg = FColor::Black;
label_bg = FColor::LightGray;
label_inactive_fg = FColor::DarkGray;
label_inactive_bg = FColor::LightGray;
label_hotkey_fg = FColor::Red;
label_hotkey_bg = FColor::LightGray;
label_emphasis_fg = FColor::Blue;
label_ellipsis_fg = FColor::DarkGray;
inputfield_active_focus_fg = FColor::Yellow;
inputfield_active_focus_bg = FColor::Blue;
inputfield_active_fg = FColor::LightGray;
inputfield_active_bg = FColor::Blue;
inputfield_inactive_fg = FColor::Black;
inputfield_inactive_bg = FColor::DarkGray;
toggle_button_active_focus_fg = FColor::Yellow;
toggle_button_active_focus_bg = FColor::Blue;
toggle_button_active_fg = FColor::LightGray;
toggle_button_active_bg = FColor::Blue;
toggle_button_inactive_fg = FColor::Black;
toggle_button_inactive_bg = FColor::DarkGray;
button_active_focus_fg = FColor::Yellow;
button_active_focus_bg = FColor::Blue;
button_active_fg = FColor::White;
button_active_bg = FColor::Blue;
button_inactive_fg = FColor::Black;
button_inactive_bg = FColor::DarkGray;
button_hotkey_fg = FColor::Yellow;
titlebar_active_fg = FColor::Blue;
titlebar_active_bg = FColor::White;
titlebar_inactive_fg = FColor::Blue;
titlebar_inactive_bg = FColor::LightGray;
titlebar_button_fg = FColor::Yellow;
titlebar_button_bg = FColor::LightBlue;
titlebar_button_focus_fg = FColor::LightGray;
titlebar_button_focus_bg = FColor::Black;
menu_active_focus_fg = FColor::Black;
menu_active_focus_bg = FColor::White;
menu_active_fg = FColor::Black;
menu_active_bg = FColor::LightGray;
menu_inactive_fg = FColor::DarkGray;
menu_inactive_bg = FColor::LightGray;
menu_hotkey_fg = FColor::Blue;
menu_hotkey_bg = FColor::LightGray;
statusbar_fg = FColor::Black;
statusbar_bg = FColor::LightGray;
statusbar_hotkey_fg = FColor::Blue;
statusbar_hotkey_bg = FColor::LightGray;
statusbar_separator_fg = FColor::Black;
statusbar_active_fg = FColor::Black;
statusbar_active_bg = FColor::White;
statusbar_active_hotkey_fg = FColor::Blue;
statusbar_active_hotkey_bg = FColor::White;
scrollbar_fg = FColor::Cyan;
scrollbar_bg = FColor::DarkGray;
scrollbar_button_fg = FColor::Yellow;
scrollbar_button_bg = FColor::DarkGray;
scrollbar_button_inactive_fg = FColor::LightGray;
scrollbar_button_inactive_bg = FColor::Black;
progressbar_fg = FColor::Yellow;
progressbar_bg = FColor::Blue;
}
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/** /**
* @brief This class implements the finalcut application. * @brief This class implements the finalcut application.
* @ingroup setup * @ingroup setup

@ -25,6 +25,144 @@ using namespace finalcut;
// Class Declaration // Class Declaration
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/**
* @brief This class implements a color theme for a finalcut application.
* @ingroup setup
*/
class HOST_SW_API dvmColorTheme final : public FWidgetColors
{
public:
/**
* @brief Initializes a new instance of the dvmColorTheme class.
*/
dvmColorTheme()
{
dvmColorTheme::setColorTheme();
}
/**
* @brief Finalizes a instance of the dvmColorTheme class.
*/
~dvmColorTheme() noexcept override = default;
/**
* @brief Get the Class Name object
* @return FString
*/
auto getClassName() const -> FString override { return "dvmColorTheme"; }
/**
* @brief Set the Color Theme object
*/
void setColorTheme() override
{
term_fg = FColor::Cyan;
term_bg = FColor::Blue;
list_fg = FColor::Black;
list_bg = FColor::LightGray;
selected_list_fg = FColor::Red;
selected_list_bg = FColor::LightGray;
dialog_fg = FColor::Black;
dialog_resize_fg = FColor::LightBlue;
dialog_emphasis_fg = FColor::Blue;
dialog_bg = FColor::LightGray;
error_box_fg = FColor::LightRed;
error_box_emphasis_fg = FColor::Yellow;
error_box_bg = FColor::Black;
tooltip_fg = FColor::White;
tooltip_bg = FColor::Black;
shadow_fg = FColor::Black;
shadow_bg = FColor::LightGray; // only for transparent shadow
current_element_focus_fg = FColor::White;
current_element_focus_bg = FColor::Cyan;
current_element_fg = FColor::LightBlue;
current_element_bg = FColor::Cyan;
current_inc_search_element_fg = FColor::LightRed;
selected_current_element_focus_fg = FColor::LightRed;
selected_current_element_focus_bg = FColor::Cyan;
selected_current_element_fg = FColor::Red;
selected_current_element_bg = FColor::Cyan;
label_fg = FColor::Black;
label_bg = FColor::LightGray;
label_inactive_fg = FColor::DarkGray;
label_inactive_bg = FColor::LightGray;
label_hotkey_fg = FColor::Red;
label_hotkey_bg = FColor::LightGray;
label_emphasis_fg = FColor::Blue;
label_ellipsis_fg = FColor::DarkGray;
inputfield_active_focus_fg = FColor::Yellow;
inputfield_active_focus_bg = FColor::Blue;
inputfield_active_fg = FColor::LightGray;
inputfield_active_bg = FColor::Blue;
inputfield_inactive_fg = FColor::Black;
inputfield_inactive_bg = FColor::DarkGray;
toggle_button_active_focus_fg = FColor::Yellow;
toggle_button_active_focus_bg = FColor::Blue;
toggle_button_active_fg = FColor::LightGray;
toggle_button_active_bg = FColor::Blue;
toggle_button_inactive_fg = FColor::Black;
toggle_button_inactive_bg = FColor::DarkGray;
button_active_focus_fg = FColor::Yellow;
button_active_focus_bg = FColor::Blue;
button_active_fg = FColor::White;
button_active_bg = FColor::Blue;
button_inactive_fg = FColor::Black;
button_inactive_bg = FColor::DarkGray;
button_hotkey_fg = FColor::Yellow;
titlebar_active_fg = FColor::Blue;
titlebar_active_bg = FColor::White;
titlebar_inactive_fg = FColor::Blue;
titlebar_inactive_bg = FColor::LightGray;
titlebar_button_fg = FColor::Yellow;
titlebar_button_bg = FColor::LightBlue;
titlebar_button_focus_fg = FColor::LightGray;
titlebar_button_focus_bg = FColor::Black;
menu_active_focus_fg = FColor::Black;
menu_active_focus_bg = FColor::White;
menu_active_fg = FColor::Black;
menu_active_bg = FColor::LightGray;
menu_inactive_fg = FColor::DarkGray;
menu_inactive_bg = FColor::LightGray;
menu_hotkey_fg = FColor::Blue;
menu_hotkey_bg = FColor::LightGray;
statusbar_fg = FColor::Black;
statusbar_bg = FColor::LightGray;
statusbar_hotkey_fg = FColor::Blue;
statusbar_hotkey_bg = FColor::LightGray;
statusbar_separator_fg = FColor::Black;
statusbar_active_fg = FColor::Black;
statusbar_active_bg = FColor::White;
statusbar_active_hotkey_fg = FColor::Blue;
statusbar_active_hotkey_bg = FColor::White;
scrollbar_fg = FColor::Cyan;
scrollbar_bg = FColor::DarkGray;
scrollbar_button_fg = FColor::Yellow;
scrollbar_button_bg = FColor::DarkGray;
scrollbar_button_inactive_fg = FColor::LightGray;
scrollbar_button_inactive_bg = FColor::Black;
progressbar_fg = FColor::Yellow;
progressbar_bg = FColor::Blue;
}
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/** /**
* @brief This class implements the finalcut application. * @brief This class implements the finalcut application.
* @ingroup monitor * @ingroup monitor

@ -234,7 +234,7 @@ int main(int argc, char** argv)
// show and start the application // show and start the application
wnd.show(); wnd.show();
finalcut::FApplication::setDarkTheme(); finalcut::FApplication::setColorTheme<dvmColorTheme>();
app.resetColors(); app.resetColors();
app.redraw(); app.redraw();

@ -0,0 +1,13 @@
# 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(GLOB tged_SRC
"src/tged/*.h"
"src/tged/*.cpp"
)

@ -0,0 +1,128 @@
// 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) 2023 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file CloseWndBase.h
* @ingroup tged
*/
#if !defined(__CLOSE_WND_BASE_H__)
#define __CLOSE_WND_BASE_H__
#include "common/Thread.h"
#include <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements the base class for windows with close buttons.
* @ingroup tged
*/
class HOST_SW_API CloseWndBase : public finalcut::FDialog {
public:
/**
* @brief Initializes a new instance of the CloseWndBase class.
* @param widget
*/
explicit CloseWndBase(FWidget* widget = nullptr) : FDialog{widget}
{
/* stub */
}
protected:
bool m_enableSetButton = false;
FButton m_setButton{"Set", this};
bool m_enableCloseButton = true;
FButton m_closeButton{"&Close", this};
/**
* @brief Initializes the window layout.
*/
void initLayout() override
{
FDialog::setMinimizable(true);
FDialog::setShadow();
std::size_t maxWidth, maxHeight;
const auto& rootWidget = getRootWidget();
if (rootWidget) {
maxWidth = rootWidget->getClientWidth();
maxHeight = rootWidget->getClientHeight();
}
else {
// fallback to xterm default size
maxWidth = 80;
maxHeight = 24;
}
const int x = 1 + int((maxWidth - getWidth()) / 2);
const int y = 1 + int((maxHeight - getHeight()) / 3);
FWindow::setPos(FPoint{x, y}, false);
FDialog::adjustSize();
FDialog::setModal();
initControls();
FDialog::initLayout();
rootWidget->redraw(); // bryanb: wtf?
redraw();
}
/**
* @brief Initializes window controls.
*/
virtual void initControls()
{
m_closeButton.setGeometry(FPoint(int(getWidth()) - 12, int(getHeight()) - 6), FSize(9, 3));
m_closeButton.addCallback("clicked", [&]() { close(); });
if (!m_enableCloseButton) {
m_closeButton.setDisable();
m_closeButton.setVisible(false);
}
m_setButton.setDisable();
m_setButton.setVisible(false);
if (m_enableSetButton) {
m_setButton.setEnable();
m_setButton.setVisible(true);
m_setButton.setGeometry(FPoint(int(getWidth()) - 24, int(getHeight()) - 6), FSize(9, 3));
}
focusFirstChild();
}
/**
* @brief Adjusts window size.
*/
void adjustSize() override
{
FDialog::adjustSize();
}
/*
** Event Handlers
*/
/**
* @brief Event that occurs when the window is closed.
* @param e Close event.
*/
void onClose(FCloseEvent* e) override
{
hide();
}
};
#endif // __CLOSE_WND_BASE_H__

@ -0,0 +1,33 @@
// 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) 2023 Bryan Biedenkapp, N2PLL
*
*/
/**
* @defgroup tged Talkgroup Editor (tged)
* @brief Digital Voice Modem - Talkgroup Editor
* @details Helper software that edits talkgroup rules files with a graphical TUI.
* @ingroup tged
*
* @file Defines.h
* @ingroup tged
*/
#if !defined(__DEFINES_H__)
#define __DEFINES_H__
#include "common/Defines.h"
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#undef __PROG_NAME__
#define __PROG_NAME__ "Digital Voice Modem (DVM) Talkgroup Rules Editor"
#undef __EXE_NAME__
#define __EXE_NAME__ "tged"
#endif // __DEFINES_H__

@ -0,0 +1,144 @@
// 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) 2023 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file LogDisplayWnd.h
* @ingroup tged
*/
#if !defined(__LOG_DISPLAY_WND_H__)
#define __LOG_DISPLAY_WND_H__
#include <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements the log display window.
* @ingroup tged
*/
class HOST_SW_API LogDisplayWnd final : public finalcut::FDialog, public std::ostringstream {
public:
/**
* @brief Initializes a new instance of the LogDisplayWnd class.
* @param widget
*/
explicit LogDisplayWnd(FWidget* widget = nullptr) : FDialog{widget}
{
m_scrollText.ignorePadding();
m_timerId = addTimer(250); // starts the timer every 250 milliseconds
}
/**
* @brief Copy constructor.
*/
LogDisplayWnd(const LogDisplayWnd&) = delete;
/**
* @brief Move constructor.
*/
LogDisplayWnd(LogDisplayWnd&&) noexcept = delete;
/**
* @brief Finalizes an instance of the LogDisplayWnd class.
*/
~LogDisplayWnd() noexcept override = default;
/**
* @brief Disable copy assignment operator (=).
*/
auto operator= (const LogDisplayWnd&) -> LogDisplayWnd& = delete;
/**
* @brief Disable move assignment operator (=).
*/
auto operator= (LogDisplayWnd&&) noexcept -> LogDisplayWnd& = delete;
private:
FTextView m_scrollText{this};
int m_timerId;
/**
* @brief Initializes the window layout.
*/
void initLayout() override
{
using namespace std::string_literals;
auto lightning = "\u26a1";
FDialog::setText("System Log"s + lightning);
std::size_t maxWidth;
const auto& rootWidget = getRootWidget();
if (rootWidget) {
maxWidth = rootWidget->getClientWidth() - 3;
}
else {
// fallback to xterm default size
maxWidth = 77;
}
FDialog::setGeometry(FPoint{2, (int)(rootWidget->getClientHeight() - 20)}, FSize{maxWidth, 20});
FDialog::setMinimumSize(FSize{80, 20});
FDialog::setResizeable(true);
FDialog::setMinimizable(true);
FDialog::setTitlebarButtonVisibility(true);
FDialog::setShadow();
minimizeWindow();
m_scrollText.setGeometry(FPoint{1, 2}, FSize{getWidth(), getHeight() - 1});
FDialog::initLayout();
}
/**
* @brief Adjusts window size.
*/
void adjustSize() override
{
FDialog::adjustSize();
m_scrollText.setGeometry(FPoint{1, 2}, FSize{getWidth(), getHeight() - 1});
}
/*
** Event Handlers
*/
/**
* @brief Event that occurs when the window is closed.
* @param e Close Event
*/
void onClose(FCloseEvent* e) override
{
minimizeWindow();
}
/**
* @brief Event that occurs on interval by timer.
* @param timer Timer Event
*/
void onTimer(FTimerEvent* timer) override
{
if (timer != nullptr) {
if (timer->getTimerId() == m_timerId) {
if (str().empty()) {
return;
}
m_scrollText.append(str());
str("");
m_scrollText.scrollToEnd();
redraw();
}
}
}
};
#endif // __LOG_DISPLAY_WND_H__

@ -0,0 +1,214 @@
// 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) 2023 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file TGEdApplication.h
* @ingroup tged
*/
#if !defined(__TGED_APPLICATION_H__)
#define __TGED_APPLICATION_H__
#include "common/Log.h"
#include "TGEdMain.h"
#include "TGEdMainWnd.h"
#include <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements a color theme for a finalcut application.
* @ingroup setup
*/
class HOST_SW_API dvmColorTheme final : public FWidgetColors
{
public:
/**
* @brief Initializes a new instance of the dvmColorTheme class.
*/
dvmColorTheme()
{
dvmColorTheme::setColorTheme();
}
/**
* @brief Finalizes a instance of the dvmColorTheme class.
*/
~dvmColorTheme() noexcept override = default;
/**
* @brief Get the Class Name object
* @return FString
*/
auto getClassName() const -> FString override { return "dvmColorTheme"; }
/**
* @brief Set the Color Theme object
*/
void setColorTheme() override
{
term_fg = FColor::Cyan;
term_bg = FColor::Blue;
list_fg = FColor::Black;
list_bg = FColor::LightGray;
selected_list_fg = FColor::Red;
selected_list_bg = FColor::LightGray;
dialog_fg = FColor::Black;
dialog_resize_fg = FColor::LightBlue;
dialog_emphasis_fg = FColor::Blue;
dialog_bg = FColor::LightGray;
error_box_fg = FColor::LightRed;
error_box_emphasis_fg = FColor::Yellow;
error_box_bg = FColor::Black;
tooltip_fg = FColor::White;
tooltip_bg = FColor::Black;
shadow_fg = FColor::Black;
shadow_bg = FColor::LightGray; // only for transparent shadow
current_element_focus_fg = FColor::White;
current_element_focus_bg = FColor::Blue;
current_element_fg = FColor::LightGray;
current_element_bg = FColor::DarkGray;
current_inc_search_element_fg = FColor::LightRed;
selected_current_element_focus_fg = FColor::LightRed;
selected_current_element_focus_bg = FColor::Cyan;
selected_current_element_fg = FColor::Red;
selected_current_element_bg = FColor::Cyan;
label_fg = FColor::Black;
label_bg = FColor::LightGray;
label_inactive_fg = FColor::DarkGray;
label_inactive_bg = FColor::LightGray;
label_hotkey_fg = FColor::Red;
label_hotkey_bg = FColor::LightGray;
label_emphasis_fg = FColor::Blue;
label_ellipsis_fg = FColor::DarkGray;
inputfield_active_focus_fg = FColor::Yellow;
inputfield_active_focus_bg = FColor::Blue;
inputfield_active_fg = FColor::LightGray;
inputfield_active_bg = FColor::Blue;
inputfield_inactive_fg = FColor::Black;
inputfield_inactive_bg = FColor::DarkGray;
toggle_button_active_focus_fg = FColor::Yellow;
toggle_button_active_focus_bg = FColor::Blue;
toggle_button_active_fg = FColor::LightGray;
toggle_button_active_bg = FColor::Blue;
toggle_button_inactive_fg = FColor::Black;
toggle_button_inactive_bg = FColor::DarkGray;
button_active_focus_fg = FColor::Yellow;
button_active_focus_bg = FColor::Blue;
button_active_fg = FColor::White;
button_active_bg = FColor::Blue;
button_inactive_fg = FColor::Black;
button_inactive_bg = FColor::DarkGray;
button_hotkey_fg = FColor::Yellow;
titlebar_active_fg = FColor::Blue;
titlebar_active_bg = FColor::White;
titlebar_inactive_fg = FColor::Blue;
titlebar_inactive_bg = FColor::LightGray;
titlebar_button_fg = FColor::Yellow;
titlebar_button_bg = FColor::LightBlue;
titlebar_button_focus_fg = FColor::LightGray;
titlebar_button_focus_bg = FColor::Black;
menu_active_focus_fg = FColor::Black;
menu_active_focus_bg = FColor::White;
menu_active_fg = FColor::Black;
menu_active_bg = FColor::LightGray;
menu_inactive_fg = FColor::DarkGray;
menu_inactive_bg = FColor::LightGray;
menu_hotkey_fg = FColor::Blue;
menu_hotkey_bg = FColor::LightGray;
statusbar_fg = FColor::Black;
statusbar_bg = FColor::LightGray;
statusbar_hotkey_fg = FColor::Blue;
statusbar_hotkey_bg = FColor::LightGray;
statusbar_separator_fg = FColor::Black;
statusbar_active_fg = FColor::Black;
statusbar_active_bg = FColor::White;
statusbar_active_hotkey_fg = FColor::Blue;
statusbar_active_hotkey_bg = FColor::White;
scrollbar_fg = FColor::Cyan;
scrollbar_bg = FColor::DarkGray;
scrollbar_button_fg = FColor::Yellow;
scrollbar_button_bg = FColor::DarkGray;
scrollbar_button_inactive_fg = FColor::LightGray;
scrollbar_button_inactive_bg = FColor::Black;
progressbar_fg = FColor::Yellow;
progressbar_bg = FColor::Blue;
}
};
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements the finalcut application.
* @ingroup tged
*/
class HOST_SW_API TGEdApplication final : public finalcut::FApplication {
public:
/**
* @brief Initializes a new instance of the TGEdApplication class.
* @param argc Passed argc.
* @param argv Passed argv.
*/
explicit TGEdApplication(const int& argc, char** argv) : FApplication{argc, argv}
{
m_statusRefreshTimer = addTimer(1000);
}
protected:
/**
* @brief Process external user events.
*/
void processExternalUserEvent() override
{
/* stub */
}
/*
** Event Handlers
*/
/**
* @brief Event that occurs on interval by timer.
* @param timer Timer Event
*/
void onTimer(FTimerEvent* timer) override
{
if (timer != nullptr) {
if (timer->getTimerId() == m_statusRefreshTimer) {
/* stub */
}
}
}
private:
int m_statusRefreshTimer;
};
#endif // __TGED_APPLICATION_H__

@ -0,0 +1,224 @@
// 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) 2023 Bryan Biedenkapp, N2PLL
*
*/
#include "Defines.h"
#include "common/yaml/Yaml.h"
#include "common/Log.h"
#include "TGEdMain.h"
#include "TGEdApplication.h"
#include "TGEdMainWnd.h"
using namespace lookups;
#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <vector>
// ---------------------------------------------------------------------------
// Macros
// ---------------------------------------------------------------------------
#define IS(s) (::strcmp(argv[i], s) == 0)
// ---------------------------------------------------------------------------
// Global Variables
// ---------------------------------------------------------------------------
std::string g_progExe = std::string(__EXE_NAME__);
std::string g_iniFile = std::string(DEFAULT_CONF_FILE);
yaml::Node g_conf;
bool g_debug = false;
bool g_hideLoggingWnd = false;
lookups::TalkgroupRulesLookup* g_tidLookups = nullptr;
// ---------------------------------------------------------------------------
// Global Functions
// ---------------------------------------------------------------------------
/* Helper to print a fatal error message and exit. */
void fatal(const char* msg, ...)
{
char buffer[400U];
::memset(buffer, 0x20U, 400U);
va_list vl;
va_start(vl, msg);
::vsprintf(buffer, msg, vl);
va_end(vl);
::fprintf(stderr, "%s: FATAL PANIC; %s\n", g_progExe.c_str(), buffer);
exit(EXIT_FAILURE);
}
/* Helper to pring usage the command line arguments. (And optionally an error.) */
void usage(const char* message, const char* arg)
{
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n");
if (message != nullptr) {
::fprintf(stderr, "%s: ", g_progExe.c_str());
::fprintf(stderr, message, arg);
::fprintf(stderr, "\n\n");
}
::fprintf(stdout,
"usage: %s [-dvh]"
"[--hide-log]"
"[-c <talkgroup rules file>]"
"\n\n"
" -d enable debug\n"
" -v show version information\n"
" -h show this screen\n"
"\n"
" --hide-log hide interactive logging window on startup\n"
"\n"
" -c <file> specifies the talkgroup rules file to edit\n"
"\n"
" -- stop handling options\n",
g_progExe.c_str());
exit(EXIT_FAILURE);
}
/* Helper to validate the command line arguments. */
int checkArgs(int argc, char* argv[])
{
int i, p = 0;
// iterate through arguments
for (i = 1; i <= argc; i++)
{
if (argv[i] == nullptr) {
break;
}
if (*argv[i] != '-') {
continue;
}
else if (IS("--")) {
++p;
break;
}
else if (IS("-c")) {
if (argc-- <= 0)
usage("error: %s", "must specify the talkgroup rules file to edit");
g_iniFile = std::string(argv[++i]);
if (g_iniFile.empty())
usage("error: %s", "talkgroup rules file cannot be blank!");
p += 2;
}
else if (IS("--hide-log")) {
++p;
g_hideLoggingWnd = true;
}
else if (IS("-d")) {
++p;
g_debug = true;
}
else if (IS("-v")) {
::fprintf(stdout, __PROG_NAME__ " %s (built %s)\r\n", __VER__, __BUILD__);
::fprintf(stdout, "Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\n");
::fprintf(stdout, "Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\n\n");
if (argc == 2)
exit(EXIT_SUCCESS);
}
else if (IS("-h")) {
usage(nullptr, nullptr);
if (argc == 2)
exit(EXIT_SUCCESS);
}
else {
usage("unrecognized option `%s'", argv[i]);
}
}
if (p < 0 || p > argc) {
p = 0;
}
return ++p;
}
// ---------------------------------------------------------------------------
// Program Entry Point
// ---------------------------------------------------------------------------
int main(int argc, char** argv)
{
if (argv[0] != nullptr && *argv[0] != 0)
g_progExe = std::string(argv[0]);
if (argc > 1) {
// check arguments
int i = checkArgs(argc, argv);
if (i < argc) {
argc -= i;
argv += i;
}
else {
argc--;
argv++;
}
}
// initialize system logging
bool ret = ::LogInitialise("", "", 0U, 1U);
if (!ret) {
::fprintf(stderr, "unable to open the log file\n");
return 1;
}
::LogInfo(__PROG_NAME__ " " __VER__ " (built " __BUILD__ ")\r\n" \
"Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors.\r\n" \
"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others\r\n" \
">> Talkgroup Rules Editor\r\n");
try {
ret = yaml::Parse(g_conf, g_iniFile.c_str());
if (!ret) {
::fatal("cannot read the configuration file, %s\n", g_iniFile.c_str());
}
}
catch (yaml::OperationException const& e) {
::fatal("cannot read the configuration file - %s (%s)", g_iniFile.c_str(), e.message());
}
// setup the finalcut tui
TGEdApplication app{argc, argv};
TGEdMainWnd wnd{&app};
finalcut::FWidget::setMainWidget(&wnd);
g_logDisplayLevel = 0U;
g_tidLookups = new TalkgroupRulesLookup(g_iniFile, 0U, false);
g_tidLookups->read();
// show and start the application
wnd.show();
finalcut::FApplication::setColorTheme<dvmColorTheme>();
app.resetColors();
app.redraw();
int _errno = app.exec();
::LogFinalise();
return _errno;
}

@ -0,0 +1,63 @@
// 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) 2023 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file TGEdMain.h
* @ingroup tged
* @file TGEdMain.cpp
* @ingroup tged
*/
#if !defined(__TGED_MAIN_H__)
#define __TGED_MAIN_H__
#include "Defines.h"
#include "common/lookups/TalkgroupRulesLookup.h"
#include "common/yaml/Yaml.h"
#include <string>
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#undef __PROG_NAME__
#define __PROG_NAME__ "Digital Voice Modem (DVM) Talkgroup Rules Editor"
#undef __EXE_NAME__
#define __EXE_NAME__ "tged"
// ---------------------------------------------------------------------------
// Externs
// ---------------------------------------------------------------------------
/** @brief */
extern std::string g_progExe;
/** @brief */
extern std::string g_iniFile;
/** @brief */
extern yaml::Node g_conf;
/** @brief */
extern bool g_debug;
/** @brief */
extern bool g_hideLoggingWnd;
/** @brief */
extern lookups::TalkgroupRulesLookup* g_tidLookups;
/**
* @brief Helper to trigger a fatal error message. This will cause the program to terminate
* immediately with an error message.
*
* @param msg String format.
*
* This is a variable argument function.
*/
extern HOST_SW_API void fatal(const char* msg, ...);
#endif // __TGED_MAIN_H__

@ -0,0 +1,183 @@
// 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) 2023 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file TGEdainWnd.h
* @ingroup tged
*/
#if !defined(__TGED_MAIN_WND_H__)
#define __TGED_MAIN_WND_H__
#include "common/lookups/AffiliationLookup.h"
#include "common/Log.h"
#include "common/Thread.h"
using namespace lookups;
#include <final/final.h>
using namespace finalcut;
#undef null
#include "TGEdMain.h"
#include "LogDisplayWnd.h"
#include "TGListWnd.h"
#include "TGEditWnd.h"
#include <vector>
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define MINIMUM_SUPPORTED_SIZE_WIDTH 83
#define MINIMUM_SUPPORTED_SIZE_HEIGHT 30
// ---------------------------------------------------------------------------
// Class Prototypes
// ---------------------------------------------------------------------------
class HOST_SW_API TGEdApplication;
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements the root window control.
* @ingroup tged
*/
class HOST_SW_API TGEdMainWnd final : public finalcut::FWidget {
public:
/**
* @brief Initializes a new instance of the TGEdMainWnd class.
* @param widget
*/
explicit TGEdMainWnd(FWidget* widget = nullptr) : FWidget{widget}
{
__InternalOutputStream(m_logWnd);
// file menu
m_fileMenuSeparator1.setSeparator();
m_saveSettingsItem.addAccelerator(FKey::Meta_s); // Meta/Alt + S
m_saveSettingsItem.addCallback("clicked", this, [&]() { g_tidLookups->commit(); });
m_keyF2.addCallback("activate", this, [&]() { g_tidLookups->commit(); });
m_quitItem.addAccelerator(FKey::Meta_x); // Meta/Alt + X
m_quitItem.addCallback("clicked", getFApplication(), &FApplication::cb_exitApp, this);
m_keyF3.addCallback("activate", getFApplication(), &FApplication::cb_exitApp, this);
m_keyF5.addCallback("activate", this, [&]() { g_tidLookups->reload(); m_wnd->loadListView(); });
// help menu
m_aboutItem.addCallback("clicked", this, [&]() {
const FString line(2, UniChar::BoxDrawingsHorizontal);
FMessageBox info("About", line + __PROG_NAME__ + line + L"\n\n"
L"" + __BANNER__ + L"\n"
L"Version " + __VER__ + L"\n\n"
L"Copyright (c) 2017-2024 Bryan Biedenkapp, N2PLL and DVMProject (https://github.com/dvmproject) Authors." + L"\n"
L"Portions Copyright (c) 2015-2021 by Jonathan Naylor, G4KLX and others",
FMessageBox::ButtonType::Ok, FMessageBox::ButtonType::Reject, FMessageBox::ButtonType::Reject, this);
info.setCenterText();
info.show();
});
}
private:
friend class TGEdApplication;
LogDisplayWnd m_logWnd{this};
TGListWnd* m_wnd;
FMenuBar m_menuBar{this};
FMenu m_fileMenu{"&File", &m_menuBar};
FMenuItem m_saveSettingsItem{"&Save", &m_fileMenu};
FCheckMenuItem m_saveOnCloseToggle{"Save on Close?", &m_fileMenu};
FMenuItem m_fileMenuSeparator1{&m_fileMenu};
FMenuItem m_quitItem{"&Quit", &m_fileMenu};
FMenu m_helpMenu{"&Help", &m_menuBar};
FMenuItem m_aboutItem{"&About", &m_helpMenu};
FStatusBar m_statusBar{this};
FStatusKey m_keyF2{FKey::F2, "Save", &m_statusBar};
FStatusKey m_keyF3{FKey::F3, "Quit", &m_statusBar};
FStatusKey m_keyF5{FKey::F5, "Reload", &m_statusBar};
/*
** Event Handlers
*/
/**
* @brief Event that occurs when the window is shown.
* @param e Show Event
*/
void onShow(FShowEvent* e) override
{
const auto& rootWidget = getRootWidget();
int fullWidth = rootWidget->getWidth();
if (fullWidth < MINIMUM_SUPPORTED_SIZE_WIDTH) {
clearArea();
::fatal("screen resolution too small must be wider then %u characters, console width = %u", MINIMUM_SUPPORTED_SIZE_WIDTH, fullWidth);
}
int fullHeight = rootWidget->getHeight();
if (fullHeight < MINIMUM_SUPPORTED_SIZE_HEIGHT) {
clearArea();
::fatal("screen resolution too small must be taller then %u characters, console height = %u", MINIMUM_SUPPORTED_SIZE_HEIGHT, fullHeight);
}
int maxWidth = 77;
if (rootWidget) {
maxWidth = rootWidget->getClientWidth() - 3;
}
int maxHeight = TG_LIST_HEIGHT;
if (rootWidget) {
maxHeight = rootWidget->getClientHeight() - 3;
}
m_wnd = new TGListWnd(this);
if (maxHeight - 21 < TG_LIST_HEIGHT)
maxHeight = TG_LIST_HEIGHT + 21;
m_wnd->setGeometry(FPoint{2, 2}, FSize{(size_t)(maxWidth - 1), (size_t)(maxHeight - 21)});
m_wnd->setModal(false);
m_wnd->show();
m_wnd->raiseWindow();
m_wnd->activateWindow();
redraw();
if (g_hideLoggingWnd) {
const auto& rootWidget = getRootWidget();
m_logWnd.setGeometry(FPoint{(int)(rootWidget->getClientWidth() - 81), (int)(rootWidget->getClientHeight() - 1)}, FSize{80, 20});
m_logWnd.minimizeWindow();
}
}
/**
* @brief Event that occurs when the window is closed.
* @param e Close Event
*/
void onClose(FCloseEvent* e) override
{
// if we are saving on close -- fire off the file save event
if (m_saveOnCloseToggle.isChecked()) {
g_tidLookups->commit();
}
FApplication::closeConfirmationDialog(this, e);
}
};
#endif // __TGED_MAIN_WND_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) 2023 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file TGEditPeerListWnd.h
* @ingroup tged
*/
#if !defined(__TG_EDIT_PEER_LIST_WND_H__)
#define __TG_EDIT_PEER_LIST_WND_H__
#include "common/Log.h"
#include "tged/CloseWndBase.h"
#include "tged/TGEdMain.h"
#include <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements the line edit control for peer IDs.
* @ingroup tged
*/
class HOST_SW_API PeerLineEdit final : public FLineEdit {
public:
/**
* @brief Initializes a new instance of the PeerLineEdit class.
* @param widget
*/
explicit PeerLineEdit(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 peer list editor window.
* @ingroup tged
*/
class HOST_SW_API TGEditPeerListWnd final : public CloseWndBase {
public:
/**
* @brief Initializes a new instance of the TGEditWnd class.
* @param rule
* @param peerList
* @param title
* @param widget
*/
explicit TGEditPeerListWnd(lookups::TalkgroupRuleGroupVoice rule, std::vector<uint32_t> peerList,
std::string title = "Peer List", FWidget *widget = nullptr) : CloseWndBase{widget}
{
m_rule = rule;
this->peerList = peerList;
m_title = title;
}
/**
* @brief List of peer IDs.
*/
std::vector<uint32_t> peerList;
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{"Peer ID: ", this};
PeerLineEdit 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 > 999999999U) {
tgId = 999999999U;
}
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 peer 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 peer list.
*/
void loadList()
{
m_listBox.clear();
for (auto entry : peerList) {
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 peer 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;
}
peerList.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 peer ID %s for TG %s (%u)", m_title.c_str(), item.getText().c_str(),
m_rule.name().c_str(), m_rule.source().tgId());
peerList.push_back(peerId);
}
}
CloseWndBase::onClose(e);
}
};
#endif // __TG_EDIT_PEER_LIST_WND_H__

@ -0,0 +1,476 @@
// 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) 2023 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file TGEditWnd.h
* @ingroup tged
*/
#if !defined(__TG_EDIT_WND_H__)
#define __TG_EDIT_WND_H__
#include "common/Log.h"
#include "tged/CloseWndBase.h"
#include "tged/TGEdMain.h"
#include "tged/TGEditPeerListWnd.h"
#include <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements the line edit control for TGIDs.
* @ingroup tged
*/
class HOST_SW_API TGIdLineEdit final : public FLineEdit {
public:
/**
* @brief Initializes a new instance of the TGIdLineEdit class.
* @param widget
*/
explicit TGIdLineEdit(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;
}
FLineEdit::onKeyPress(e);
}
};
/**
* @brief This class implements the talkgroup editor window.
* @ingroup tged
*/
class HOST_SW_API TGEditWnd final : public CloseWndBase {
public:
/**
* @brief Initializes a new instance of the TGEditWnd class.
*
* @param rule
* @param widget
*/
explicit TGEditWnd(lookups::TalkgroupRuleGroupVoice rule, FWidget* widget = nullptr) : CloseWndBase{widget}
{
m_rule = rule;
if (m_rule.isInvalid()) {
m_new = true;
} else {
m_origTgId = m_rule.source().tgId();
m_origTgSlot = m_rule.source().tgSlot();
}
}
private:
bool m_new;
bool m_skipSaving;
lookups::TalkgroupRuleGroupVoice m_rule;
uint32_t m_origTgId;
uint8_t m_origTgSlot;
FLabel m_tgNameLabel{"Name: ", this};
FLineEdit m_tgName{this};
FLabel m_tgAliasLabel{"Alias: ", this};
FLineEdit m_tgAlias{this};
FCheckBox m_saveCopy{"Save Copy", this};
FCheckBox m_incOnSave{"Increment On Save", this};
FButtonGroup m_sourceGroup{"Source", this};
FLabel m_tgIdLabel{"TGID: ", &m_sourceGroup};
TGIdLineEdit m_tgId{&m_sourceGroup};
FLabel m_tgSlotLabel{"Slot: ", &m_sourceGroup};
FSpinBox m_tgSlot{&m_sourceGroup};
FButtonGroup m_configGroup{"Configuration", this};
FCheckBox m_activeEnabled{"Active", &m_configGroup};
FCheckBox m_affiliatedEnabled{"Affiliated", &m_configGroup};
FCheckBox m_parrotEnabled{"Parrot", &m_configGroup};
FButton m_inclusionList{"&Inclusions...", this};
FButton m_exclusionList{"&Exclusions...", this};
FButton m_alwaysList{"&Always...", this};
FButton m_preferredList{"&Preferred...", this};
FButton m_rewriteList{"&Rewrites...", this};
/**
* @brief Initializes the window layout.
*/
void initLayout() override
{
FDialog::setText("Talkgroup");
FDialog::setSize(FSize{60, 18});
m_enableSetButton = false;
CloseWndBase::initLayout();
}
/**
* @brief Initializes window controls.
*/
void initControls() override
{
m_closeButton.setText("&OK");
m_tgNameLabel.setGeometry(FPoint(2, 2), FSize(8, 1));
m_tgName.setGeometry(FPoint(11, 2), FSize(24, 1));
if (!m_rule.isInvalid()) {
m_tgName.setText(m_rule.name());
}
m_tgName.setShadow(false);
m_tgName.addCallback("changed", [&]() { m_rule.name(m_tgName.getText().toString()); });
m_tgAliasLabel.setGeometry(FPoint(2, 3), FSize(8, 1));
m_tgAlias.setGeometry(FPoint(11, 3), FSize(24, 1));
if (!m_rule.isInvalid()) {
m_tgAlias.setText(m_rule.nameAlias());
}
m_tgAlias.setShadow(false);
m_tgAlias.addCallback("changed", [&]() { m_rule.nameAlias(m_tgAlias.getText().toString()); });
m_saveCopy.setGeometry(FPoint(36, 2), FSize(18, 1));
m_saveCopy.addCallback("toggled", [&]() {
if (m_saveCopy.isChecked()) {
m_incOnSave.setEnable();
} else {
m_incOnSave.setChecked(false);
m_incOnSave.setDisable();
}
redraw();
});
m_incOnSave.setGeometry(FPoint(36, 3), FSize(18, 1));
m_incOnSave.setDisable();
// talkgroup source
{
m_sourceGroup.setGeometry(FPoint(2, 5), FSize(30, 5));
m_tgIdLabel.setGeometry(FPoint(2, 1), FSize(6, 1));
m_tgId.setGeometry(FPoint(11, 1), FSize(17, 1));
m_tgId.setAlignment(finalcut::Align::Right);
if (!m_rule.isInvalid()) {
m_tgId.setText(std::to_string(m_rule.source().tgId()));
} else {
auto source = m_rule.source();
source.tgId(1U);
m_rule.source(source);
m_tgId.setText("1");
}
m_tgId.setShadow(false);
m_tgId.addCallback("up-pressed", [&]() {
uint32_t tgId = ::atoi(m_tgId.getText().c_str());
tgId++;
if (tgId > 16777215U) {
tgId = 16777215U;
}
m_tgId.setText(std::to_string(tgId));
redraw();
});
m_tgId.addCallback("down-pressed", [&]() {
uint32_t tgId = ::atoi(m_tgId.getText().c_str());
tgId--;
if (tgId < 1U) {
tgId = 1U;
}
m_tgId.setText(std::to_string(tgId));
redraw();
});
m_tgId.addCallback("changed", [&]() {
uint32_t tgId = ::atoi(m_tgId.getText().c_str());
if (tgId < 1U) {
tgId = 1U;
}
if (tgId > 16777215U) {
tgId = 16777215U;
}
m_tgId.setText(std::to_string(tgId));
auto source = m_rule.source();
source.tgId(tgId);
m_rule.source(source);
});
m_tgSlotLabel.setGeometry(FPoint(2, 2), FSize(6, 1));
m_tgSlot.setGeometry(FPoint(11, 2), FSize(17, 1));
if (m_rule.source().tgSlot() != 0U)
m_tgSlot.setValue(m_rule.source().tgSlot());
else {
auto source = m_rule.source();
source.tgSlot(1U);
m_rule.source(source);
m_tgSlot.setValue(1U);
}
m_tgSlot.setMinValue(1U);
m_tgSlot.setMaxValue(2U);
m_tgSlot.setShadow(false);
m_tgSlot.addCallback("changed", [&]() {
auto source = m_rule.source();
source.tgSlot(m_tgSlot.getValue());
m_rule.source(source);
});
}
// configuration
{
m_configGroup.setGeometry(FPoint(34, 5), FSize(23, 5));
m_activeEnabled.setGeometry(FPoint(2, 1), FSize(10, 1));
m_activeEnabled.setChecked(m_rule.config().active());
m_activeEnabled.addCallback("toggled", [&]() {
auto config = m_rule.config();
config.active(m_activeEnabled.isChecked());
m_rule.config(config);
});
m_affiliatedEnabled.setGeometry(FPoint(2, 2), FSize(10, 1));
m_affiliatedEnabled.setChecked(m_rule.config().affiliated());
m_affiliatedEnabled.addCallback("toggled", [&]() {
auto config = m_rule.config();
config.affiliated(m_affiliatedEnabled.isChecked());
m_rule.config(config);
});
m_parrotEnabled.setGeometry(FPoint(2, 3), FSize(10, 1));
m_parrotEnabled.setChecked(m_rule.config().parrot());
m_parrotEnabled.addCallback("toggled", [&]() {
auto config = m_rule.config();
config.parrot(m_parrotEnabled.isChecked());
m_rule.config(config);
});
}
m_inclusionList.setGeometry(FPoint(2, 10), FSize(16, 1));
m_inclusionList.addCallback("clicked", [&]() {
TGEditPeerListWnd wnd{m_rule, m_rule.config().inclusion(), "Peer Inclusions", this};
wnd.show();
auto config = m_rule.config();
config.inclusion(wnd.peerList);
m_rule.config(config);
LogMessage(LOG_HOST, "Updated %s (%u) peer inclusion list", m_rule.name().c_str(), m_rule.source().tgId());
});
m_exclusionList.setGeometry(FPoint(20, 10), FSize(16, 1));
m_exclusionList.addCallback("clicked", [&]() {
TGEditPeerListWnd wnd{m_rule, m_rule.config().exclusion(), "Peer Exclusions", this};
wnd.show();
auto config = m_rule.config();
config.exclusion(wnd.peerList);
m_rule.config(config);
LogMessage(LOG_HOST, "Updated %s (%u) peer exclusion list", m_rule.name().c_str(), m_rule.source().tgId());
});
m_alwaysList.setGeometry(FPoint(2, 12), FSize(16, 1));
m_alwaysList.addCallback("clicked", [&]() {
TGEditPeerListWnd wnd{m_rule, m_rule.config().alwaysSend(), "Peers Always Receiving", this};
wnd.show();
auto config = m_rule.config();
config.alwaysSend(wnd.peerList);
m_rule.config(config);
LogMessage(LOG_HOST, "Updated %s (%u) peer always receiving list", m_rule.name().c_str(), m_rule.source().tgId());
});
m_preferredList.setGeometry(FPoint(20, 12), FSize(16, 1));
m_preferredList.addCallback("clicked", [&]() {
TGEditPeerListWnd wnd{m_rule, m_rule.config().preferred(), "Peer Preference", this};
wnd.show();
auto config = m_rule.config();
config.preferred(wnd.peerList);
m_rule.config(config);
LogMessage(LOG_HOST, "Updated %s (%u) peer preference list", m_rule.name().c_str(), m_rule.source().tgId());
});
m_rewriteList.setGeometry(FPoint(2, 14), FSize(16, 1));
m_rewriteList.setDisable();
m_rewriteList.addCallback("clicked", [&]() {
// TODO
});
CloseWndBase::initControls();
}
/**
* @brief
*/
void logRuleInfo()
{
std::string groupName = m_rule.name();
uint32_t tgId = m_rule.source().tgId();
uint8_t tgSlot = m_rule.source().tgSlot();
bool active = m_rule.config().active();
bool parrot = m_rule.config().parrot();
bool affil = m_rule.config().affiliated();
uint32_t incCount = m_rule.config().inclusion().size();
uint32_t excCount = m_rule.config().exclusion().size();
uint32_t rewrCount = m_rule.config().rewrite().size();
uint32_t alwyCount = m_rule.config().alwaysSend().size();
uint32_t prefCount = m_rule.config().preferred().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());
}
if (alwyCount > 0 && affil) {
::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);
}
/*
** 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::Enter) {
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;
}
if (!m_rule.isInvalid()) {
if (m_incOnSave.isChecked()) {
auto source = m_rule.source();
uint32_t tgId = source.tgId();
tgId++;
if (tgId > 16777215U) {
tgId = 16777215U;
}
source.tgId(tgId);
m_rule.source(source);
m_tgId.setText(std::to_string(tgId));
redraw();
}
if (m_origTgId != 0U && m_origTgSlot != 0U && !m_saveCopy.isChecked()) {
if (m_rule.name() == "") {
LogError(LOG_HOST, "Not saving talkgroup, TG %s (%u), talkgroup must be named.", m_rule.name().c_str(), m_rule.source().tgId());
FMessageBox::error(this, "Talkgroup must be named.");
return;
}
// update TG
auto groupVoice = g_tidLookups->groupVoice();
auto it = std::find_if(groupVoice.begin(), groupVoice.end(),
[&](lookups::TalkgroupRuleGroupVoice x)
{
return x.source().tgId() == m_origTgId && x.source().tgSlot() == m_origTgSlot;
});
if (it != groupVoice.end()) {
LogMessage(LOG_HOST, "Updating TG %s (%u) to %s (%u)", it->name().c_str(), it->source().tgId(), m_rule.name().c_str(), m_rule.source().tgId());
g_tidLookups->eraseEntry(m_origTgId, m_origTgSlot);
g_tidLookups->addEntry(m_rule);
logRuleInfo();
}
} else {
if (m_rule.name() == "") {
LogError(LOG_HOST, "Not saving talkgroup, TG %s (%u), talkgroup must be named.", m_rule.name().c_str(), m_rule.source().tgId());
FMessageBox::error(this, "Talkgroup must be named.");
return;
}
auto groupVoice = g_tidLookups->groupVoice();
auto it = std::find_if(groupVoice.begin(), groupVoice.end(),
[&](lookups::TalkgroupRuleGroupVoice x)
{
return x.source().tgId() == m_origTgId && x.source().tgSlot() == m_origTgSlot;
});
if (it != groupVoice.end()) {
LogError(LOG_HOST, "Not saving duplicate talkgroup, TG %s (%u), talkgroups must be unique.", m_rule.name().c_str(), m_rule.source().tgId());
FMessageBox::error(this, "Duplicate talkgroup, change TGID. Talkgroups must be unique.");
if (m_saveCopy.isChecked())
m_saveCopy.setChecked(false);
return;
}
// add new TG
if (m_saveCopy.isChecked()) {
LogMessage(LOG_HOST, "Copying TG. Adding TG %s (%u)", m_rule.name().c_str(), m_rule.source().tgId());
} else {
LogMessage(LOG_HOST, "Adding TG %s (%u)", m_rule.name().c_str(), m_rule.source().tgId());
}
g_tidLookups->addEntry(m_rule);
logRuleInfo();
// don't actually close the modal on a copy save
if (m_saveCopy.isChecked()) {
return;
}
}
} else {
LogError(LOG_HOST, "Not saving talkgroup, TG %s (%u), have a TGID greater than 0.", m_rule.name().c_str(), m_rule.source().tgId());
FMessageBox::error(this, "Talkgroup must have a TGID greater than 0.");
return;
}
CloseWndBase::onClose(e);
}
};
#endif // __TG_EDIT_WND_H__

@ -0,0 +1,297 @@
// 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) 2023 Bryan Biedenkapp, N2PLL
*
*/
/**
* @file TGListWnd.h
* @ingroup tged
*/
#if !defined(__TG_LIST_WND_H__)
#define __TG_LIST_WND_H__
#include "common/Log.h"
#include "TGEdMainWnd.h"
#include "TGEditWnd.h"
#include <final/final.h>
using namespace finalcut;
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
#define TG_LIST_WIDTH 74
#define TG_LIST_HEIGHT 15
// ---------------------------------------------------------------------------
// Class Declaration
// ---------------------------------------------------------------------------
/**
* @brief This class implements the talkgroup list window.
* @ingroup tged
*/
class HOST_SW_API TGListWnd final : public finalcut::FDialog {
public:
/**
* @brief Initializes a new instance of the TGListWnd class.
* @param widget
*/
explicit TGListWnd(FWidget* widget = nullptr) : FDialog{widget}
{
/* stub */
}
/**
* @brief Copy constructor.
*/
TGListWnd(const TGListWnd&) = delete;
/**
* @brief Move constructor.
*/
TGListWnd(TGListWnd&&) noexcept = delete;
/**
* @brief Finalizes an instance of the TGListWnd class.
*/
~TGListWnd() noexcept override = default;
/**
* @brief Disable copy assignment operator (=).
*/
auto operator= (const TGListWnd&) -> TGListWnd& = delete;
/**
* @brief Disable move assignment operator (=).
*/
auto operator= (TGListWnd&&) noexcept -> TGListWnd& = delete;
/**
* @brief Disable set X coordinate.
*/
void setX(int, bool = true) override { }
/**
* @brief Disable set Y coordinate.
*/
void setY(int, bool = true) override { }
/**
* @brief Disable set position.
*/
void setPos(const FPoint&, bool = true) override { }
/**
* @brief Populates the talkgroup listview.
*/
void loadListView()
{
m_selected = TalkgroupRuleGroupVoice();
m_selectedTgId = 0U;
m_listView.clear();
for (auto entry : g_tidLookups->groupVoice()) {
// pad TGs properly
std::ostringstream oss;
oss << std::setw(5) << std::setfill('0') << entry.source().tgId();
// build list view entry
const std::array<std::string, 8U> 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())
};
const finalcut::FStringList line(columns.cbegin(), columns.cend());
m_listView.insert(line);
}
setFocusWidget(&m_listView);
redraw();
}
private:
lookups::TalkgroupRuleGroupVoice m_selected;
uint32_t m_selectedTgId;
FListView m_listView{this};
FButton m_addTG{"&Add", this};
FButton m_editTG{"&Edit", this};
FButton m_deleteTG{"&Delete", this};
/**
* @brief Initializes the window layout.
*/
void initLayout() override
{
FDialog::setMinimumSize(FSize{TG_LIST_WIDTH, TG_LIST_HEIGHT});
FDialog::setResizeable(false);
FDialog::setMinimizable(false);
FDialog::setTitlebarButtonVisibility(false);
FDialog::setModal(false);
FDialog::setText("Talkgroup List");
initControls();
loadListView();
FDialog::initLayout();
}
/**
* @brief Initializes window controls.
*/
void initControls()
{
m_addTG.setGeometry(FPoint(2, int(getHeight() - 4)), FSize(9, 1));
m_addTG.setBackgroundColor(FColor::DarkGreen);
m_addTG.setFocusBackgroundColor(FColor::DarkGreen);
m_addTG.addCallback("clicked", [&]() { addEntry(); });
m_editTG.setGeometry(FPoint(13, int(getHeight() - 4)), FSize(10, 1));
m_editTG.setDisable();
m_editTG.addCallback("clicked", [&]() { editEntry(); });
m_deleteTG.setGeometry(FPoint(int(getWidth()) - 13, int(getHeight() - 4)), FSize(10, 1));
m_deleteTG.setDisable();
m_deleteTG.addCallback("clicked", [&]() { deleteEntry(); });
m_listView.setGeometry(FPoint{1, 1}, FSize{getWidth() - 1, getHeight() - 5});
// configure list view columns
m_listView.addColumn("Name", 25);
m_listView.addColumn("Alias", 20);
m_listView.addColumn("TGID", 9);
m_listView.addColumn("Active", 5);
m_listView.addColumn("Affiliated", 5);
m_listView.addColumn("Inclusions", 5);
m_listView.addColumn("Exclusions", 5);
m_listView.addColumn("Always", 5);
// set right alignment for TGID
m_listView.setColumnAlignment(3, finalcut::Align::Right);
m_listView.setColumnAlignment(4, finalcut::Align::Center);
m_listView.setColumnAlignment(5, finalcut::Align::Center);
m_listView.setColumnAlignment(6, finalcut::Align::Right);
m_listView.setColumnAlignment(7, finalcut::Align::Right);
m_listView.setColumnAlignment(8, finalcut::Align::Right);
// set type of sorting
m_listView.setColumnSortType(1, finalcut::SortType::Name);
m_listView.setColumnSortType(2, finalcut::SortType::Name);
m_listView.setColumnSortType(3, finalcut::SortType::Name);
// sort by TGID
m_listView.setColumnSort(2, finalcut::SortOrder::Ascending);
m_listView.setColumnSort(3, finalcut::SortOrder::Ascending);
m_listView.addCallback("clicked", [&]() { editEntry(); });
m_listView.addCallback("row-changed", [&]() {
FListViewItem* curItem = m_listView.getCurrentItem();
if (curItem != nullptr) {
FString strTgid = curItem->getText(3);
uint32_t tgid = ::atoi(strTgid.c_str());
if (tgid != m_selectedTgId) {
auto entry = g_tidLookups->find(tgid);
if (!entry.isInvalid()) {
m_selected = entry;
if (m_selectedTgId != tgid)
LogMessage(LOG_HOST, "Selected TG %s (%u) for editing", m_selected.name().c_str(), m_selected.source().tgId());
m_selectedTgId = tgid;
m_editTG.setEnable();
m_deleteTG.setEnable();
m_deleteTG.setBackgroundColor(FColor::DarkRed);
m_deleteTG.setFocusBackgroundColor(FColor::DarkRed);
} else {
m_editTG.setDisable();
m_deleteTG.setDisable();
m_deleteTG.resetColors();
}
redraw();
}
}
});
setFocusWidget(&m_listView);
redraw();
}
/**
* @brief
*/
void addEntry()
{
this->lowerWindow();
this->deactivateWindow();
TGEditWnd wnd{TalkgroupRuleGroupVoice(), this};
wnd.show();
this->raiseWindow();
this->activateWindow();
loadListView();
}
/**
* @brief
*/
void editEntry()
{
if (m_selected.isInvalid())
return;
this->lowerWindow();
this->deactivateWindow();
TGEditWnd wnd{m_selected, this};
wnd.show();
this->raiseWindow();
this->activateWindow();
loadListView();
}
/**
* @brief
*/
void deleteEntry()
{
if (m_selected.isInvalid())
return;
LogMessage(LOG_HOST, "Deleting TG %s (%u)", m_selected.name().c_str(), m_selected.source().tgId());
g_tidLookups->eraseEntry(m_selected.source().tgId(), m_selected.source().tgSlot());
loadListView();
}
/*
** 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();
} else if (key == FKey::Enter || key == FKey::Return) {
editEntry();
}
}
};
#endif // __TG_LIST_WND_H__
Loading…
Cancel
Save

Powered by TurnKey Linux.