mirror of
https://github.com/AlexandreRouma/SDRPlusPlus.git
synced 2025-01-28 18:44:43 +01:00
866 lines
35 KiB
C++
866 lines
35 KiB
C++
#include <imgui.h>
|
|
#include <spdlog/spdlog.h>
|
|
#include <module.h>
|
|
#include <gui/gui.h>
|
|
#include <gui/style.h>
|
|
#include <core.h>
|
|
#include <thread>
|
|
#include <radio_interface.h>
|
|
#include <signal_path/signal_path.h>
|
|
#include <vector>
|
|
#include <gui/tuner.h>
|
|
#include <gui/file_dialogs.h>
|
|
#include <utils/freq_formatting.h>
|
|
#include <gui/dialogs/dialog_box.h>
|
|
#include <fstream>
|
|
|
|
SDRPP_MOD_INFO{
|
|
/* Name: */ "frequency_manager",
|
|
/* Description: */ "Frequency manager module for SDR++",
|
|
/* Author: */ "Ryzerth;Zimm",
|
|
/* Version: */ 0, 3, 0,
|
|
/* Max instances */ 1
|
|
};
|
|
|
|
struct FrequencyBookmark {
|
|
double frequency;
|
|
double bandwidth;
|
|
int mode;
|
|
bool selected;
|
|
};
|
|
|
|
struct WaterfallBookmark {
|
|
std::string listName;
|
|
std::string bookmarkName;
|
|
FrequencyBookmark bookmark;
|
|
};
|
|
|
|
ConfigManager config;
|
|
|
|
const char* demodModeList[] = {
|
|
"NFM",
|
|
"WFM",
|
|
"AM",
|
|
"DSB",
|
|
"USB",
|
|
"CW",
|
|
"LSB",
|
|
"RAW"
|
|
};
|
|
|
|
const char* demodModeListTxt = "NFM\0WFM\0AM\0DSB\0USB\0CW\0LSB\0RAW\0";
|
|
|
|
enum {
|
|
BOOKMARK_DISP_MODE_OFF,
|
|
BOOKMARK_DISP_MODE_TOP,
|
|
BOOKMARK_DISP_MODE_BOTTOM,
|
|
_BOOKMARK_DISP_MODE_COUNT
|
|
};
|
|
|
|
const char* bookmarkDisplayModesTxt = "Off\0Top\0Bottom\0";
|
|
|
|
class FrequencyManagerModule : public ModuleManager::Instance {
|
|
public:
|
|
FrequencyManagerModule(std::string name) {
|
|
this->name = name;
|
|
|
|
config.acquire();
|
|
std::string selList = config.conf["selectedList"];
|
|
bookmarkDisplayMode = config.conf["bookmarkDisplayMode"];
|
|
config.release();
|
|
|
|
refreshLists();
|
|
loadByName(selList);
|
|
refreshWaterfallBookmarks();
|
|
|
|
fftRedrawHandler.ctx = this;
|
|
fftRedrawHandler.handler = fftRedraw;
|
|
inputHandler.ctx = this;
|
|
inputHandler.handler = fftInput;
|
|
|
|
gui::menu.registerEntry(name, menuHandler, this, NULL);
|
|
gui::waterfall.onFFTRedraw.bindHandler(&fftRedrawHandler);
|
|
gui::waterfall.onInputProcess.bindHandler(&inputHandler);
|
|
}
|
|
|
|
~FrequencyManagerModule() {
|
|
gui::menu.removeEntry(name);
|
|
gui::waterfall.onFFTRedraw.unbindHandler(&fftRedrawHandler);
|
|
gui::waterfall.onInputProcess.unbindHandler(&inputHandler);
|
|
}
|
|
|
|
void postInit() {}
|
|
|
|
void enable() {
|
|
enabled = true;
|
|
}
|
|
|
|
void disable() {
|
|
enabled = false;
|
|
}
|
|
|
|
bool isEnabled() {
|
|
return enabled;
|
|
}
|
|
|
|
private:
|
|
static void applyBookmark(FrequencyBookmark bm, std::string vfoName) {
|
|
if (vfoName == "") {
|
|
// TODO: Replace with proper tune call
|
|
gui::waterfall.setCenterFrequency(bm.frequency);
|
|
gui::waterfall.centerFreqMoved = true;
|
|
}
|
|
else {
|
|
if (core::modComManager.interfaceExists(vfoName)) {
|
|
if (core::modComManager.getModuleName(vfoName) == "radio") {
|
|
int mode = bm.mode;
|
|
float bandwidth = bm.bandwidth;
|
|
core::modComManager.callInterface(vfoName, RADIO_IFACE_CMD_SET_MODE, &mode, NULL);
|
|
core::modComManager.callInterface(vfoName, RADIO_IFACE_CMD_SET_BANDWIDTH, &bandwidth, NULL);
|
|
}
|
|
}
|
|
tuner::tune(tuner::TUNER_MODE_NORMAL, vfoName, bm.frequency);
|
|
}
|
|
}
|
|
|
|
bool bookmarkEditDialog() {
|
|
bool open = true;
|
|
gui::mainWindow.lockWaterfallControls = true;
|
|
|
|
std::string id = "Edit##freq_manager_edit_popup_" + name;
|
|
ImGui::OpenPopup(id.c_str());
|
|
|
|
char nameBuf[1024];
|
|
strcpy(nameBuf, editedBookmarkName.c_str());
|
|
|
|
if (ImGui::BeginPopup(id.c_str(), ImGuiWindowFlags_NoResize)) {
|
|
ImGui::BeginTable(("freq_manager_edit_table" + name).c_str(), 2);
|
|
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::LeftLabel("Name");
|
|
ImGui::TableSetColumnIndex(1);
|
|
ImGui::SetNextItemWidth(200);
|
|
if (ImGui::InputText(("##freq_manager_edit_name" + name).c_str(), nameBuf, 1023)) {
|
|
editedBookmarkName = nameBuf;
|
|
}
|
|
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::LeftLabel("Frequency");
|
|
ImGui::TableSetColumnIndex(1);
|
|
ImGui::SetNextItemWidth(200);
|
|
ImGui::InputDouble(("##freq_manager_edit_freq" + name).c_str(), &editedBookmark.frequency);
|
|
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::LeftLabel("Bandwidth");
|
|
ImGui::TableSetColumnIndex(1);
|
|
ImGui::SetNextItemWidth(200);
|
|
ImGui::InputDouble(("##freq_manager_edit_bw" + name).c_str(), &editedBookmark.bandwidth);
|
|
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImGui::LeftLabel("Mode");
|
|
ImGui::TableSetColumnIndex(1);
|
|
ImGui::SetNextItemWidth(200);
|
|
|
|
ImGui::Combo(("##freq_manager_edit_mode" + name).c_str(), &editedBookmark.mode, demodModeListTxt);
|
|
|
|
ImGui::EndTable();
|
|
|
|
bool applyDisabled = (strlen(nameBuf) == 0) || (bookmarks.find(editedBookmarkName) != bookmarks.end() && editedBookmarkName != firstEditedBookmarkName);
|
|
if (applyDisabled) { style::beginDisabled(); }
|
|
if (ImGui::Button("Apply")) {
|
|
open = false;
|
|
|
|
// If editing, delete the original one
|
|
if (editOpen) {
|
|
bookmarks.erase(firstEditedBookmarkName);
|
|
}
|
|
bookmarks[editedBookmarkName] = editedBookmark;
|
|
|
|
saveByName(selectedListName);
|
|
}
|
|
if (applyDisabled) { style::endDisabled(); }
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel")) {
|
|
open = false;
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
return open;
|
|
}
|
|
|
|
bool newListDialog() {
|
|
bool open = true;
|
|
gui::mainWindow.lockWaterfallControls = true;
|
|
|
|
float menuWidth = ImGui::GetContentRegionAvail().x;
|
|
|
|
std::string id = "New##freq_manager_new_popup_" + name;
|
|
ImGui::OpenPopup(id.c_str());
|
|
|
|
char nameBuf[1024];
|
|
strcpy(nameBuf, editedListName.c_str());
|
|
|
|
if (ImGui::BeginPopup(id.c_str(), ImGuiWindowFlags_NoResize)) {
|
|
ImGui::LeftLabel("Name");
|
|
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
|
if (ImGui::InputText(("##freq_manager_edit_name" + name).c_str(), nameBuf, 1023)) {
|
|
editedListName = nameBuf;
|
|
}
|
|
|
|
bool alreadyExists = (std::find(listNames.begin(), listNames.end(), editedListName) != listNames.end());
|
|
|
|
if (strlen(nameBuf) == 0 || alreadyExists) { style::beginDisabled(); }
|
|
if (ImGui::Button("Apply")) {
|
|
open = false;
|
|
|
|
config.acquire();
|
|
if (renameListOpen) {
|
|
config.conf["lists"][editedListName] = config.conf["lists"][firstEditedListName];
|
|
config.conf["lists"].erase(firstEditedListName);
|
|
}
|
|
else {
|
|
config.conf["lists"][editedListName]["showOnWaterfall"] = true;
|
|
config.conf["lists"][editedListName]["bookmarks"] = json::object();
|
|
}
|
|
refreshWaterfallBookmarks(false);
|
|
config.release(true);
|
|
refreshLists();
|
|
loadByName(editedListName);
|
|
}
|
|
if (strlen(nameBuf) == 0 || alreadyExists) { style::endDisabled(); }
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Cancel")) {
|
|
open = false;
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
return open;
|
|
}
|
|
|
|
bool selectListsDialog() {
|
|
gui::mainWindow.lockWaterfallControls = true;
|
|
|
|
float menuWidth = ImGui::GetContentRegionAvail().x;
|
|
|
|
std::string id = "Select lists##freq_manager_sel_popup_" + name;
|
|
ImGui::OpenPopup(id.c_str());
|
|
|
|
bool open = true;
|
|
|
|
if (ImGui::BeginPopup(id.c_str(), ImGuiWindowFlags_NoResize)) {
|
|
// No need to lock config since we're not modifying anything and there's only one instance
|
|
for (auto [listName, list] : config.conf["lists"].items()) {
|
|
bool shown = list["showOnWaterfall"];
|
|
if (ImGui::Checkbox((listName + "##freq_manager_sel_list_").c_str(), &shown)) {
|
|
config.acquire();
|
|
config.conf["lists"][listName]["showOnWaterfall"] = shown;
|
|
refreshWaterfallBookmarks(false);
|
|
config.release(true);
|
|
}
|
|
}
|
|
|
|
if (ImGui::Button("Ok")) {
|
|
open = false;
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
return open;
|
|
}
|
|
|
|
void refreshLists() {
|
|
listNames.clear();
|
|
listNamesTxt = "";
|
|
|
|
config.acquire();
|
|
for (auto [_name, list] : config.conf["lists"].items()) {
|
|
listNames.push_back(_name);
|
|
listNamesTxt += _name;
|
|
listNamesTxt += '\0';
|
|
}
|
|
config.release();
|
|
}
|
|
|
|
void refreshWaterfallBookmarks(bool lockConfig = true) {
|
|
if (lockConfig) { config.acquire(); }
|
|
waterfallBookmarks.clear();
|
|
for (auto [listName, list] : config.conf["lists"].items()) {
|
|
if (!((bool)list["showOnWaterfall"])) { continue; }
|
|
WaterfallBookmark wbm;
|
|
wbm.listName = listName;
|
|
for (auto [bookmarkName, bm] : config.conf["lists"][listName]["bookmarks"].items()) {
|
|
wbm.bookmarkName = bookmarkName;
|
|
wbm.bookmark.frequency = config.conf["lists"][listName]["bookmarks"][bookmarkName]["frequency"];
|
|
wbm.bookmark.bandwidth = config.conf["lists"][listName]["bookmarks"][bookmarkName]["bandwidth"];
|
|
wbm.bookmark.mode = config.conf["lists"][listName]["bookmarks"][bookmarkName]["mode"];
|
|
wbm.bookmark.selected = false;
|
|
waterfallBookmarks.push_back(wbm);
|
|
}
|
|
}
|
|
if (lockConfig) { config.release(); }
|
|
}
|
|
|
|
void loadFirst() {
|
|
if (listNames.size() > 0) {
|
|
loadByName(listNames[0]);
|
|
return;
|
|
}
|
|
selectedListName = "";
|
|
selectedListId = 0;
|
|
}
|
|
|
|
void loadByName(std::string listName) {
|
|
bookmarks.clear();
|
|
if (std::find(listNames.begin(), listNames.end(), listName) == listNames.end()) {
|
|
selectedListName = "";
|
|
selectedListId = 0;
|
|
loadFirst();
|
|
return;
|
|
}
|
|
selectedListId = std::distance(listNames.begin(), std::find(listNames.begin(), listNames.end(), listName));
|
|
selectedListName = listName;
|
|
config.acquire();
|
|
for (auto [bmName, bm] : config.conf["lists"][listName]["bookmarks"].items()) {
|
|
FrequencyBookmark fbm;
|
|
fbm.frequency = bm["frequency"];
|
|
fbm.bandwidth = bm["bandwidth"];
|
|
fbm.mode = bm["mode"];
|
|
fbm.selected = false;
|
|
bookmarks[bmName] = fbm;
|
|
}
|
|
config.release();
|
|
}
|
|
|
|
void saveByName(std::string listName) {
|
|
config.acquire();
|
|
config.conf["lists"][listName]["bookmarks"] = json::object();
|
|
for (auto [bmName, bm] : bookmarks) {
|
|
config.conf["lists"][listName]["bookmarks"][bmName]["frequency"] = bm.frequency;
|
|
config.conf["lists"][listName]["bookmarks"][bmName]["bandwidth"] = bm.bandwidth;
|
|
config.conf["lists"][listName]["bookmarks"][bmName]["mode"] = bm.mode;
|
|
}
|
|
refreshWaterfallBookmarks(false);
|
|
config.release(true);
|
|
}
|
|
|
|
static void menuHandler(void* ctx) {
|
|
FrequencyManagerModule* _this = (FrequencyManagerModule*)ctx;
|
|
float menuWidth = ImGui::GetContentRegionAvail().x;
|
|
|
|
// TODO: Replace with something that won't iterate every frame
|
|
std::vector<std::string> selectedNames;
|
|
for (auto& [name, bm] : _this->bookmarks) {
|
|
if (bm.selected) { selectedNames.push_back(name); }
|
|
}
|
|
|
|
float lineHeight = ImGui::GetTextLineHeightWithSpacing();
|
|
|
|
float btnSize = ImGui::CalcTextSize("Rename").x + 8;
|
|
ImGui::SetNextItemWidth(menuWidth - 24 - (2 * lineHeight) - btnSize);
|
|
if (ImGui::Combo(("##freq_manager_list_sel" + _this->name).c_str(), &_this->selectedListId, _this->listNamesTxt.c_str())) {
|
|
_this->loadByName(_this->listNames[_this->selectedListId]);
|
|
config.acquire();
|
|
config.conf["selectedList"] = _this->selectedListName;
|
|
config.release(true);
|
|
}
|
|
ImGui::SameLine();
|
|
if (_this->listNames.size() == 0) { style::beginDisabled(); }
|
|
if (ImGui::Button(("Rename##_freq_mgr_ren_lst_" + _this->name).c_str(), ImVec2(btnSize, 0))) {
|
|
_this->firstEditedListName = _this->listNames[_this->selectedListId];
|
|
_this->editedListName = _this->firstEditedListName;
|
|
_this->renameListOpen = true;
|
|
}
|
|
if (_this->listNames.size() == 0) { style::endDisabled(); }
|
|
ImGui::SameLine();
|
|
if (ImGui::Button(("+##_freq_mgr_add_lst_" + _this->name).c_str(), ImVec2(lineHeight, 0))) {
|
|
// Find new unique default name
|
|
if (std::find(_this->listNames.begin(), _this->listNames.end(), "New List") == _this->listNames.end()) {
|
|
_this->editedListName = "New List";
|
|
}
|
|
else {
|
|
char buf[64];
|
|
for (int i = 1; i < 1000; i++) {
|
|
sprintf(buf, "New List (%d)", i);
|
|
if (std::find(_this->listNames.begin(), _this->listNames.end(), buf) == _this->listNames.end()) { break; }
|
|
}
|
|
_this->editedListName = buf;
|
|
}
|
|
_this->newListOpen = true;
|
|
}
|
|
ImGui::SameLine();
|
|
if (_this->selectedListName == "") { style::beginDisabled(); }
|
|
if (ImGui::Button(("-##_freq_mgr_del_lst_" + _this->name).c_str(), ImVec2(lineHeight, 0))) {
|
|
_this->deleteListOpen = true;
|
|
}
|
|
if (_this->selectedListName == "") { style::endDisabled(); }
|
|
|
|
// List delete confirmation
|
|
if (ImGui::GenericDialog(("freq_manager_del_list_confirm" + _this->name).c_str(), _this->deleteListOpen, GENERIC_DIALOG_BUTTONS_YES_NO, [_this]() {
|
|
ImGui::Text("Deleting list named \"%s\". Are you sure?", _this->selectedListName.c_str());
|
|
}) == GENERIC_DIALOG_BUTTON_YES) {
|
|
config.acquire();
|
|
config.conf["lists"].erase(_this->selectedListName);
|
|
_this->refreshWaterfallBookmarks(false);
|
|
config.release(true);
|
|
_this->refreshLists();
|
|
_this->selectedListId = std::clamp<int>(_this->selectedListId, 0, _this->listNames.size());
|
|
if (_this->listNames.size() > 0) {
|
|
_this->loadByName(_this->listNames[_this->selectedListId]);
|
|
}
|
|
else {
|
|
_this->selectedListName = "";
|
|
}
|
|
}
|
|
|
|
if (_this->selectedListName == "") { style::beginDisabled(); }
|
|
//Draw buttons on top of the list
|
|
ImGui::BeginTable(("freq_manager_btn_table" + _this->name).c_str(), 3);
|
|
ImGui::TableNextRow();
|
|
|
|
ImGui::TableSetColumnIndex(0);
|
|
if (ImGui::Button(("Add##_freq_mgr_add_" + _this->name).c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
|
|
// If there's no VFO selected, just save the center freq
|
|
if (gui::waterfall.selectedVFO == "") {
|
|
_this->editedBookmark.frequency = gui::waterfall.getCenterFrequency();
|
|
_this->editedBookmark.bandwidth = 0;
|
|
_this->editedBookmark.mode = 7;
|
|
}
|
|
else {
|
|
_this->editedBookmark.frequency = gui::waterfall.getCenterFrequency() + sigpath::vfoManager.getOffset(gui::waterfall.selectedVFO);
|
|
_this->editedBookmark.bandwidth = sigpath::vfoManager.getBandwidth(gui::waterfall.selectedVFO);
|
|
_this->editedBookmark.mode = 7;
|
|
if (core::modComManager.getModuleName(gui::waterfall.selectedVFO) == "radio") {
|
|
int mode;
|
|
core::modComManager.callInterface(gui::waterfall.selectedVFO, RADIO_IFACE_CMD_GET_MODE, NULL, &mode);
|
|
_this->editedBookmark.mode = mode;
|
|
}
|
|
}
|
|
|
|
_this->editedBookmark.selected = false;
|
|
|
|
_this->createOpen = true;
|
|
|
|
// Find new unique default name
|
|
if (_this->bookmarks.find("New Bookmark") == _this->bookmarks.end()) {
|
|
_this->editedBookmarkName = "New Bookmark";
|
|
}
|
|
else {
|
|
char buf[64];
|
|
for (int i = 1; i < 1000; i++) {
|
|
sprintf(buf, "New Bookmark (%d)", i);
|
|
if (_this->bookmarks.find(buf) == _this->bookmarks.end()) { break; }
|
|
}
|
|
_this->editedBookmarkName = buf;
|
|
}
|
|
}
|
|
|
|
ImGui::TableSetColumnIndex(1);
|
|
if (selectedNames.size() == 0 && _this->selectedListName != "") { style::beginDisabled(); }
|
|
if (ImGui::Button(("Remove##_freq_mgr_rem_" + _this->name).c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
|
|
_this->deleteBookmarksOpen = true;
|
|
}
|
|
if (selectedNames.size() == 0 && _this->selectedListName != "") { style::endDisabled(); }
|
|
ImGui::TableSetColumnIndex(2);
|
|
if (selectedNames.size() != 1 && _this->selectedListName != "") { style::beginDisabled(); }
|
|
if (ImGui::Button(("Edit##_freq_mgr_edt_" + _this->name).c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
|
|
_this->editOpen = true;
|
|
_this->editedBookmark = _this->bookmarks[selectedNames[0]];
|
|
_this->editedBookmarkName = selectedNames[0];
|
|
_this->firstEditedBookmarkName = selectedNames[0];
|
|
}
|
|
if (selectedNames.size() != 1 && _this->selectedListName != "") { style::endDisabled(); }
|
|
|
|
ImGui::EndTable();
|
|
|
|
// Bookmark delete confirm dialog
|
|
// List delete confirmation
|
|
if (ImGui::GenericDialog(("freq_manager_del_list_confirm" + _this->name).c_str(), _this->deleteBookmarksOpen, GENERIC_DIALOG_BUTTONS_YES_NO, [_this]() {
|
|
ImGui::TextUnformatted("Deleting selected bookmaks. Are you sure?");
|
|
}) == GENERIC_DIALOG_BUTTON_YES) {
|
|
for (auto& _name : selectedNames) { _this->bookmarks.erase(_name); }
|
|
_this->saveByName(_this->selectedListName);
|
|
}
|
|
|
|
// Bookmark list
|
|
if (ImGui::BeginTable(("freq_manager_bkm_table" + _this->name).c_str(), 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, 200))) {
|
|
ImGui::TableSetupColumn("Name");
|
|
ImGui::TableSetupColumn("Bookmark");
|
|
ImGui::TableSetupScrollFreeze(2, 1);
|
|
ImGui::TableHeadersRow();
|
|
for (auto& [name, bm] : _this->bookmarks) {
|
|
ImGui::TableNextRow();
|
|
ImGui::TableSetColumnIndex(0);
|
|
ImVec2 min = ImGui::GetCursorPos();
|
|
|
|
if (ImGui::Selectable((name + "##_freq_mgr_bkm_name_" + _this->name).c_str(), &bm.selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SelectOnClick)) {
|
|
// if shift or control isn't pressed, deselect all others
|
|
if (!ImGui::GetIO().KeyShift && !ImGui::GetIO().KeyCtrl) {
|
|
for (auto& [_name, _bm] : _this->bookmarks) {
|
|
if (name == _name) { continue; }
|
|
_bm.selected = false;
|
|
}
|
|
}
|
|
}
|
|
if (ImGui::TableGetHoveredColumn() >= 0 && ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
|
applyBookmark(bm, gui::waterfall.selectedVFO);
|
|
}
|
|
|
|
ImGui::TableSetColumnIndex(1);
|
|
ImGui::Text("%s %s", utils::formatFreq(bm.frequency).c_str(), demodModeList[bm.mode]);
|
|
ImVec2 max = ImGui::GetCursorPos();
|
|
}
|
|
ImGui::EndTable();
|
|
}
|
|
|
|
|
|
if (selectedNames.size() != 1 && _this->selectedListName != "") { style::beginDisabled(); }
|
|
if (ImGui::Button(("Apply##_freq_mgr_apply_" + _this->name).c_str(), ImVec2(menuWidth, 0))) {
|
|
FrequencyBookmark& bm = _this->bookmarks[selectedNames[0]];
|
|
applyBookmark(bm, gui::waterfall.selectedVFO);
|
|
bm.selected = false;
|
|
}
|
|
if (selectedNames.size() != 1 && _this->selectedListName != "") { style::endDisabled(); }
|
|
|
|
//Draw import and export buttons
|
|
ImGui::BeginTable(("freq_manager_bottom_btn_table" + _this->name).c_str(), 2);
|
|
ImGui::TableNextRow();
|
|
|
|
ImGui::TableSetColumnIndex(0);
|
|
if (ImGui::Button(("Import##_freq_mgr_imp_" + _this->name).c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0)) && !_this->importOpen) {
|
|
_this->importOpen = true;
|
|
_this->importDialog = new pfd::open_file("Import bookmarks", "", { "JSON Files (*.json)", "*.json", "All Files", "*" }, true);
|
|
}
|
|
|
|
ImGui::TableSetColumnIndex(1);
|
|
if (selectedNames.size() == 0 && _this->selectedListName != "") { style::beginDisabled(); }
|
|
if (ImGui::Button(("Export##_freq_mgr_exp_" + _this->name).c_str(), ImVec2(ImGui::GetContentRegionAvail().x, 0)) && !_this->exportOpen) {
|
|
_this->exportedBookmarks = json::object();
|
|
config.acquire();
|
|
for (auto& _name : selectedNames) {
|
|
_this->exportedBookmarks["bookmarks"][_name] = config.conf["lists"][_this->selectedListName]["bookmarks"][_name];
|
|
}
|
|
config.release();
|
|
_this->exportOpen = true;
|
|
_this->exportDialog = new pfd::save_file("Export bookmarks", "", { "JSON Files (*.json)", "*.json", "All Files", "*" }, true);
|
|
}
|
|
if (selectedNames.size() == 0 && _this->selectedListName != "") { style::endDisabled(); }
|
|
ImGui::EndTable();
|
|
|
|
if (ImGui::Button(("Select displayed lists##_freq_mgr_exp_" + _this->name).c_str(), ImVec2(menuWidth, 0))) {
|
|
_this->selectListsOpen = true;
|
|
}
|
|
|
|
ImGui::LeftLabel("Bookmark display mode");
|
|
ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX());
|
|
if (ImGui::Combo(("##_freq_mgr_dms_" + _this->name).c_str(), &_this->bookmarkDisplayMode, bookmarkDisplayModesTxt)) {
|
|
config.acquire();
|
|
config.conf["bookmarkDisplayMode"] = _this->bookmarkDisplayMode;
|
|
config.release(true);
|
|
}
|
|
|
|
if (_this->selectedListName == "") { style::endDisabled(); }
|
|
|
|
if (_this->createOpen) {
|
|
_this->createOpen = _this->bookmarkEditDialog();
|
|
}
|
|
|
|
if (_this->editOpen) {
|
|
_this->editOpen = _this->bookmarkEditDialog();
|
|
}
|
|
|
|
if (_this->newListOpen) {
|
|
_this->newListOpen = _this->newListDialog();
|
|
}
|
|
|
|
if (_this->renameListOpen) {
|
|
_this->renameListOpen = _this->newListDialog();
|
|
}
|
|
|
|
if (_this->selectListsOpen) {
|
|
_this->selectListsOpen = _this->selectListsDialog();
|
|
}
|
|
|
|
// Handle import and export
|
|
if (_this->importOpen && _this->importDialog->ready()) {
|
|
_this->importOpen = false;
|
|
std::vector<std::string> paths = _this->importDialog->result();
|
|
if (paths.size() > 0 && _this->listNames.size() > 0) {
|
|
_this->importBookmarks(paths[0]);
|
|
}
|
|
delete _this->importDialog;
|
|
}
|
|
if (_this->exportOpen && _this->exportDialog->ready()) {
|
|
_this->exportOpen = false;
|
|
std::string path = _this->exportDialog->result();
|
|
if (path != "") {
|
|
_this->exportBookmarks(path);
|
|
}
|
|
delete _this->exportDialog;
|
|
}
|
|
}
|
|
|
|
static void fftRedraw(ImGui::WaterFall::FFTRedrawArgs args, void* ctx) {
|
|
FrequencyManagerModule* _this = (FrequencyManagerModule*)ctx;
|
|
if (_this->bookmarkDisplayMode == BOOKMARK_DISP_MODE_OFF) { return; }
|
|
|
|
if (_this->bookmarkDisplayMode == BOOKMARK_DISP_MODE_TOP) {
|
|
for (auto const bm : _this->waterfallBookmarks) {
|
|
double centerXpos = args.min.x + std::round((bm.bookmark.frequency - args.lowFreq) * args.freqToPixelRatio);
|
|
|
|
if (bm.bookmark.frequency >= args.lowFreq && bm.bookmark.frequency <= args.highFreq) {
|
|
args.window->DrawList->AddLine(ImVec2(centerXpos, args.min.y), ImVec2(centerXpos, args.max.y), IM_COL32(255, 255, 0, 255));
|
|
}
|
|
|
|
ImVec2 nameSize = ImGui::CalcTextSize(bm.bookmarkName.c_str());
|
|
ImVec2 rectMin = ImVec2(centerXpos - (nameSize.x / 2) - 5, args.min.y);
|
|
ImVec2 rectMax = ImVec2(centerXpos + (nameSize.x / 2) + 5, args.min.y + nameSize.y);
|
|
ImVec2 clampedRectMin = ImVec2(std::clamp<double>(rectMin.x, args.min.x, args.max.x), rectMin.y);
|
|
ImVec2 clampedRectMax = ImVec2(std::clamp<double>(rectMax.x, args.min.x, args.max.x), rectMax.y);
|
|
|
|
if (clampedRectMax.x - clampedRectMin.x > 0) {
|
|
args.window->DrawList->AddRectFilled(clampedRectMin, clampedRectMax, IM_COL32(255, 255, 0, 255));
|
|
}
|
|
if (rectMin.x >= args.min.x && rectMax.x <= args.max.x) {
|
|
args.window->DrawList->AddText(ImVec2(centerXpos - (nameSize.x / 2), args.min.y), IM_COL32(0, 0, 0, 255), bm.bookmarkName.c_str());
|
|
}
|
|
}
|
|
}
|
|
else if (_this->bookmarkDisplayMode == BOOKMARK_DISP_MODE_BOTTOM) {
|
|
for (auto const bm : _this->waterfallBookmarks) {
|
|
double centerXpos = args.min.x + std::round((bm.bookmark.frequency - args.lowFreq) * args.freqToPixelRatio);
|
|
|
|
if (bm.bookmark.frequency >= args.lowFreq && bm.bookmark.frequency <= args.highFreq) {
|
|
args.window->DrawList->AddLine(ImVec2(centerXpos, args.min.y), ImVec2(centerXpos, args.max.y), IM_COL32(255, 255, 0, 255));
|
|
}
|
|
|
|
ImVec2 nameSize = ImGui::CalcTextSize(bm.bookmarkName.c_str());
|
|
ImVec2 rectMin = ImVec2(centerXpos - (nameSize.x / 2) - 5, args.max.y - nameSize.y);
|
|
ImVec2 rectMax = ImVec2(centerXpos + (nameSize.x / 2) + 5, args.max.y);
|
|
ImVec2 clampedRectMin = ImVec2(std::clamp<double>(rectMin.x, args.min.x, args.max.x), rectMin.y);
|
|
ImVec2 clampedRectMax = ImVec2(std::clamp<double>(rectMax.x, args.min.x, args.max.x), rectMax.y);
|
|
|
|
if (clampedRectMax.x - clampedRectMin.x > 0) {
|
|
args.window->DrawList->AddRectFilled(clampedRectMin, clampedRectMax, IM_COL32(255, 255, 0, 255));
|
|
}
|
|
if (rectMin.x >= args.min.x && rectMax.x <= args.max.x) {
|
|
args.window->DrawList->AddText(ImVec2(centerXpos - (nameSize.x / 2), args.max.y - nameSize.y), IM_COL32(0, 0, 0, 255), bm.bookmarkName.c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool mouseAlreadyDown = false;
|
|
bool mouseClickedInLabel = false;
|
|
static void fftInput(ImGui::WaterFall::InputHandlerArgs args, void* ctx) {
|
|
FrequencyManagerModule* _this = (FrequencyManagerModule*)ctx;
|
|
if (_this->bookmarkDisplayMode == BOOKMARK_DISP_MODE_OFF) { return; }
|
|
|
|
if (_this->mouseClickedInLabel) {
|
|
if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
|
_this->mouseClickedInLabel = false;
|
|
}
|
|
gui::waterfall.inputHandled = true;
|
|
return;
|
|
}
|
|
|
|
// First check that the mouse clicked outside of any label. Also get the bookmark that's hovered
|
|
bool inALabel = false;
|
|
WaterfallBookmark hoveredBookmark;
|
|
std::string hoveredBookmarkName;
|
|
|
|
if (_this->bookmarkDisplayMode == BOOKMARK_DISP_MODE_TOP) {
|
|
int count = _this->waterfallBookmarks.size();
|
|
for (int i = count - 1; i >= 0; i--) {
|
|
auto& bm = _this->waterfallBookmarks[i];
|
|
double centerXpos = args.fftRectMin.x + std::round((bm.bookmark.frequency - args.lowFreq) * args.freqToPixelRatio);
|
|
ImVec2 nameSize = ImGui::CalcTextSize(bm.bookmarkName.c_str());
|
|
ImVec2 rectMin = ImVec2(centerXpos - (nameSize.x / 2) - 5, args.fftRectMin.y);
|
|
ImVec2 rectMax = ImVec2(centerXpos + (nameSize.x / 2) + 5, args.fftRectMin.y + nameSize.y);
|
|
ImVec2 clampedRectMin = ImVec2(std::clamp<double>(rectMin.x, args.fftRectMin.x, args.fftRectMax.x), rectMin.y);
|
|
ImVec2 clampedRectMax = ImVec2(std::clamp<double>(rectMax.x, args.fftRectMin.x, args.fftRectMax.x), rectMax.y);
|
|
|
|
if (ImGui::IsMouseHoveringRect(clampedRectMin, clampedRectMax)) {
|
|
inALabel = true;
|
|
hoveredBookmark = bm;
|
|
hoveredBookmarkName = bm.bookmarkName;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (_this->bookmarkDisplayMode == BOOKMARK_DISP_MODE_BOTTOM) {
|
|
int count = _this->waterfallBookmarks.size();
|
|
for (int i = count - 1; i >= 0; i--) {
|
|
auto& bm = _this->waterfallBookmarks[i];
|
|
double centerXpos = args.fftRectMin.x + std::round((bm.bookmark.frequency - args.lowFreq) * args.freqToPixelRatio);
|
|
ImVec2 nameSize = ImGui::CalcTextSize(bm.bookmarkName.c_str());
|
|
ImVec2 rectMin = ImVec2(centerXpos - (nameSize.x / 2) - 5, args.fftRectMax.y - nameSize.y);
|
|
ImVec2 rectMax = ImVec2(centerXpos + (nameSize.x / 2) + 5, args.fftRectMax.y);
|
|
ImVec2 clampedRectMin = ImVec2(std::clamp<double>(rectMin.x, args.fftRectMin.x, args.fftRectMax.x), rectMin.y);
|
|
ImVec2 clampedRectMax = ImVec2(std::clamp<double>(rectMax.x, args.fftRectMin.x, args.fftRectMax.x), rectMax.y);
|
|
|
|
if (ImGui::IsMouseHoveringRect(clampedRectMin, clampedRectMax)) {
|
|
inALabel = true;
|
|
hoveredBookmark = bm;
|
|
hoveredBookmarkName = bm.bookmarkName;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if mouse was already down
|
|
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !inALabel) {
|
|
_this->mouseAlreadyDown = true;
|
|
}
|
|
if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
|
|
_this->mouseAlreadyDown = false;
|
|
_this->mouseClickedInLabel = false;
|
|
}
|
|
|
|
// If yes, cancel
|
|
if (_this->mouseAlreadyDown || !inALabel) { return; }
|
|
|
|
gui::waterfall.inputHandled = true;
|
|
|
|
double centerXpos = args.fftRectMin.x + std::round((hoveredBookmark.bookmark.frequency - args.lowFreq) * args.freqToPixelRatio);
|
|
ImVec2 nameSize = ImGui::CalcTextSize(hoveredBookmarkName.c_str());
|
|
ImVec2 rectMin = ImVec2(centerXpos - (nameSize.x / 2) - 5, (_this->bookmarkDisplayMode == BOOKMARK_DISP_MODE_BOTTOM) ? (args.fftRectMax.y - nameSize.y) : args.fftRectMin.y);
|
|
ImVec2 rectMax = ImVec2(centerXpos + (nameSize.x / 2) + 5, (_this->bookmarkDisplayMode == BOOKMARK_DISP_MODE_BOTTOM) ? args.fftRectMax.y : args.fftRectMin.y + nameSize.y);
|
|
ImVec2 clampedRectMin = ImVec2(std::clamp<double>(rectMin.x, args.fftRectMin.x, args.fftRectMax.x), rectMin.y);
|
|
ImVec2 clampedRectMax = ImVec2(std::clamp<double>(rectMax.x, args.fftRectMin.x, args.fftRectMax.x), rectMax.y);
|
|
|
|
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
|
_this->mouseClickedInLabel = true;
|
|
applyBookmark(hoveredBookmark.bookmark, gui::waterfall.selectedVFO);
|
|
}
|
|
|
|
ImGui::BeginTooltip();
|
|
ImGui::TextUnformatted(hoveredBookmarkName.c_str());
|
|
ImGui::Separator();
|
|
ImGui::Text("List: %s", hoveredBookmark.listName.c_str());
|
|
ImGui::Text("Frequency: %s", utils::formatFreq(hoveredBookmark.bookmark.frequency).c_str());
|
|
ImGui::Text("Bandwidth: %s", utils::formatFreq(hoveredBookmark.bookmark.bandwidth).c_str());
|
|
ImGui::Text("Mode: %s", demodModeList[hoveredBookmark.bookmark.mode]);
|
|
ImGui::EndTooltip();
|
|
}
|
|
|
|
json exportedBookmarks;
|
|
bool importOpen = false;
|
|
bool exportOpen = false;
|
|
pfd::open_file* importDialog;
|
|
pfd::save_file* exportDialog;
|
|
|
|
void importBookmarks(std::string path) {
|
|
std::ifstream fs(path);
|
|
json importBookmarks;
|
|
fs >> importBookmarks;
|
|
|
|
if (!importBookmarks.contains("bookmarks")) {
|
|
spdlog::error("File does not contains any bookmarks");
|
|
return;
|
|
}
|
|
|
|
if (!importBookmarks["bookmarks"].is_object()) {
|
|
spdlog::error("Bookmark attribute is invalid");
|
|
return;
|
|
}
|
|
|
|
// Load every bookmark
|
|
for (auto const [_name, bm] : importBookmarks["bookmarks"].items()) {
|
|
if (bookmarks.find(_name) != bookmarks.end()) {
|
|
spdlog::warn("Bookmark with the name '{0}' already exists in list, skipping", _name);
|
|
continue;
|
|
}
|
|
FrequencyBookmark fbm;
|
|
fbm.frequency = bm["frequency"];
|
|
fbm.bandwidth = bm["bandwidth"];
|
|
fbm.mode = bm["mode"];
|
|
fbm.selected = false;
|
|
bookmarks[_name] = fbm;
|
|
}
|
|
saveByName(selectedListName);
|
|
|
|
fs.close();
|
|
}
|
|
|
|
void exportBookmarks(std::string path) {
|
|
std::ofstream fs(path);
|
|
exportedBookmarks >> fs;
|
|
fs.close();
|
|
}
|
|
|
|
std::string name;
|
|
bool enabled = true;
|
|
bool createOpen = false;
|
|
bool editOpen = false;
|
|
bool newListOpen = false;
|
|
bool renameListOpen = false;
|
|
bool selectListsOpen = false;
|
|
|
|
bool deleteListOpen = false;
|
|
bool deleteBookmarksOpen = false;
|
|
|
|
EventHandler<ImGui::WaterFall::FFTRedrawArgs> fftRedrawHandler;
|
|
EventHandler<ImGui::WaterFall::InputHandlerArgs> inputHandler;
|
|
|
|
std::map<std::string, FrequencyBookmark> bookmarks;
|
|
|
|
std::string editedBookmarkName = "";
|
|
std::string firstEditedBookmarkName = "";
|
|
FrequencyBookmark editedBookmark;
|
|
|
|
std::vector<std::string> listNames;
|
|
std::string listNamesTxt = "";
|
|
std::string selectedListName = "";
|
|
int selectedListId = 0;
|
|
|
|
std::string editedListName;
|
|
std::string firstEditedListName;
|
|
|
|
std::vector<WaterfallBookmark> waterfallBookmarks;
|
|
|
|
int bookmarkDisplayMode = 0;
|
|
};
|
|
|
|
MOD_EXPORT void _INIT_() {
|
|
json def = json({});
|
|
def["selectedList"] = "General";
|
|
def["bookmarkDisplayMode"] = BOOKMARK_DISP_MODE_TOP;
|
|
def["lists"]["General"]["showOnWaterfall"] = true;
|
|
def["lists"]["General"]["bookmarks"] = json::object();
|
|
|
|
config.setPath(core::args["root"].s() + "/frequency_manager_config.json");
|
|
config.load(def);
|
|
config.enableAutoSave();
|
|
|
|
// Check if of list and convert if they're the old type
|
|
config.acquire();
|
|
if (!config.conf.contains("bookmarkDisplayMode")) {
|
|
config.conf["bookmarkDisplayMode"] = BOOKMARK_DISP_MODE_TOP;
|
|
}
|
|
for (auto [listName, list] : config.conf["lists"].items()) {
|
|
if (list.contains("bookmarks") && list.contains("showOnWaterfall") && list["showOnWaterfall"].is_boolean()) { continue; }
|
|
json newList;
|
|
newList = json::object();
|
|
newList["showOnWaterfall"] = true;
|
|
newList["bookmarks"] = list;
|
|
config.conf["lists"][listName] = newList;
|
|
}
|
|
config.release(true);
|
|
}
|
|
|
|
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
|
|
return new FrequencyManagerModule(name);
|
|
}
|
|
|
|
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
|
|
delete (FrequencyManagerModule*)instance;
|
|
}
|
|
|
|
MOD_EXPORT void _END_() {
|
|
config.disableAutoSave();
|
|
config.save();
|
|
}
|