diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 8bb62cb0a..338dd80c8 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -463,3 +463,9 @@ option(PLUGIN_WIPTRACKING_APP "Enable WIP Tracking Application" OFF) if(PLUGIN_WIPTRACKING_APP) add_subdirectory(wip_tracking_app) endif() + +option(PLUGIN_WIPSETTINGS_EDITOR_APP "Enable WIP Settings Editor Application" ON) + +if (PLUGIN_WIPSETTINGS_EDITOR_APP) + add_subdirectory(wip_settings_editor_app) +endif() \ No newline at end of file diff --git a/plugins/wip_settings_editor_app/CMakeLists.txt b/plugins/wip_settings_editor_app/CMakeLists.txt new file mode 100644 index 000000000..6f88ca66d --- /dev/null +++ b/plugins/wip_settings_editor_app/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.12) +project(wipsettings_app) + +if(BUILD_GUI) + + file(GLOB_RECURSE wipsettings_app_CPPS *.cpp) + add_library(wipsettings_app SHARED ${wipsettings_app_CPPS}) + + target_link_libraries(wipsettings_app + PUBLIC + satdump_core + satdump_interface + ) + + target_include_directories(wipsettings_app + PUBLIC + $ + $ + PRIVATE + $ + ) + + install(TARGETS wipsettings_app + DESTINATION ${CMAKE_INSTALL_LIBDIR}/satdump/plugins + ) +endif() diff --git a/plugins/wip_settings_editor_app/main.cpp b/plugins/wip_settings_editor_app/main.cpp new file mode 100644 index 000000000..1e6028130 --- /dev/null +++ b/plugins/wip_settings_editor_app/main.cpp @@ -0,0 +1,34 @@ +#include "core/plugin.h" +#include "logger.h" + +#include "app.h" +#include "explorer/explorer.h" +#include "settings_editor.h" + +class WipSettingsEditorAppPlugin : public satdump::Plugin +{ +public: + std::string getID() { return "wipsettings_app"; } + + void init() + { + satdump::eventBus->register_handler(renderExplorerLoaderButton); + } + + static void renderExplorerLoaderButton(const satdump::explorer::ExplorerApplication::RenderLoadMenuElementsEvent &evt) + { + if (ImGui::BeginMenu("File")) + { + if (ImGui::BeginMenu("Tools")) + { + if (ImGui::MenuItem("Instrument Config Generator")) + evt.master_handler->addSubHandler(std::make_shared()); + ImGui::EndMenu(); + } + + ImGui::EndMenu(); + } + } +}; + +PLUGIN_LOADER(WipSettingsEditorAppPlugin) \ No newline at end of file diff --git a/plugins/wip_settings_editor_app/settings_editor.cpp b/plugins/wip_settings_editor_app/settings_editor.cpp new file mode 100644 index 000000000..f94d83453 --- /dev/null +++ b/plugins/wip_settings_editor_app/settings_editor.cpp @@ -0,0 +1,353 @@ +#include "common/utils.h" +#include "settings_editor.h" +#include "resources.h" +#include +#include +#include +#include + +namespace satdump { + + WipSettingsEditorHandler::WipSettingsEditorHandler() + { + handler_tree_icon = u8"\uf471"; + } + + WipSettingsEditorHandler::~WipSettingsEditorHandler() + { + json_files.clear(); + preset_names.clear(); + preset_selected.clear(); + original_jsons.clear(); + all_instruments.clear(); + enabled_instruments.clear(); + + export_success = false; + export_status_msg.clear(); + instrument_to_add = -1; + selected_dropdown = -1; + std::memset(export_dir, 0, sizeof(export_dir)); + } + + void WipSettingsEditorHandler::drawMenu() + { + if (ImGui::CollapsingHeader("Settings", ImGuiTreeNodeFlags_DefaultOpen)) { + // Add settings options here + } + } + + void WipSettingsEditorHandler::drawMenuBar() + { + // file_open_menu.render("initiate the gizmo", "Select JSON file", ".", {{"JSON Files", ".json"}}); + } + + void WipSettingsEditorHandler::drawContents(ImVec2 win_size) + { + (void)win_size; + // Load JSON files + if (json_files.empty()) { + auto dir = resources::getResourcePath("instrument_cfgs"); + + for (const auto& entry : std::filesystem::directory_iterator(dir)) { + if (!entry.is_regular_file() || entry.path().extension() != ".json") + continue; + + std::string filename = entry.path().stem().string(); + json_files.push_back(filename); + all_instruments.push_back(filename); + + std::ifstream file(entry.path()); + std::stringstream buffer; + buffer << file.rdbuf(); + + ordered_json j; + try { + j = ordered_json::parse(buffer.str()); + } catch (const std::exception&) { + // ignore parse errors + } + + std::vector names; + std::vector selected; + + if (j.contains("presets") && j["presets"].is_array()) { + for (const auto& pr : j["presets"]) { + if (pr.contains("name") && pr["name"].is_string()) { + names.push_back(pr["name"].get()); + selected.push_back(false); + } + } + } + + preset_names.push_back(std::move(names)); + preset_selected.push_back(std::move(selected)); + original_jsons.push_back(std::move(j)); + } + } + + // UI for export directory and buttons + ImGui::Text("Export Selected Presets:"); + ImGui::InputText("", export_dir, sizeof(export_dir)); + ImGui::SameLine(); + + if (ImGui::Button("Export Configs")) { + export_success = false; + export_status_msg.clear(); + + if (std::strlen(export_dir) == 0) { + export_status_msg = "Please specify an export directory."; + } else { + std::filesystem::path out_dir(export_dir); + try { + if (!std::filesystem::exists(out_dir)) { + std::filesystem::create_directories(out_dir); + } + + for (size_t i = 0; i < json_files.size(); ++i) { + if (std::find(enabled_instruments.begin(), + enabled_instruments.end(), + json_files[i]) == enabled_instruments.end()) + { + continue; + } + + ordered_json export_json = original_jsons[i]; + if (export_json.contains("presets") && export_json["presets"].is_array()) { + ordered_json filtered = ordered_json::array(); + + for (size_t j = 0; j < preset_names[i].size(); ++j) { + if (!preset_selected[i][j]) + continue; + + for (const auto& pr : export_json["presets"]) { + if (pr.contains("name") && pr["name"].is_string() && + pr["name"].get() == preset_names[i][j]) + { + filtered.push_back(pr); + break; + } + } + } + + export_json["presets"] = filtered; + } + + auto out_file = out_dir / (json_files[i] + ".json"); + std::ofstream ofs(out_file); + if (!ofs) { + export_status_msg += "Failed to write: " + out_file.string() + "\n"; + } else { + ofs << export_json.dump(4); + } + } + + if (export_status_msg.empty()) { + export_success = true; + export_status_msg = "Export completed successfully."; + } + } catch (const std::exception& e) { + export_status_msg = std::string("Export failed: ") + e.what(); + } + } + } + + ImGui::SameLine(); + if (ImGui::Button("Export to Config")) { + export_config_pressed = true; + /* + //functionality not implemented yet - once satdump supports config overrride this will be modified + + export_success = false; + export_status_msg.clear(); + + try { + ordered_json config_export = ordered_json::array(); + + for (size_t i = 0; i < json_files.size(); ++i) { + if (std::find(enabled_instruments.begin(), + enabled_instruments.end(), + json_files[i]) == enabled_instruments.end()) + { + continue; + } + + const auto& input = original_jsons[i]; + ordered_json out_obj; + + if (input.contains("name")) { + out_obj["name"] = input["name"]; + } + + if (input.contains("presets") && input["presets"].is_array()) { + ordered_json filtered = ordered_json::array(); + + for (size_t j = 0; j < preset_names[i].size(); ++j) { + if (!preset_selected[i][j]) + continue; + + for (const auto& pr : input["presets"]) { + if (pr.contains("name") && pr["name"].is_string() && + pr["name"].get() == preset_names[i][j]) + { + filtered.push_back(pr); + break; + } + } + } + + out_obj["presets"] = filtered; + } + + if (input.contains("default")) { + out_obj["default"] = input["default"]; + } + + for (auto& [key, val] : input.items()) { + if (key != "name" && key != "presets" && key != "default") { + out_obj[key] = val; + } + } + + config_export.push_back(std::move(out_obj)); + } + + constexpr auto path = "config/"; + std::ofstream ofs(path); + + if (!ofs) { + export_status_msg = std::string("Failed to write: ") + path; + } else { + ofs << config_export.dump(4); + export_success = true; + export_status_msg = std::string("Exported to ") + path; + } + } catch (const std::exception& e) { + export_status_msg = std::string("Export failed: ") + e.what(); + } + */ + } + if (export_config_pressed) { + ImGui::TextColored(ImVec4(1, 0, 0, 1), "WIP feature - not currently possible"); + } + + if (!export_status_msg.empty()) { + ImVec4 color = export_success ? ImVec4(0, 1, 0, 1) : ImVec4(1, 0, 0, 1); + ImGui::TextColored(color, "%s", export_status_msg.c_str()); + } + + ImGui::Separator(); + + // --- Instrument selection --- + std::vector available_instruments; + for (const auto& inst : all_instruments) { + if (std::find(enabled_instruments.begin(), enabled_instruments.end(), inst) == + enabled_instruments.end()) + { + available_instruments.push_back(inst); + } + } + + std::sort(available_instruments.begin(), available_instruments.end()); + + ImGui::Text("Add Instrument:"); + if (available_instruments.empty()) { + ImGui::TextDisabled("None available"); + } else { + if (ImGui::BeginCombo("##instrument_dropdown", + selected_dropdown >= 0 + ? available_instruments[selected_dropdown].c_str() + : "None")) + { + for (int n = 0; n < static_cast(available_instruments.size()); ++n) { + bool is_selected = (selected_dropdown == n); + if (ImGui::Selectable(available_instruments[n].c_str(), is_selected)) { + selected_dropdown = n; + } + if (is_selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + + ImGui::SameLine(); + if (ImGui::Button("Add") && selected_dropdown >= 0) { + enabled_instruments.push_back(available_instruments[selected_dropdown]); + selected_dropdown = -1; + } + + ImGui::SameLine(); + if (ImGui::Button("Add All")) { + for (const auto& inst : available_instruments) { + enabled_instruments.push_back(inst); + } + } + } + + ImGui::SameLine(); + if (ImGui::Button("Remove All")) { + enabled_instruments.clear(); + } + + ImGui::Separator(); + + ImGui::Text("Current instrument configs to be exported:"); + ImGui::BeginChild("json_files_scroll", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar); + + for (size_t i = 0; i < json_files.size(); ++i) { + const auto& name = json_files[i]; + if (std::find(enabled_instruments.begin(), enabled_instruments.end(), name) == + enabled_instruments.end()) + { + continue; + } + + ImGui::PushID(static_cast(i)); + float full_w = ImGui::GetContentRegionAvail().x; + float button_w = ImGui::CalcTextSize("Remove").x + ImGui::GetStyle().FramePadding.x * 2; + float header_w = full_w - button_w - ImGui::GetStyle().ItemSpacing.x; + + ImGui::Columns(2, nullptr, false); + ImGui::SetColumnWidth(0, header_w); + + bool open = ImGui::CollapsingHeader((name + "##" + std::to_string(i)).c_str()); + ImGui::NextColumn(); + + if (ImGui::Button("Remove", ImVec2(button_w, 0))) { + enabled_instruments.erase( + std::remove(enabled_instruments.begin(), enabled_instruments.end(), name), + enabled_instruments.end()); + ImGui::Columns(1); + ImGui::PopID(); + continue; + } + + ImGui::Columns(1); + + if (open) { + if (ImGui::Button("Select All")) { + std::fill(preset_selected[i].begin(), preset_selected[i].end(), true); + } + ImGui::SameLine(); + if (ImGui::Button("Select None")) { + std::fill(preset_selected[i].begin(), preset_selected[i].end(), false); + } + + for (size_t j = 0; j < preset_names[i].size(); ++j) { + bool checked = preset_selected[i][j]; + if (ImGui::Checkbox( + (preset_names[i][j] + "##" + std::to_string(i) + "_" + std::to_string(j)).c_str(), + &checked)) + { + preset_selected[i][j] = checked; + } + } + } + + ImGui::PopID(); + } + + ImGui::EndChild(); + } + +} diff --git a/plugins/wip_settings_editor_app/settings_editor.h b/plugins/wip_settings_editor_app/settings_editor.h new file mode 100644 index 000000000..ca0906fd8 --- /dev/null +++ b/plugins/wip_settings_editor_app/settings_editor.h @@ -0,0 +1,54 @@ +#pragma once + +#include "common/widgets/menuitem_fileopen.h" +#include "handlers/handler.h" +#include "imgui/imgui.h" +#include +#include +#include + + +// order-keeping JSON alias +using ordered_json = nlohmann::ordered_json; + +namespace satdump +{ + class WipSettingsEditorHandler : public handlers::Handler + { + protected: + void drawMenu(); + void drawContents(ImVec2 win_size); + void drawMenuBar(); + + private: + // File open dialog + widget::MenuItemFileOpen file_open_menu; + + // Internal state storage for instruments and presets + std::vector json_files; + std::vector> preset_names; + std::vector> preset_selected; + + std::vector original_jsons; + + std::vector all_instruments; + std::vector enabled_instruments; + + int instrument_to_add = -1; + int selected_dropdown = -1; + + // Export + char export_dir[512] = ""; + bool export_success = false; + std::string export_status_msg; + + bool export_config_pressed = false; + + public: + WipSettingsEditorHandler(); + ~WipSettingsEditorHandler(); + + std::string getID() { return "wipsettings_handler"; } + std::string getName() { return "Instrument Config Generator"; } + }; +} // namespace satdump