add peer ID editor; implement support in the peer lookup and FNE to validate whether or not a peer can perform a encryption key request;
parent
6927ea284c
commit
5a96f10906
@ -1,9 +1,9 @@
|
|||||||
#
|
#
|
||||||
# This file sets the valid peer IDs allowed on a FNE.
|
# This file sets the valid peer IDs allowed on a FNE.
|
||||||
#
|
#
|
||||||
# Entry Format: "Peer ID,Peer Password,Peer Link (1 = Enabled / 0 = Disabled),Peer Alias (optional),<newline>"
|
# Entry Format: "Peer ID,Peer Password,Peer Link (1 = Enabled / 0 = Disabled),Peer Alias (optional),Can Request Keys (1 = Enabled / 0 = Disabled),<newline>"
|
||||||
#1234,,0,
|
#1234,,0,,1,
|
||||||
#5678,MYSECUREPASSWORD,0,
|
#5678,MYSECUREPASSWORD,0,,0,
|
||||||
#9876,MYSECUREPASSWORD,1,
|
#9876,MYSECUREPASSWORD,1,,0,
|
||||||
#5432,MYSECUREPASSWORD,,Peer Alias 1,
|
#5432,MYSECUREPASSWORD,,Peer Alias 1,0,
|
||||||
#1012,MYSECUREPASSWORD,1,Peer Alias 2,
|
#1012,MYSECUREPASSWORD,1,Peer Alias 2,1,
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
#/*
|
||||||
|
# * Digital Voice Modem - Peer ID 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 peered_SRC
|
||||||
|
"src/peered/*.h"
|
||||||
|
"src/peered/*.cpp"
|
||||||
|
)
|
||||||
@ -0,0 +1,130 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Digital Voice Modem - Peer ID Editor
|
||||||
|
* GPLv2 Open Source. Use is subject to license terms.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023,2024,2025 Bryan Biedenkapp, N2PLL
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @file CloseWndBase.h
|
||||||
|
* @ingroup peered
|
||||||
|
*/
|
||||||
|
#if !defined(__CLOSE_WND_BASE_H__)
|
||||||
|
#define __CLOSE_WND_BASE_H__
|
||||||
|
|
||||||
|
#include "common/Thread.h"
|
||||||
|
|
||||||
|
#include "FDblDialog.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 FDblDialog {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the CloseWndBase class.
|
||||||
|
* @param widget
|
||||||
|
*/
|
||||||
|
explicit CloseWndBase(FWidget* widget = nullptr) : FDblDialog{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 - Peer ID Editor
|
||||||
|
* GPLv2 Open Source. Use is subject to license terms.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @defgroup peered Peer ID Editor (peered)
|
||||||
|
* @brief Digital Voice Modem - Peer ID Editor
|
||||||
|
* @details Helper software that edits peer ID files with a graphical TUI.
|
||||||
|
* @ingroup peered
|
||||||
|
*
|
||||||
|
* @file Defines.h
|
||||||
|
* @ingroup peered
|
||||||
|
*/
|
||||||
|
#if !defined(__DEFINES_H__)
|
||||||
|
#define __DEFINES_H__
|
||||||
|
|
||||||
|
#include "common/Defines.h"
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#undef __PROG_NAME__
|
||||||
|
#define __PROG_NAME__ "Digital Voice Modem (DVM) Peer ID Editor"
|
||||||
|
#undef __EXE_NAME__
|
||||||
|
#define __EXE_NAME__ "peered"
|
||||||
|
|
||||||
|
#endif // __DEFINES_H__
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Digital Voice Modem - Peer ID 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 FDblDialog.h
|
||||||
|
* @ingroup peered
|
||||||
|
*/
|
||||||
|
#if !defined(__F_DBL_DIALOG_H__)
|
||||||
|
#define __F_DBL_DIALOG_H__
|
||||||
|
|
||||||
|
#include "common/Defines.h"
|
||||||
|
|
||||||
|
#include <final/final.h>
|
||||||
|
using namespace finalcut;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This class implements the double-border dialog.
|
||||||
|
* @ingroup peered
|
||||||
|
*/
|
||||||
|
class HOST_SW_API FDblDialog : public finalcut::FDialog {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the FDblDialog class.
|
||||||
|
* @param widget
|
||||||
|
*/
|
||||||
|
explicit FDblDialog(FWidget* widget = nullptr) : finalcut::FDialog{widget}
|
||||||
|
{
|
||||||
|
/* stub */
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*/
|
||||||
|
void drawBorder() override
|
||||||
|
{
|
||||||
|
if (!hasBorder())
|
||||||
|
return;
|
||||||
|
|
||||||
|
setColor();
|
||||||
|
|
||||||
|
FRect box{{1, 2}, getSize()};
|
||||||
|
box.scaleBy(0, -1);
|
||||||
|
|
||||||
|
FRect rect = box;
|
||||||
|
if (rect.x1_ref() > rect.x2_ref())
|
||||||
|
std::swap(rect.x1_ref(), rect.x2_ref());
|
||||||
|
|
||||||
|
if (rect.y1_ref() > rect.y2_ref())
|
||||||
|
std::swap(rect.y1_ref(), rect.y2_ref());
|
||||||
|
|
||||||
|
rect.x1_ref() = std::max(rect.x1_ref(), 1);
|
||||||
|
rect.y1_ref() = std::max(rect.y1_ref(), 1);
|
||||||
|
rect.x2_ref() = std::min(rect.x2_ref(), rect.x1_ref() + int(getWidth()) - 1);
|
||||||
|
rect.y2_ref() = std::min(rect.y2_ref(), rect.y1_ref() + int(getHeight()) - 1);
|
||||||
|
|
||||||
|
if (box.getWidth() < 3)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Use box-drawing characters to draw a border
|
||||||
|
constexpr std::array<wchar_t, 8> box_char
|
||||||
|
{{
|
||||||
|
static_cast<wchar_t>(0x2554), // ╔
|
||||||
|
static_cast<wchar_t>(0x2550), // ═
|
||||||
|
static_cast<wchar_t>(0x2557), // ╗
|
||||||
|
static_cast<wchar_t>(0x2551), // ║
|
||||||
|
static_cast<wchar_t>(0x2551), // ║
|
||||||
|
static_cast<wchar_t>(0x255A), // ╚
|
||||||
|
static_cast<wchar_t>(0x2550), // ═
|
||||||
|
static_cast<wchar_t>(0x255D) // ╝
|
||||||
|
}};
|
||||||
|
|
||||||
|
drawGenericBox(this, box, box_char);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __F_DBL_DIALOG_H__
|
||||||
@ -0,0 +1,144 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Digital Voice Modem - Peer ID 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 peered
|
||||||
|
*/
|
||||||
|
#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 peered
|
||||||
|
*/
|
||||||
|
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 - Peer ID Editor
|
||||||
|
* GPLv2 Open Source. Use is subject to license terms.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023,2024 Bryan Biedenkapp, N2PLL
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @file PeerEdApplication.h
|
||||||
|
* @ingroup peered
|
||||||
|
*/
|
||||||
|
#if !defined(__PEERED_APPLICATION_H__)
|
||||||
|
#define __PEERED_APPLICATION_H__
|
||||||
|
|
||||||
|
#include "common/Log.h"
|
||||||
|
#include "PeerEdMain.h"
|
||||||
|
#include "PeerEdMainWnd.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 PeerEdApplication final : public finalcut::FApplication {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the PeerEdApplication class.
|
||||||
|
* @param argc Passed argc.
|
||||||
|
* @param argv Passed argv.
|
||||||
|
*/
|
||||||
|
explicit PeerEdApplication(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 // __PEERED_APPLICATION_H__
|
||||||
@ -0,0 +1,225 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Digital Voice Modem - Peer ID 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 "PeerEdMain.h"
|
||||||
|
#include "PeerEdApplication.h"
|
||||||
|
#include "PeerEdMainWnd.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::PeerListLookup* g_pidLookups = 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-2025 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 <peer ID 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 peer ID 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 peer ID file to edit");
|
||||||
|
g_iniFile = std::string(argv[++i]);
|
||||||
|
|
||||||
|
if (g_iniFile.empty())
|
||||||
|
usage("error: %s", "peer ID 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-2025 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-2025 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" \
|
||||||
|
">> Peer ID 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
|
||||||
|
PeerEdApplication app{argc, argv};
|
||||||
|
|
||||||
|
PeerEdMainWnd wnd{&app};
|
||||||
|
finalcut::FWidget::setMainWidget(&wnd);
|
||||||
|
|
||||||
|
g_logDisplayLevel = 0U;
|
||||||
|
|
||||||
|
g_pidLookups = new PeerListLookup(g_iniFile, PeerListLookup::WHITELIST, 0U, false);
|
||||||
|
g_pidLookups->read();
|
||||||
|
LogMessage(LOG_HOST, "Loaded peer ID file: %s", g_iniFile.c_str());
|
||||||
|
|
||||||
|
// 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,101 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Digital Voice Modem - Peer ID 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 PeerEdMain.h
|
||||||
|
* @ingroup peered
|
||||||
|
* @file PeerEdMain.cpp
|
||||||
|
* @ingroup peered
|
||||||
|
*/
|
||||||
|
#if !defined(__PEERED_MAIN_H__)
|
||||||
|
#define __PEERED_MAIN_H__
|
||||||
|
|
||||||
|
#include "Defines.h"
|
||||||
|
#include "common/lookups/PeerListLookup.h"
|
||||||
|
#include "common/yaml/Yaml.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#undef __PROG_NAME__
|
||||||
|
#define __PROG_NAME__ "Digital Voice Modem (DVM) Peer ID Editor"
|
||||||
|
#undef __EXE_NAME__
|
||||||
|
#define __EXE_NAME__ "peered"
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bryanb: This is some low-down, dirty, C++ hack-o-ramma.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Implements RTTI type defining.
|
||||||
|
* @typedef Tag
|
||||||
|
*/
|
||||||
|
template<typename Tag>
|
||||||
|
struct RTTIResult {
|
||||||
|
typedef typename Tag::type type;
|
||||||
|
static type ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Tag>
|
||||||
|
typename RTTIResult<Tag>::type RTTIResult<Tag>::ptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Implements nasty hack to access private members of a class.
|
||||||
|
* @typedef Tag
|
||||||
|
* @typedef TypePtr
|
||||||
|
*/
|
||||||
|
template<typename Tag, typename Tag::type TypePtr>
|
||||||
|
struct HackTheGibson : RTTIResult<Tag> {
|
||||||
|
/* fill it ... */
|
||||||
|
struct filler {
|
||||||
|
filler() { RTTIResult<Tag>::ptr = TypePtr; }
|
||||||
|
};
|
||||||
|
static filler fillerObj;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Tag, typename Tag::type TypePtr>
|
||||||
|
typename HackTheGibson<Tag, TypePtr>::filler HackTheGibson<Tag, TypePtr>::fillerObj;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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::PeerListLookup* g_pidLookups;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 // __PEERED_MAIN_H__
|
||||||
@ -0,0 +1,218 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Digital Voice Modem - Peer ID 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 PeerEdMainWnd.h
|
||||||
|
* @ingroup peered
|
||||||
|
*/
|
||||||
|
#if !defined(__PEERED_MAIN_WND_H__)
|
||||||
|
#define __PEERED_MAIN_WND_H__
|
||||||
|
|
||||||
|
#include "common/Log.h"
|
||||||
|
#include "common/Thread.h"
|
||||||
|
|
||||||
|
using namespace lookups;
|
||||||
|
|
||||||
|
#include <final/final.h>
|
||||||
|
using namespace finalcut;
|
||||||
|
#undef null
|
||||||
|
|
||||||
|
#include "PeerEdMain.h"
|
||||||
|
|
||||||
|
#include "LogDisplayWnd.h"
|
||||||
|
#include "PeerListWnd.h"
|
||||||
|
#include "PeerEditWnd.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#define MINIMUM_SUPPORTED_SIZE_WIDTH 83
|
||||||
|
#define MINIMUM_SUPPORTED_SIZE_HEIGHT 30
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Prototypes
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class HOST_SW_API PeerEdApplication;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This class implements the root window control.
|
||||||
|
* @ingroup peered
|
||||||
|
*/
|
||||||
|
class HOST_SW_API PeerEdMainWnd final : public finalcut::FWidget {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the PeerEdMainWnd class.
|
||||||
|
* @param widget
|
||||||
|
*/
|
||||||
|
explicit PeerEdMainWnd(FWidget* widget = nullptr) : FWidget{widget}
|
||||||
|
{
|
||||||
|
__InternalOutputStream(m_logWnd);
|
||||||
|
|
||||||
|
// file menu
|
||||||
|
m_fileMenuSeparator1.setSeparator();
|
||||||
|
m_fileMenuSeparator2.setSeparator();
|
||||||
|
m_saveSettingsItem.addAccelerator(FKey::Meta_s); // Meta/Alt + S
|
||||||
|
m_saveSettingsItem.addCallback("clicked", this, [&]() { save(); });
|
||||||
|
m_reloadSettingsItem.addAccelerator(FKey::Meta_r); // Meta/Alt + R
|
||||||
|
m_reloadSettingsItem.addCallback("clicked", this, [&]() { g_pidLookups->reload(); m_wnd->loadListView(); });
|
||||||
|
m_keyF2.addCallback("activate", this, [&]() { save(); });
|
||||||
|
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_pidLookups->reload(); m_wnd->loadListView(); LogMessage(LOG_HOST, "Loaded peer ID file: %s", g_iniFile.c_str()); });
|
||||||
|
|
||||||
|
m_backupOnSave.setChecked();
|
||||||
|
|
||||||
|
// 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-2025 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 PeerEdApplication;
|
||||||
|
|
||||||
|
LogDisplayWnd m_logWnd{this};
|
||||||
|
PeerListWnd* m_wnd;
|
||||||
|
|
||||||
|
FMenuBar m_menuBar{this};
|
||||||
|
|
||||||
|
FMenu m_fileMenu{"&File", &m_menuBar};
|
||||||
|
FMenuItem m_reloadSettingsItem{"&Reload", &m_fileMenu};
|
||||||
|
FMenuItem m_saveSettingsItem{"&Save", &m_fileMenu};
|
||||||
|
FMenuItem m_fileMenuSeparator2{&m_fileMenu};
|
||||||
|
FCheckMenuItem m_saveOnCloseToggle{"Save on Close?", &m_fileMenu};
|
||||||
|
FCheckMenuItem m_backupOnSave{"Backup Rules File?", &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};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*/
|
||||||
|
void save()
|
||||||
|
{
|
||||||
|
if (m_backupOnSave.isChecked()) {
|
||||||
|
std::string bakFile = g_iniFile + ".bak";
|
||||||
|
LogMessage(LOG_HOST, "Backing up existing file %s to %s", g_iniFile.c_str(), bakFile.c_str());
|
||||||
|
copyFile(g_iniFile.c_str(), bakFile.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
g_pidLookups->commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Helper to copy one file to another.
|
||||||
|
* @param src Source file.
|
||||||
|
* @param dest Destination File.
|
||||||
|
* @returns bool True, if file copied, otherwise false.
|
||||||
|
*/
|
||||||
|
bool copyFile(const char *srcFilePath, const char* destFilePath)
|
||||||
|
{
|
||||||
|
std::ifstream src(srcFilePath, std::ios::binary);
|
||||||
|
std::ofstream dest(destFilePath, std::ios::binary);
|
||||||
|
dest << src.rdbuf();
|
||||||
|
return src && dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** 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 = PEER_LIST_HEIGHT;
|
||||||
|
if (rootWidget) {
|
||||||
|
maxHeight = rootWidget->getClientHeight() - 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_wnd = new PeerListWnd(this);
|
||||||
|
if (maxHeight - 21 < PEER_LIST_HEIGHT)
|
||||||
|
maxHeight = PEER_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_pidLookups->commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
FApplication::closeConfirmationDialog(this, e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __PEERED_MAIN_WND_H__
|
||||||
@ -0,0 +1,367 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Digital Voice Modem - Peer ID Editor
|
||||||
|
* GPLv2 Open Source. Use is subject to license terms.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @file PeerEditWnd.h
|
||||||
|
* @ingroup peered
|
||||||
|
*/
|
||||||
|
#if !defined(__PEER_EDIT_WND_H__)
|
||||||
|
#define __PEER_EDIT_WND_H__
|
||||||
|
|
||||||
|
#include "common/Log.h"
|
||||||
|
|
||||||
|
#include "peered/CloseWndBase.h"
|
||||||
|
#include "peered/PeerEdMain.h"
|
||||||
|
|
||||||
|
#include <final/final.h>
|
||||||
|
using namespace finalcut;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This class implements the line edit control for peer IDs.
|
||||||
|
* @ingroup peered
|
||||||
|
*/
|
||||||
|
class HOST_SW_API PeerIdLineEdit final : public FLineEdit {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the PeerIdLineEdit class.
|
||||||
|
* @param widget
|
||||||
|
*/
|
||||||
|
explicit PeerIdLineEdit(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 peer ID editor window.
|
||||||
|
* @ingroup peered
|
||||||
|
*/
|
||||||
|
class HOST_SW_API PeerEditWnd final : public CloseWndBase {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the PeerEditWnd class.
|
||||||
|
*
|
||||||
|
* @param rule
|
||||||
|
* @param widget
|
||||||
|
*/
|
||||||
|
explicit PeerEditWnd(lookups::PeerId rule, FWidget* widget = nullptr) : CloseWndBase{widget}
|
||||||
|
{
|
||||||
|
m_rule = rule;
|
||||||
|
if (m_rule.peerDefault()) {
|
||||||
|
m_new = true;
|
||||||
|
} else {
|
||||||
|
m_origPeerId = m_rule.peerId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_new;
|
||||||
|
bool m_skipSaving;
|
||||||
|
lookups::PeerId m_rule;
|
||||||
|
|
||||||
|
uint32_t m_origPeerId;
|
||||||
|
|
||||||
|
FLabel m_peerAliasLabel{"Alias: ", this};
|
||||||
|
FLineEdit m_peerAlias{this};
|
||||||
|
|
||||||
|
FCheckBox m_saveCopy{"Save Copy", this};
|
||||||
|
FCheckBox m_incOnSave{"Increment On Save", this};
|
||||||
|
|
||||||
|
FButtonGroup m_sourceGroup{"Peer ID", this};
|
||||||
|
FLabel m_peerIdLabel{"Peer ID: ", &m_sourceGroup};
|
||||||
|
PeerIdLineEdit m_peerId{&m_sourceGroup};
|
||||||
|
FLabel m_peerPasswordLabel{"Password: ", &m_sourceGroup};
|
||||||
|
FLineEdit m_peerPassword{&m_sourceGroup};
|
||||||
|
|
||||||
|
FButtonGroup m_configGroup{"Configuration", this};
|
||||||
|
FCheckBox m_peerLinkEnabled{"Peer Link", &m_configGroup};
|
||||||
|
FCheckBox m_canReqKeysEnabled{"Request Keys", &m_configGroup};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes the window layout.
|
||||||
|
*/
|
||||||
|
void initLayout() override
|
||||||
|
{
|
||||||
|
FDialog::setText("Peer ID");
|
||||||
|
FDialog::setSize(FSize{60, 18});
|
||||||
|
|
||||||
|
m_enableSetButton = false;
|
||||||
|
CloseWndBase::initLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes window controls.
|
||||||
|
*/
|
||||||
|
void initControls() override
|
||||||
|
{
|
||||||
|
m_closeButton.setText("&OK");
|
||||||
|
|
||||||
|
m_peerAliasLabel.setGeometry(FPoint(2, 2), FSize(8, 1));
|
||||||
|
m_peerAlias.setGeometry(FPoint(11, 2), FSize(24, 1));
|
||||||
|
if (!m_rule.peerDefault()) {
|
||||||
|
m_peerAlias.setText(m_rule.peerAlias());
|
||||||
|
}
|
||||||
|
m_peerAlias.setShadow(false);
|
||||||
|
m_peerAlias.addCallback("changed", [&]() { m_rule.peerAlias(m_peerAlias.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_peerIdLabel.setGeometry(FPoint(2, 1), FSize(10, 1));
|
||||||
|
m_peerId.setGeometry(FPoint(11, 1), FSize(17, 1));
|
||||||
|
m_peerId.setAlignment(finalcut::Align::Right);
|
||||||
|
if (!m_rule.peerDefault()) {
|
||||||
|
m_peerId.setText(std::to_string(m_rule.peerId()));
|
||||||
|
} else {
|
||||||
|
m_rule.peerId(1U);
|
||||||
|
m_peerId.setText("1");
|
||||||
|
}
|
||||||
|
m_peerId.setShadow(false);
|
||||||
|
m_peerId.addCallback("up-pressed", [&]() {
|
||||||
|
uint32_t peerId = ::atoi(m_peerId.getText().c_str());
|
||||||
|
peerId++;
|
||||||
|
if (peerId > 999999999U) {
|
||||||
|
peerId = 999999999U;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_peerId.setText(std::to_string(peerId));
|
||||||
|
|
||||||
|
m_rule.peerId(peerId);
|
||||||
|
redraw();
|
||||||
|
});
|
||||||
|
m_peerId.addCallback("down-pressed", [&]() {
|
||||||
|
uint32_t peerId = ::atoi(m_peerId.getText().c_str());
|
||||||
|
peerId--;
|
||||||
|
if (peerId < 1U) {
|
||||||
|
peerId = 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_peerId.setText(std::to_string(peerId));
|
||||||
|
|
||||||
|
m_rule.peerId(peerId);
|
||||||
|
redraw();
|
||||||
|
});
|
||||||
|
m_peerId.addCallback("changed", [&]() {
|
||||||
|
if (m_peerId.getText().getLength() == 0) {
|
||||||
|
m_rule.peerId(1U);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t peerId = ::atoi(m_peerId.getText().c_str());
|
||||||
|
if (peerId < 1U) {
|
||||||
|
peerId = 1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peerId > 999999999U) {
|
||||||
|
peerId = 999999999U;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_peerId.setText(std::to_string(peerId));
|
||||||
|
|
||||||
|
m_rule.peerId(peerId);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_peerPasswordLabel.setGeometry(FPoint(2, 2), FSize(10, 1));
|
||||||
|
m_peerPassword.setGeometry(FPoint(11, 2), FSize(17, 1));
|
||||||
|
if (!m_rule.peerDefault()) {
|
||||||
|
m_peerPassword.setText(m_rule.peerPassword());
|
||||||
|
}
|
||||||
|
m_peerPassword.setShadow(false);
|
||||||
|
m_peerPassword.addCallback("changed", [&]() { m_rule.peerPassword(m_peerPassword.getText().toString()); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// configuration
|
||||||
|
{
|
||||||
|
m_configGroup.setGeometry(FPoint(34, 5), FSize(23, 5));
|
||||||
|
|
||||||
|
m_peerLinkEnabled.setGeometry(FPoint(2, 1), FSize(10, 1));
|
||||||
|
m_peerLinkEnabled.setChecked(m_rule.peerLink());
|
||||||
|
m_peerLinkEnabled.addCallback("toggled", [&]() {
|
||||||
|
m_rule.peerLink(m_peerLinkEnabled.isChecked());
|
||||||
|
});
|
||||||
|
|
||||||
|
m_canReqKeysEnabled.setGeometry(FPoint(2, 2), FSize(10, 1));
|
||||||
|
m_canReqKeysEnabled.setChecked(m_rule.peerLink());
|
||||||
|
m_canReqKeysEnabled.addCallback("toggled", [&]() {
|
||||||
|
m_rule.canRequestKeys(m_canReqKeysEnabled.isChecked());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseWndBase::initControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*/
|
||||||
|
void logRuleInfo()
|
||||||
|
{
|
||||||
|
std::string peerAlias = m_rule.peerAlias();
|
||||||
|
uint32_t peerId = m_rule.peerId();
|
||||||
|
bool peerLink = m_rule.peerLink();
|
||||||
|
bool canRequestKeys = m_rule.canRequestKeys();
|
||||||
|
|
||||||
|
::LogInfoEx(LOG_HOST, "Peer ALIAS: %s PEERID: %u PEER LINK: %u CAN REQUEST KEYS: %u", peerAlias.c_str(), peerId, peerLink, canRequestKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** 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.peerDefault()) {
|
||||||
|
if (m_incOnSave.isChecked()) {
|
||||||
|
uint32_t peerId = m_rule.peerId();
|
||||||
|
peerId++;
|
||||||
|
|
||||||
|
if (peerId > 999999999U) {
|
||||||
|
peerId = 999999999U;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_rule.peerId(peerId);
|
||||||
|
|
||||||
|
m_peerId.setText(std::to_string(peerId));
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_origPeerId != 0U && !m_saveCopy.isChecked()) {
|
||||||
|
if (m_rule.peerId() == 0U) {
|
||||||
|
LogError(LOG_HOST, "Not saving peer, peer %s (%u), peer ID must be greater then 0.", m_rule.peerAlias().c_str(), m_rule.peerId());
|
||||||
|
FMessageBox::error(this, "Peer ID must be valid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update peer
|
||||||
|
auto peers = g_pidLookups->tableAsList();
|
||||||
|
auto it = std::find_if(peers.begin(), peers.end(),
|
||||||
|
[&](lookups::PeerId x)
|
||||||
|
{
|
||||||
|
return x.peerId() == m_origPeerId;
|
||||||
|
});
|
||||||
|
if (it != peers.end()) {
|
||||||
|
LogMessage(LOG_HOST, "Updating peer %s (%u) to %s (%u)", it->peerAlias().c_str(), it->peerId(), m_rule.peerAlias().c_str(), m_rule.peerId());
|
||||||
|
g_pidLookups->eraseEntry(m_origPeerId);
|
||||||
|
g_pidLookups->addEntry(m_rule.peerId(), m_rule.peerAlias(), m_rule.peerPassword(), m_rule.peerLink());
|
||||||
|
|
||||||
|
logRuleInfo();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (m_rule.peerId() == 0U) {
|
||||||
|
LogError(LOG_HOST, "Not saving peer, peer %s (%u), peer ID must be greater then 0.", m_rule.peerAlias().c_str(), m_rule.peerId());
|
||||||
|
FMessageBox::error(this, "Peer ID must be valid.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto peers = g_pidLookups->tableAsList();
|
||||||
|
auto it = std::find_if(peers.begin(), peers.end(),
|
||||||
|
[&](lookups::PeerId x)
|
||||||
|
{
|
||||||
|
return x.peerId() == m_rule.peerId();
|
||||||
|
});
|
||||||
|
if (it != peers.end()) {
|
||||||
|
LogError(LOG_HOST, "Not saving duplicate peer, peer %s (%u), peers must be unique.", m_rule.peerAlias().c_str(), m_rule.peerId());
|
||||||
|
FMessageBox::error(this, "Duplicate peer, change peer ID. Peers must be unique.");
|
||||||
|
if (m_saveCopy.isChecked())
|
||||||
|
m_saveCopy.setChecked(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new peer
|
||||||
|
if (m_saveCopy.isChecked()) {
|
||||||
|
LogMessage(LOG_HOST, "Copying Peer. Adding Peer %s (%u)", m_rule.peerAlias().c_str(), m_rule.peerId());
|
||||||
|
} else {
|
||||||
|
LogMessage(LOG_HOST, "Adding Peer %s (%u)", m_rule.peerAlias().c_str(), m_rule.peerId());
|
||||||
|
}
|
||||||
|
g_pidLookups->addEntry(m_rule.peerId(), m_rule.peerAlias(), m_rule.peerPassword(), m_rule.peerLink());
|
||||||
|
|
||||||
|
logRuleInfo();
|
||||||
|
|
||||||
|
// don't actually close the modal on a copy save
|
||||||
|
if (m_saveCopy.isChecked()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LogError(LOG_HOST, "Not saving peer, peer %s (%u), have a peer ID greater than 0.", m_rule.peerAlias().c_str(), m_rule.peerId());
|
||||||
|
FMessageBox::error(this, "Talkgroup must have a peer ID greater than 0.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseWndBase::onClose(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __PEER_EDIT_WND_H__
|
||||||
@ -0,0 +1,391 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Digital Voice Modem - Peer ID Editor
|
||||||
|
* GPLv2 Open Source. Use is subject to license terms.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Bryan Biedenkapp, N2PLL
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @file PeerListWnd.h
|
||||||
|
* @ingroup peered
|
||||||
|
*/
|
||||||
|
#if !defined(__PEER_LIST_WND_H__)
|
||||||
|
#define __PEER_LIST_WND_H__
|
||||||
|
|
||||||
|
#include "common/Log.h"
|
||||||
|
|
||||||
|
#include "FDblDialog.h"
|
||||||
|
#include "PeerEdMainWnd.h"
|
||||||
|
#include "PeerEditWnd.h"
|
||||||
|
|
||||||
|
#include <final/final.h>
|
||||||
|
using namespace finalcut;
|
||||||
|
|
||||||
|
struct PrivateFListViewScrollToY { typedef void(FListView::*type)(int); };
|
||||||
|
template class HackTheGibson<PrivateFListViewScrollToY, &FListView::scrollToY>;
|
||||||
|
struct PrivateFListViewIteratorFirst { typedef FListViewIterator FListView::*type; };
|
||||||
|
template class HackTheGibson<PrivateFListViewIteratorFirst, &FListView::first_visible_line>;
|
||||||
|
struct PrivateFListViewVBarPtr { typedef FScrollbarPtr FListView::*type; };
|
||||||
|
template class HackTheGibson<PrivateFListViewVBarPtr, &FListView::vbar>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Constants
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#define PEER_LIST_WIDTH 74
|
||||||
|
#define PEER_LIST_HEIGHT 15
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Class Declaration
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This class implements the peer list window.
|
||||||
|
* @ingroup peered
|
||||||
|
*/
|
||||||
|
class HOST_SW_API PeerListWnd final : public FDblDialog {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initializes a new instance of the PeerListWnd class.
|
||||||
|
* @param widget
|
||||||
|
*/
|
||||||
|
explicit PeerListWnd(FWidget* widget = nullptr) : FDblDialog{widget}
|
||||||
|
{
|
||||||
|
/* stub */
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Copy constructor.
|
||||||
|
*/
|
||||||
|
PeerListWnd(const PeerListWnd&) = delete;
|
||||||
|
/**
|
||||||
|
* @brief Move constructor.
|
||||||
|
*/
|
||||||
|
PeerListWnd(PeerListWnd&&) noexcept = delete;
|
||||||
|
/**
|
||||||
|
* @brief Finalizes an instance of the PeerListWnd class.
|
||||||
|
*/
|
||||||
|
~PeerListWnd() noexcept override = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Disable copy assignment operator (=).
|
||||||
|
*/
|
||||||
|
auto operator= (const PeerListWnd&) -> PeerListWnd& = delete;
|
||||||
|
/**
|
||||||
|
* @brief Disable move assignment operator (=).
|
||||||
|
*/
|
||||||
|
auto operator= (PeerListWnd&&) noexcept -> PeerListWnd& = 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 = PeerId();
|
||||||
|
m_selectedPeerId = 0U;
|
||||||
|
|
||||||
|
auto entry = g_pidLookups->tableAsList()[0U];
|
||||||
|
m_selected = entry;
|
||||||
|
|
||||||
|
// bryanb: HACK -- use HackTheGibson to access the private current listview iterator to get the scroll position
|
||||||
|
/*
|
||||||
|
* This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence.
|
||||||
|
*/
|
||||||
|
int firstScrollLinePos = 0;
|
||||||
|
if (m_listView.getCount() > 0) {
|
||||||
|
firstScrollLinePos = (m_listView.*RTTIResult<PrivateFListViewIteratorFirst>::ptr).getPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_listView.clear();
|
||||||
|
for (auto entry : g_pidLookups->tableAsList()) {
|
||||||
|
// pad peer ID properly
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << std::setw(7) << std::setfill('0') << entry.peerId();
|
||||||
|
|
||||||
|
bool masterPassword = (entry.peerPassword().size() == 0U);
|
||||||
|
|
||||||
|
// build list view entry
|
||||||
|
const std::array<std::string, 5U> columns = {
|
||||||
|
oss.str(),
|
||||||
|
(masterPassword) ? "X" : "",
|
||||||
|
(entry.peerLink()) ? "X" : "",
|
||||||
|
(entry.canRequestKeys()) ? "X" : "",
|
||||||
|
entry.peerAlias()
|
||||||
|
};
|
||||||
|
|
||||||
|
const finalcut::FStringList line(columns.cbegin(), columns.cend());
|
||||||
|
m_listView.insert(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// bryanb: HACK -- use HackTheGibson to access the private set scroll Y to set the scroll position
|
||||||
|
/*
|
||||||
|
* This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence.
|
||||||
|
*/
|
||||||
|
if ((size_t)firstScrollLinePos > m_listView.getCount())
|
||||||
|
firstScrollLinePos = 0;
|
||||||
|
if (firstScrollLinePos > 0 && m_listView.getCount() > 0) {
|
||||||
|
(m_listView.*RTTIResult<PrivateFListViewScrollToY>::ptr)(firstScrollLinePos);
|
||||||
|
(m_listView.*RTTIResult<PrivateFListViewVBarPtr>::ptr)->setValue(firstScrollLinePos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate dialog title
|
||||||
|
uint32_t len = g_pidLookups->tableAsList().size();
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "Peer ID List (" << len << " Peers)";
|
||||||
|
FDialog::setText(ss.str());
|
||||||
|
|
||||||
|
setFocusWidget(&m_listView);
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
lookups::PeerId m_selected;
|
||||||
|
uint32_t m_selectedPeerId;
|
||||||
|
|
||||||
|
FListView m_listView{this};
|
||||||
|
|
||||||
|
FButton m_addPeer{"&Add", this};
|
||||||
|
FButton m_editPeer{"&Edit", this};
|
||||||
|
FLabel m_fileName{"/path/to/peer.dat", this};
|
||||||
|
FButton m_deletePeer{"&Delete", this};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes the window layout.
|
||||||
|
*/
|
||||||
|
void initLayout() override
|
||||||
|
{
|
||||||
|
FDialog::setMinimumSize(FSize{PEER_LIST_WIDTH, PEER_LIST_HEIGHT});
|
||||||
|
|
||||||
|
FDialog::setResizeable(false);
|
||||||
|
FDialog::setMinimizable(false);
|
||||||
|
FDialog::setTitlebarButtonVisibility(false);
|
||||||
|
FDialog::setModal(false);
|
||||||
|
|
||||||
|
FDialog::setText("Peer ID List");
|
||||||
|
|
||||||
|
initControls();
|
||||||
|
loadListView();
|
||||||
|
|
||||||
|
FDialog::initLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes window controls.
|
||||||
|
*/
|
||||||
|
void initControls()
|
||||||
|
{
|
||||||
|
m_addPeer.setGeometry(FPoint(2, int(getHeight() - 4)), FSize(9, 1));
|
||||||
|
m_addPeer.setBackgroundColor(FColor::DarkGreen);
|
||||||
|
m_addPeer.setFocusBackgroundColor(FColor::DarkGreen);
|
||||||
|
m_addPeer.addCallback("clicked", [&]() { addEntry(); });
|
||||||
|
|
||||||
|
m_editPeer.setGeometry(FPoint(13, int(getHeight() - 4)), FSize(10, 1));
|
||||||
|
m_editPeer.setDisable();
|
||||||
|
m_editPeer.addCallback("clicked", [&]() { editEntry(); });
|
||||||
|
|
||||||
|
m_fileName.setGeometry(FPoint(27, int(getHeight() - 4)), FSize(42, 1));
|
||||||
|
m_fileName.setText(g_iniFile);
|
||||||
|
|
||||||
|
m_deletePeer.setGeometry(FPoint(int(getWidth()) - 13, int(getHeight() - 4)), FSize(10, 1));
|
||||||
|
m_deletePeer.setDisable();
|
||||||
|
m_deletePeer.addCallback("clicked", [&]() { deleteEntry(); });
|
||||||
|
|
||||||
|
m_listView.setGeometry(FPoint{1, 1}, FSize{getWidth() - 1, getHeight() - 5});
|
||||||
|
|
||||||
|
// configure list view columns
|
||||||
|
m_listView.addColumn("Peer ID", 10);
|
||||||
|
m_listView.addColumn("Master Password", 16);
|
||||||
|
m_listView.addColumn("Peer Link", 12);
|
||||||
|
m_listView.addColumn("Can Request Keys", 12);
|
||||||
|
m_listView.addColumn("Alias", 40);
|
||||||
|
|
||||||
|
// set right alignment for peer ID
|
||||||
|
m_listView.setColumnAlignment(2, finalcut::Align::Center);
|
||||||
|
m_listView.setColumnAlignment(3, finalcut::Align::Center);
|
||||||
|
m_listView.setColumnAlignment(4, finalcut::Align::Center);
|
||||||
|
m_listView.setColumnAlignment(5, finalcut::Align::Left);
|
||||||
|
|
||||||
|
// set type of sorting
|
||||||
|
m_listView.setColumnSortType(1, finalcut::SortType::Name);
|
||||||
|
|
||||||
|
// sort by peer ID
|
||||||
|
m_listView.setColumnSort(1, finalcut::SortOrder::Ascending);
|
||||||
|
|
||||||
|
m_listView.addCallback("clicked", [&]() { editEntry(); });
|
||||||
|
m_listView.addCallback("row-changed", [&]() {
|
||||||
|
FListViewItem* curItem = m_listView.getCurrentItem();
|
||||||
|
if (curItem != nullptr) {
|
||||||
|
FString strPeerId = curItem->getText(1);
|
||||||
|
uint32_t peerId = ::atoi(strPeerId.c_str());
|
||||||
|
|
||||||
|
if (peerId != m_selectedPeerId) {
|
||||||
|
auto entry = g_pidLookups->find(peerId);
|
||||||
|
if (!entry.peerDefault()) {
|
||||||
|
m_selected = entry;
|
||||||
|
|
||||||
|
m_selectedPeerId = peerId;
|
||||||
|
|
||||||
|
m_editPeer.setEnable();
|
||||||
|
m_deletePeer.setEnable();
|
||||||
|
m_deletePeer.setBackgroundColor(FColor::DarkRed);
|
||||||
|
m_deletePeer.setFocusBackgroundColor(FColor::DarkRed);
|
||||||
|
} else {
|
||||||
|
m_editPeer.setDisable();
|
||||||
|
m_deletePeer.setDisable();
|
||||||
|
m_deletePeer.resetColors();
|
||||||
|
}
|
||||||
|
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setFocusWidget(&m_listView);
|
||||||
|
redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*/
|
||||||
|
void addEntry()
|
||||||
|
{
|
||||||
|
this->lowerWindow();
|
||||||
|
this->deactivateWindow();
|
||||||
|
|
||||||
|
PeerEditWnd wnd{PeerId(), this};
|
||||||
|
wnd.show();
|
||||||
|
|
||||||
|
this->raiseWindow();
|
||||||
|
this->activateWindow();
|
||||||
|
|
||||||
|
loadListView();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*/
|
||||||
|
void editEntry()
|
||||||
|
{
|
||||||
|
if (m_selected.peerDefault())
|
||||||
|
return;
|
||||||
|
|
||||||
|
this->lowerWindow();
|
||||||
|
this->deactivateWindow();
|
||||||
|
|
||||||
|
PeerEditWnd wnd{m_selected, this};
|
||||||
|
wnd.show();
|
||||||
|
|
||||||
|
this->raiseWindow();
|
||||||
|
this->activateWindow();
|
||||||
|
|
||||||
|
loadListView();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*/
|
||||||
|
void deleteEntry()
|
||||||
|
{
|
||||||
|
if (m_selected.peerDefault())
|
||||||
|
return;
|
||||||
|
|
||||||
|
LogMessage(LOG_HOST, "Deleting peer ID %s (%u)", m_selected.peerAlias().c_str(), m_selected.peerId());
|
||||||
|
g_pidLookups->eraseEntry(m_selected.peerId());
|
||||||
|
|
||||||
|
// bryanb: HACK -- use HackTheGibson to access the private current listview iterator to get the scroll position
|
||||||
|
/*
|
||||||
|
* This uses the RTTI hack to access private members on FListView; and this code *could* break as a consequence.
|
||||||
|
*/
|
||||||
|
int firstScrollLinePos = 0;
|
||||||
|
if (m_listView.getCount() > 0) {
|
||||||
|
firstScrollLinePos = (m_listView.*RTTIResult<PrivateFListViewIteratorFirst>::ptr).getPosition();
|
||||||
|
}
|
||||||
|
if ((size_t)firstScrollLinePos > m_listView.getCount())
|
||||||
|
firstScrollLinePos = 0;
|
||||||
|
if (firstScrollLinePos > 0 && m_listView.getCount() > 0) {
|
||||||
|
--firstScrollLinePos;
|
||||||
|
(m_listView.*RTTIResult<PrivateFListViewScrollToY>::ptr)(firstScrollLinePos);
|
||||||
|
(m_listView.*RTTIResult<PrivateFListViewVBarPtr>::ptr)->setValue(firstScrollLinePos);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadListView();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
*/
|
||||||
|
void drawBorder() override
|
||||||
|
{
|
||||||
|
if (!hasBorder())
|
||||||
|
return;
|
||||||
|
|
||||||
|
setColor();
|
||||||
|
|
||||||
|
FRect box{{1, 2}, getSize()};
|
||||||
|
box.scaleBy(0, -1);
|
||||||
|
|
||||||
|
FRect rect = box;
|
||||||
|
if (rect.x1_ref() > rect.x2_ref())
|
||||||
|
std::swap(rect.x1_ref(), rect.x2_ref());
|
||||||
|
|
||||||
|
if (rect.y1_ref() > rect.y2_ref())
|
||||||
|
std::swap(rect.y1_ref(), rect.y2_ref());
|
||||||
|
|
||||||
|
rect.x1_ref() = std::max(rect.x1_ref(), 1);
|
||||||
|
rect.y1_ref() = std::max(rect.y1_ref(), 1);
|
||||||
|
rect.x2_ref() = std::min(rect.x2_ref(), rect.x1_ref() + int(getWidth()) - 1);
|
||||||
|
rect.y2_ref() = std::min(rect.y2_ref(), rect.y1_ref() + int(getHeight()) - 1);
|
||||||
|
|
||||||
|
if (box.getWidth() < 3)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Use box-drawing characters to draw a border
|
||||||
|
constexpr std::array<wchar_t, 8> box_char
|
||||||
|
{{
|
||||||
|
static_cast<wchar_t>(0x2554), // ╔
|
||||||
|
static_cast<wchar_t>(0x2550), // ═
|
||||||
|
static_cast<wchar_t>(0x2557), // ╗
|
||||||
|
static_cast<wchar_t>(0x2551), // ║
|
||||||
|
static_cast<wchar_t>(0x2551), // ║
|
||||||
|
static_cast<wchar_t>(0x255A), // ╚
|
||||||
|
static_cast<wchar_t>(0x2550), // ═
|
||||||
|
static_cast<wchar_t>(0x255D) // ╝
|
||||||
|
}};
|
||||||
|
|
||||||
|
drawGenericBox(this, box, box_char);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** 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 // __PEER_LIST_WND_H__
|
||||||
Loading…
Reference in new issue