diff --git a/.gitignore b/.gitignore index e821d84..9f50645 100755 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ build .cache subprojects/* !subprojects/*.wrap -src/noinc.hpp diff --git a/meson.build b/meson.build index 26534ff..136ec47 100755 --- a/meson.build +++ b/meson.build @@ -37,6 +37,7 @@ deps = [ dependency('libpqxx'), dependency('uuid'), dependency('threads'), + dependency('gtk+-3.0'), dependency('OpenGL'), dependency('sndfile'), subproject('vertex_engine').get_variable('vertex_engine_dep'), diff --git a/run.sh b/run.sh deleted file mode 100755 index 4b31628..0000000 --- a/run.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/zsh - -PROJECT_NAME=$(basename $PWD) - -run() { - command meson compile -C build - if [[ -z "$1" ]]; then - cd build - ./bin/$PROJECT_NAME - cd .. - else - meson test $1 -C build - fi -} - -if [ -d "build" ]; then - run -else - command meson setup build - run -fi diff --git a/src/meson.build b/src/meson.build index 1a0cab0..90a206d 100755 --- a/src/meson.build +++ b/src/meson.build @@ -10,6 +10,7 @@ headers = [ 'monitor/gui/components/audio/audio.hpp', 'monitor/gui/components/markers/markers.hpp', 'monitor/gui/components/helpers/helpers.hpp', + 'monitor/gui/components/file_dialog/file_dialog.hpp', ############ GUI/WIN 'monitor/gui/win/win.hpp', @@ -17,6 +18,7 @@ headers = [ ############ UTILS 'monitor/utils/var.hpp', 'monitor/libs/audio/audio.hpp', + 'monitor/libs/gtkfd/gtkfd.hpp', ############ # RUN @@ -31,9 +33,6 @@ sources = [ 'monitor/gui/components/creator/cpp/base.cpp', 'monitor/gui/components/creator/cpp/on_event.cpp', 'monitor/gui/components/creator/cpp/render/render.cpp', - 'monitor/gui/components/creator/cpp/render/buttons.cpp', - 'monitor/gui/components/creator/cpp/render/combo.cpp', - 'monitor/gui/components/creator/cpp/render/setup.cpp', 'monitor/gui/components/creator/cpp/render/spinner.cpp', 'monitor/gui/components/snapshot/cpp/base.cpp', 'monitor/gui/components/snapshot/cpp/on_event.cpp', @@ -50,9 +49,11 @@ sources = [ 'monitor/gui/components/helpers/cpp/base.cpp', 'monitor/gui/components/helpers/cpp/on_event.cpp', 'monitor/gui/components/helpers/cpp/render.cpp', + 'monitor/gui/components/file_dialog/file_dialog.cpp', ############ UTILS 'monitor/libs/audio/audio.cpp', + 'monitor/libs/gtkfd/gtkfd.cpp', ############ GUI/WIN 'monitor/gui/win/cpp/base.cpp', diff --git a/src/monitor/gui/components/creator/cpp/base.cpp b/src/monitor/gui/components/creator/cpp/base.cpp index f87e8fb..d313a34 100644 --- a/src/monitor/gui/components/creator/cpp/base.cpp +++ b/src/monitor/gui/components/creator/cpp/base.cpp @@ -1,15 +1,11 @@ #include "monitor/gui/components/creator/creator.hpp" - #include "monitor/utils/event_type.hpp" -#include "monitor/utils/var.hpp" namespace monitor::components { void creator::on_attach() { CONNECT(this); - for (const auto& entry : std::filesystem::directory_iterator(utils::var::DIR)) - if (entry.is_directory()) m_dirs.push_back(entry.path().filename().string()); } void creator::on_detach() @@ -22,33 +18,24 @@ namespace monitor::components void creator::create() { m_status = utils::var::STATUS::PROCESS; - m_setup.m_file = utils::var::DIR / m_dir.m_name / m_file.m_name; - // send to: tabs - VE::event e { utils::event_type::CREATE_SNAPSHOT, m_setup }; - EMIT(e); - clear(); + auto f = [this]() { + m_setup.m_file = m_file_path; + // send to: tabs + VE::event e { utils::event_type::CREATE_SNAPSHOT, m_setup }; + EMIT(e); + }; + std::thread th(f); + th.detach(); } void creator::clear() { - m_dir.clear(); - m_file.clear(); - m_files.clear(); m_status = utils::var::STATUS::COMPLETED; - auto future = std::async(std::launch::async, [this]() { std::this_thread::sleep_for(std::chrono::seconds(3)); + m_file_path.clear(); m_status = utils::var::STATUS::EMPTY; }); } - - - void creator::set_domain(std::string name) - { - if (name == "frequensy") - m_setup.m_domain = hr::DOMAIN_PLUGIN::FREQUENSY; - else - m_setup.m_domain = hr::DOMAIN_PLUGIN::TIME; - } } diff --git a/src/monitor/gui/components/creator/cpp/on_event.cpp b/src/monitor/gui/components/creator/cpp/on_event.cpp index adfaaa6..80a8624 100644 --- a/src/monitor/gui/components/creator/cpp/on_event.cpp +++ b/src/monitor/gui/components/creator/cpp/on_event.cpp @@ -11,11 +11,11 @@ namespace monitor::components switch (type) { - // case utils::event_type::CREATE_WORKSPACE_COMPLETE: - // { - // clear(); - // break; - // } + case utils::event_type::CREATE_SNAPSHOT_COMPLETED: + { + clear(); + break; + } } } } diff --git a/src/monitor/gui/components/creator/cpp/render/buttons.cpp b/src/monitor/gui/components/creator/cpp/render/buttons.cpp deleted file mode 100644 index cf1bc60..0000000 --- a/src/monitor/gui/components/creator/cpp/render/buttons.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "monitor/gui/components/creator/creator.hpp" -#include "monitor/utils/var.hpp" - -namespace monitor::components -{ - void creator::render_buttons() - { - ImGui::BeginDisabled(m_status == utils::var::STATUS::EMPTY); - ImGui::BeginDisabled(m_status == utils::var::STATUS::PROCESS); - ImGui::BeginDisabled(m_status == utils::var::STATUS::COMPLETED); - - if (ImGui::Button("create", ImVec2{ 94.f, 27.f })) create(); - - ImGui::EndDisabled(); - ImGui::EndDisabled(); - ImGui::EndDisabled(); - } -} diff --git a/src/monitor/gui/components/creator/cpp/render/combo.cpp b/src/monitor/gui/components/creator/cpp/render/combo.cpp deleted file mode 100644 index ffd7c4e..0000000 --- a/src/monitor/gui/components/creator/cpp/render/combo.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "monitor/gui/components/creator/creator.hpp" -#include "monitor/utils/var.hpp" - -namespace monitor::components -{ - void creator::render_combo() - { - auto ctx = ImGui::GetCurrentContext(); - ImGui::SameLine(0.f, ctx->Style.FramePadding.x); - - ImGui::SetNextItemWidth(400.f); - if (ImGui::BeginCombo(VE_NO_NAME("select_dir"), m_dir.m_id < 0 ? "---" : m_dirs[m_dir.m_id].c_str())) - { - for (std::size_t i = 0; i < m_dirs.size(); ++i) - { - const bool is_selected = (m_dir.m_id == static_cast(i)); - if (ImGui::Selectable(m_dirs[i].c_str(), is_selected)) - { - m_files.clear(); - m_dir.init(i, m_dirs[i]); - for (const auto& entry : std::filesystem::directory_iterator(utils::var::DIR/m_dir.m_name)) - if (entry.is_regular_file()) m_files.push_back(entry.path().filename().string()); - } - if (is_selected) ImGui::SetItemDefaultFocus(); - } - - ImGui::EndCombo(); - } - - ctx = ImGui::GetCurrentContext(); - ImGui::SameLine(0.f, ctx->Style.FramePadding.x); - - ImGui::SetNextItemWidth(400.f); - if (ImGui::BeginCombo(VE_NO_NAME("select_file"), m_file.m_id < 0 ? "---" : m_files[m_file.m_id].c_str())) - { - if (!m_files.empty()) - { - for (std::size_t i = 0; i < m_files.size(); ++i) - { - const bool is_selected = (m_file.m_id == static_cast(i)); - if (ImGui::Selectable(m_files[i].c_str(), is_selected)) - { - m_file.init(i, m_files[i]); - m_status = utils::var::STATUS::READY; - } - if (is_selected) ImGui::SetItemDefaultFocus(); - } - } - ImGui::EndCombo(); - } - } -} diff --git a/src/monitor/gui/components/creator/cpp/render/render.cpp b/src/monitor/gui/components/creator/cpp/render/render.cpp index b0b2d77..f6db7f8 100644 --- a/src/monitor/gui/components/creator/cpp/render/render.cpp +++ b/src/monitor/gui/components/creator/cpp/render/render.cpp @@ -1,4 +1,5 @@ #include "monitor/gui/components/creator/creator.hpp" +#include "monitor/gui/components/file_dialog/file_dialog.hpp" namespace monitor::components { @@ -6,11 +7,33 @@ namespace monitor::components { auto ctx = ImGui::GetCurrentContext(); - render_combo(); - render_spinner(); - ImGui::SameLine(0.f, 9.f * ctx->Style.FramePadding.x); - render_setup(); - ImGui::SameLine(0.f, 2.f * ctx->Style.FramePadding.x); - render_buttons(); + ImGui::BeginDisabled(m_status == utils::var::STATUS::PROCESS); + ImGui::BeginDisabled(m_status == utils::var::STATUS::COMPLETED); + if (ImGui::Button("open", ImVec2{ 94.f, 27.f })) + { + m_file_path = m_fd.open(); + if (!m_file_path.empty()) m_status = utils::var::STATUS::READY; + else m_status = utils::var::STATUS::EMPTY; + } + ImGui::EndDisabled(); + ImGui::EndDisabled(); + + ImGui::SameLine(); + + if (!m_file_path.empty()) + { + ImGui::Text(m_file_path.filename().c_str()); + ImGui::SameLine(); + + ImGui::BeginDisabled(m_status == utils::var::STATUS::EMPTY); + ImGui::BeginDisabled(m_status == utils::var::STATUS::PROCESS); + ImGui::BeginDisabled(m_status == utils::var::STATUS::COMPLETED); + if (ImGui::Button("create", ImVec2{ 94.f, 27.f })) create(); + ImGui::EndDisabled(); + ImGui::EndDisabled(); + ImGui::EndDisabled(); + + render_spinner(); + } } } diff --git a/src/monitor/gui/components/creator/cpp/render/setup.cpp b/src/monitor/gui/components/creator/cpp/render/setup.cpp deleted file mode 100644 index c12f87e..0000000 --- a/src/monitor/gui/components/creator/cpp/render/setup.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "monitor/gui/components/creator/creator.hpp" -#include - -namespace monitor::components -{ - void creator::render_setup() - { - ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); - - float spacing = ImGui::GetStyle().ItemInnerSpacing.x; - - ImGui::Text("block size:"); - ImGui::SameLine(0.0f, spacing); - render_block_size_setup(spacing); - - ImGui::SameLine(); - ImGui::Text(" | "); - ImGui::SameLine(0.f, spacing); - - render_step_size_setup(spacing); - ImGui::SameLine(); - ImGui::Text(" | "); - - ImGui::PopItemFlag(); - } - - void creator::render_block_size_setup(float spacing) - { - ImGui::SetCursorPosY(8.f); - VE_PUSH_FONT(ICON, 12); - ImGui::PushID(VE_NO_NAME("block_size minus")); - if (ImGui::Button(VE::style::icon::ICON_MINUS, ImVec2{24, 20})) - { - m_setup.m_block_size -= m_step; - m_setup.m_block_size = std::max(m_step, m_setup.m_block_size); - } - ImGui::PopID(); - VE_POP_FONT(); - - ImGui::SameLine(0.0f, spacing); - ImGui::Text("%zu", m_setup.m_block_size); - // тут нужно жестко, а то при изменении циферек прагает в сторону поле - ImGui::SameLine(1000.0f); - - ImGui::SetCursorPosY(8.f); - VE_PUSH_FONT(ICON, 12); - ImGui::PushID(VE_NO_NAME("block_size plus")); - if (ImGui::Button(VE::style::icon::ICON_PLUS, ImVec2{24, 20})) - m_setup.m_block_size += m_step; - ImGui::PopID(); - VE_POP_FONT(); - ImGui::PopItemFlag(); - } - - void creator::render_step_size_setup(float spacing) - { - ImGui::Text("step size:"); - ImGui::SameLine(0.0f, spacing); - - ImGui::PushItemFlag(ImGuiItemFlags_ButtonRepeat, true); - ImGui::SetCursorPosY(8.f); - VE_PUSH_FONT(ICON, 12); - ImGui::PushID(VE_NO_NAME("step_size_minus")); - if (ImGui::Button(VE::style::icon::ICON_MINUS, ImVec2{24, 20})) - { - m_setup.m_step_size -= m_step; - m_setup.m_step_size = std::max(m_step, m_setup.m_step_size); - } - ImGui::PopID(); - VE_POP_FONT(); - - ImGui::SameLine(0.0f, spacing); - ImGui::Text("%zu", m_setup.m_step_size); - // тут нужно жестко, а то при изменении циферек прагает в сторону поле - ImGui::SameLine(1195.0f); - - ImGui::SetCursorPosY(8.f); - VE_PUSH_FONT(ICON, 12); - ImGui::PushID(VE_NO_NAME("step_size_plus")); - if (ImGui::Button(VE::style::icon::ICON_PLUS, ImVec2{24, 20})) - m_setup.m_step_size += m_step; - ImGui::PopID(); - VE_POP_FONT(); - } -} diff --git a/src/monitor/gui/components/creator/cpp/render/spinner.cpp b/src/monitor/gui/components/creator/cpp/render/spinner.cpp index 2e67630..ca1374d 100644 --- a/src/monitor/gui/components/creator/cpp/render/spinner.cpp +++ b/src/monitor/gui/components/creator/cpp/render/spinner.cpp @@ -7,7 +7,11 @@ namespace monitor::components { void creator::render_spinner() { - ImVec2 pos{ 830.f, 12.f }; + ImVec2 buttonPos = ImGui::GetItemRectMin(); // левый верхний угол кнопки + ImVec2 buttonSize = ImGui::GetItemRectSize(); // размер кнопки + float spacing = 10.f; + ImVec2 pos = ImVec2(buttonPos.x + buttonSize.x + spacing, buttonPos.y); + if (m_status == utils::var::STATUS::COMPLETED) m_spinner.render(pos); else if (m_status == utils::var::STATUS::READY) m_spinner.render(pos, "#B4CF16"); else if (m_status == utils::var::STATUS::PROCESS) m_spinner.render(pos, "#B82C5C", true); diff --git a/src/monitor/gui/components/creator/creator.hpp b/src/monitor/gui/components/creator/creator.hpp index 3ebd326..91fab0a 100644 --- a/src/monitor/gui/components/creator/creator.hpp +++ b/src/monitor/gui/components/creator/creator.hpp @@ -3,6 +3,7 @@ #include #include #include "monitor/gui/components/spinner/spinner.hpp" +#include "monitor/gui/components/file_dialog/file_dialog.hpp" #include "monitor/utils/var.hpp" namespace monitor::components @@ -13,42 +14,18 @@ namespace monitor::components VE_EVENT_OVERIDE(); private: - struct combo - { - std::string m_name = "no selected"; - int m_id = -1; - void init(int id, std::string name) { m_id = id; m_name = name; } - void clear() - { - m_name.clear(); - m_id = -1; - } - }; - - private: - std::vector m_dirs; - std::vector m_files; - combo m_dir; - combo m_file; utils::var::STATUS m_status = utils::var::STATUS::EMPTY; components::spinner m_spinner; + + private: hr::setup m_setup; - std::size_t m_step = 256; - + file_dialog m_fd; + std::filesystem::path m_file_path; private: - void set_domain(std::string name); + void render_spinner(); void create(); void clear(); - void render_combo(); - void render_spinner(); - void render_buttons(); - void render_setup(); - void render_combo_signals(); - - private: - void render_block_size_setup(float spacing); - void render_step_size_setup(float spacing); }; } diff --git a/src/monitor/gui/components/file_dialog/file_dialog.cpp b/src/monitor/gui/components/file_dialog/file_dialog.cpp new file mode 100644 index 0000000..1138dcd --- /dev/null +++ b/src/monitor/gui/components/file_dialog/file_dialog.cpp @@ -0,0 +1,9 @@ +#include "file_dialog.hpp" + +namespace monitor::components +{ + std::filesystem::path file_dialog::open(const std::filesystem::path& default_path) + { + return m_nfd.open(default_path); + } +} diff --git a/src/monitor/gui/components/file_dialog/file_dialog.hpp b/src/monitor/gui/components/file_dialog/file_dialog.hpp new file mode 100644 index 0000000..ab79834 --- /dev/null +++ b/src/monitor/gui/components/file_dialog/file_dialog.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "monitor/libs/gtkfd/gtkfd.hpp" + +namespace monitor::components +{ + class file_dialog + { + public: + file_dialog() = default; + ~file_dialog() = default; + + public: + std::filesystem::path open(const std::filesystem::path& default_path = "/mnt/raid/projects/dsp/songs"); + + private: + libs::gtkfd m_nfd; + }; +} diff --git a/src/monitor/gui/components/tabs/cpp/base.cpp b/src/monitor/gui/components/tabs/cpp/base.cpp index 653718a..6ed62d9 100755 --- a/src/monitor/gui/components/tabs/cpp/base.cpp +++ b/src/monitor/gui/components/tabs/cpp/base.cpp @@ -29,6 +29,9 @@ namespace monitor::components s->init(setup); s->on_attach(); m_snapshots.push_back(s); + + VE::event e { utils::event_type::CREATE_SNAPSHOT_COMPLETED, nullptr }; + EMIT(e); } std::string tabs::string_cut(std::filesystem::path p, int n) diff --git a/src/monitor/libs/gtkfd.OLD/gtkfd.cpp b/src/monitor/libs/gtkfd.OLD/gtkfd.cpp new file mode 100644 index 0000000..b478af7 --- /dev/null +++ b/src/monitor/libs/gtkfd.OLD/gtkfd.cpp @@ -0,0 +1,500 @@ +#include +#include +#include +#include +#include "gtkfd.hpp" + + template + struct Free_Guard + { + T* data; + Free_Guard(T* freeable) noexcept : data(freeable) {} + ~Free_Guard() { NFDi_Free(data); } + }; + + template + struct FreeCheck_Guard + { + T* data; + FreeCheck_Guard(T* freeable = nullptr) noexcept : data(freeable) {} + ~FreeCheck_Guard() { if (data) NFDi_Free(data); } + }; + + /* current error */ + const char* g_errorstr = nullptr; + + void NFDi_SetError(const char* msg) + { + g_errorstr = msg; + } + + template + T* NFDi_Malloc(size_t bytes) + { + void* ptr = malloc(bytes); + if (!ptr) NFDi_SetError("NFDi_Malloc failed."); + return static_cast(ptr); + } + + template + void NFDi_Free(T *ptr) + { + assert(ptr); + free(static_cast(ptr)); + } + + template + T* copy(const T* begin, const T* end, T* out) + { + for (; begin != end; ++begin) *out++ = *begin; + return out; + } + + // Does not own the filter and extension. + struct Pair_GtkFileFilter_FileExtension + { + GtkFileFilter* filter; + const char* extensionBegin; + const char* extensionEnd; + }; + + struct ButtonClickedArgs + { + Pair_GtkFileFilter_FileExtension* map; + GtkFileChooser* chooser; + }; + + void AddFiltersToDialog(GtkFileChooser* chooser, const item* filterList, uint_t filterCount) + { + if (filterCount) + { + assert(filterList); + // we have filters to add ... format and add them + + for (uint_t index = 0; index != filterCount; ++index) + { + GtkFileFilter* filter = gtk_file_filter_new(); + + // count number of file extensions + size_t sep = 1; + for (const char* p_spec = filterList[index].m_spec; *p_spec; ++p_spec) if (*p_spec == L',') ++sep; + + // calculate space needed (including the trailing '\0') + size_t nameSize = sep + strlen(filterList[index].m_spec) + 3 + strlen(filterList[index].m_name); + + // malloc the required memory + char* nameBuf = NFDi_Malloc(sizeof(char) * nameSize); + + char* p_nameBuf = nameBuf; + for (const char* p_filterName = filterList[index].m_name; *p_filterName; ++p_filterName) *p_nameBuf++ = *p_filterName; + *p_nameBuf++ = ' '; + *p_nameBuf++ = '('; + const char* p_extensionStart = filterList[index].m_spec; + for (const char *p_spec = filterList[index].m_spec; true; ++p_spec) + { + if (*p_spec == ',' || !*p_spec) + { + if (*p_spec == ',') + { + *p_nameBuf++ = ','; + *p_nameBuf++ = ' '; + } + + // +1 for the trailing '\0' + char* extnBuf = NFDi_Malloc(sizeof(char) * (p_spec - p_extensionStart + 3)); + char* p_extnBufEnd = extnBuf; + *p_extnBufEnd++ = '*'; + *p_extnBufEnd++ = '.'; + p_extnBufEnd = copy(p_extensionStart, p_spec, p_extnBufEnd); + *p_extnBufEnd++ = '\0'; + assert((unsigned int)(p_extnBufEnd - extnBuf) == sizeof(char) * (p_spec - p_extensionStart + 3)); + gtk_file_filter_add_pattern(filter, extnBuf); + NFDi_Free(extnBuf); + if (*p_spec) p_extensionStart = p_spec + 1; + else break; + } + else *p_nameBuf++ = *p_spec; + } + *p_nameBuf++ = ')'; + *p_nameBuf++ = '\0'; + assert((unsigned int)(p_nameBuf - nameBuf) == sizeof(char) * nameSize); + + // add to the filter + gtk_file_filter_set_name(filter, nameBuf); + + // free the memory + NFDi_Free(nameBuf); + + // add filter to chooser + gtk_file_chooser_add_filter(chooser, filter); + } + } + + /* always append a wildcard option to the end*/ + + GtkFileFilter* filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, "All files"); + gtk_file_filter_add_pattern(filter, "*"); + gtk_file_chooser_add_filter(chooser, filter); + } + + // returns null-terminated map (trailing .filter is null) + Pair_GtkFileFilter_FileExtension* AddFiltersToDialogWithMap(GtkFileChooser* chooser, const item* filterList, uint_t filterCount) + { + Pair_GtkFileFilter_FileExtension* map = NFDi_Malloc(sizeof(Pair_GtkFileFilter_FileExtension) * (filterCount + 1)); + + if (filterCount) + { + assert(filterList); + for (uint_t index = 0; index != filterCount; ++index) + { + GtkFileFilter* filter = gtk_file_filter_new(); + + // store filter in map + map[index].filter = filter; + map[index].extensionBegin = filterList[index].m_spec; + map[index].extensionEnd = nullptr; + + // count number of file extensions + std::size_t sep = 1; + for (const char *p_spec = filterList[index].m_spec; *p_spec; ++p_spec) + if (*p_spec == L',') ++sep; + + // calculate space needed (including the trailing '\0') + std::size_t nameSize = sep + strlen(filterList[index].m_spec) + 3 + strlen(filterList[index].m_name); + + // malloc the required memory + char* nameBuf = NFDi_Malloc(sizeof(char) * nameSize); + char* p_nameBuf = nameBuf; + + for (const char *p_filterName = filterList[index].m_name; *p_filterName; ++p_filterName) *p_nameBuf++ = *p_filterName; + *p_nameBuf++ = ' '; + *p_nameBuf++ = '('; + const char* p_extensionStart = filterList[index].m_spec; + + for (const char* p_spec = filterList[index].m_spec; true; ++p_spec) + { + if (*p_spec == ',' || !*p_spec) + { + if (*p_spec == ',') + { + *p_nameBuf++ = ','; + *p_nameBuf++ = ' '; + } + + // +1 for the trailing '\0' + char *extnBuf = NFDi_Malloc(sizeof(char) * (p_spec - p_extensionStart + 3)); + char *p_extnBufEnd = extnBuf; + *p_extnBufEnd++ = '*'; + *p_extnBufEnd++ = '.'; + p_extnBufEnd = copy(p_extensionStart, p_spec, p_extnBufEnd); + *p_extnBufEnd++ = '\0'; + assert((unsigned int)(p_extnBufEnd - extnBuf) == sizeof(char) * (p_spec - p_extensionStart + 3)); + gtk_file_filter_add_pattern(filter, extnBuf); + NFDi_Free(extnBuf); + + // store current pointer in map (if it's the first one) + if (map[index].extensionEnd == nullptr) map[index].extensionEnd = p_spec; + if (*p_spec) p_extensionStart = p_spec + 1; + else break; + } + else *p_nameBuf++ = *p_spec; + } + *p_nameBuf++ = ')'; + *p_nameBuf++ = '\0'; + assert((unsigned int)(p_nameBuf - nameBuf) == sizeof(char) * nameSize); + + // add to the filter + gtk_file_filter_set_name(filter, nameBuf); + + // free the memory + NFDi_Free(nameBuf); + + // add filter to chooser + gtk_file_chooser_add_filter(chooser, filter); + } + } + + // set trailing map index to null + map[filterCount].filter = nullptr; + + /* always append a wildcard option to the end*/ + GtkFileFilter* filter = gtk_file_filter_new(); + gtk_file_filter_set_name(filter, "All files"); + gtk_file_filter_add_pattern(filter, "*"); + gtk_file_chooser_add_filter(chooser, filter); + + return map; + } + + void SetDefaultPath(GtkFileChooser* chooser, const char *defaultPath) + { + if (!defaultPath || !*defaultPath) return; + + /* GTK+ manual recommends not specifically setting the default path. + We do it anyway in order to be consistent across platforms. + If consistency with the native OS is preferred, this is the line + to comment out. -ml */ + gtk_file_chooser_set_current_folder(chooser, defaultPath); + } + + void SetDefaultName(GtkFileChooser* chooser, const char *defaultName) + { + if (!defaultName || !*defaultName) return; + gtk_file_chooser_set_current_name(chooser, defaultName); + } + + void WaitForCleanup() + { + while (gtk_events_pending()) gtk_main_iteration(); + } + + struct Widget_Guard + { + GtkWidget* data; + Widget_Guard(GtkWidget* widget) : data(widget) {} + ~Widget_Guard() + { + WaitForCleanup(); + gtk_widget_destroy(data); + WaitForCleanup(); + } + }; + + static void FileActivatedSignalHandler(GtkButton* saveButton, void* userdata) + { + ButtonClickedArgs* args = static_cast(userdata); + GtkFileChooser* chooser = args->chooser; + char* currentFileName = gtk_file_chooser_get_current_name(chooser); + + if (*currentFileName) + { + // string is not empty + // find a '.' in the file name + const char* p_period = currentFileName; + for (;*p_period; ++p_period) if (*p_period == '.') break; + + if (!*p_period) + { // there is no '.', so append the default extension + Pair_GtkFileFilter_FileExtension* filterMap = static_cast(args->map); + GtkFileFilter* currentFilter = gtk_file_chooser_get_filter(chooser); + if (currentFilter) + for (;filterMap->filter; ++filterMap) + if (filterMap->filter == currentFilter) break; + + if (filterMap->filter) + { + // memory for appended string (including '.' and trailing '\0') + char* appendedFileName = NFDi_Malloc(sizeof(char) * ((p_period - currentFileName) + (filterMap->extensionEnd - filterMap->extensionBegin) + 2)); + char* p_fileName = copy(currentFileName, p_period, appendedFileName); + *p_fileName++ = '.'; + p_fileName = copy(filterMap->extensionBegin, filterMap->extensionEnd, p_fileName); + *p_fileName++ = '\0'; + + assert(p_fileName - appendedFileName == (p_period - currentFileName) + (filterMap->extensionEnd - filterMap->extensionBegin) + 2); + + // set the appended file name + gtk_file_chooser_set_current_name(chooser, appendedFileName); + + // free the memory + NFDi_Free(appendedFileName); + } + } + } + + // free the memory + g_free(currentFileName); + } + +const char* NFD_GetError(void) +{ + return g_errorstr; +} + +void NFD_ClearError(void) +{ + NFDi_SetError(nullptr); +} + +/* public */ +nfdresult_t Init() +{ + // Init GTK + if (!gtk_init_check(NULL, NULL)) + { + NFDi_SetError("gtk_init_check failed to initilaize GTK+"); + return nfdresult_t::ERROR; + } + return nfdresult_t::OKAY; +} + +void NFD_Quit(void) +{ + // do nothing, GTK cannot be de-initialized +} + +void NFD_FreePathN(char* filePath) +{ + assert(filePath); + g_free(filePath); +} + +nfdresult_t NFD_OpenDialogN(char** outPath, const item* filterList, uint_t filterCount, const char* defaultPath) +{ + GtkWidget* widget = gtk_file_chooser_dialog_new("Open File", nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, nullptr); + + // guard to destroy the widget when returning from this function + Widget_Guard widgetGuard(widget); + + /* Build the filter list */ + AddFiltersToDialog(GTK_FILE_CHOOSER(widget), filterList, filterCount); + + /* Set the default path */ + SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath); + + if (gtk_dialog_run(GTK_DIALOG(widget)) == GTK_RESPONSE_ACCEPT) + { + // write out the file name + *outPath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget)); + return nfdresult_t::OKAY; + } + else return nfdresult_t::CANCEL; +} + +nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths, const item* filterList, uint_t filterCount, const char* defaultPath ) +{ + GtkWidget* widget = gtk_file_chooser_dialog_new("Open Files", nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, nullptr); + + // guard to destroy the widget when returning from this function + Widget_Guard widgetGuard(widget); + + // set select multiple + gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(widget), TRUE); + + /* Build the filter list */ + AddFiltersToDialog(GTK_FILE_CHOOSER(widget), filterList, filterCount); + + /* Set the default path */ + SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath); + + if (gtk_dialog_run(GTK_DIALOG(widget)) == GTK_RESPONSE_ACCEPT) + { + // write out the file name + GSList *fileList = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(widget)); + *outPaths = fileList; + return nfdresult_t::OKAY; + } + else return nfdresult_t::CANCEL; +} + +nfdresult_t NFD_SaveDialogN(char** outPath, const item* filterList, uint_t filterCount, const char* defaultPath, const char* defaultName) +{ + GtkWidget* widget = gtk_file_chooser_dialog_new("Save File", nullptr, GTK_FILE_CHOOSER_ACTION_SAVE, "_Cancel", GTK_RESPONSE_CANCEL, nullptr); + + // guard to destroy the widget when returning from this function + Widget_Guard widgetGuard(widget); + GtkWidget* saveButton = gtk_dialog_add_button(GTK_DIALOG(widget), "_Save", GTK_RESPONSE_ACCEPT); + + // Prompt on overwrite + gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(widget), TRUE); + + /* Build the filter list */ + ButtonClickedArgs buttonClickedArgs; + buttonClickedArgs.chooser = GTK_FILE_CHOOSER(widget); + buttonClickedArgs.map = AddFiltersToDialogWithMap(GTK_FILE_CHOOSER(widget), filterList, filterCount); + + /* Set the default path */ + SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath); + + /* Set the default file name */ + SetDefaultName(GTK_FILE_CHOOSER(widget), defaultName); + + /* set the handler to add file extension */ + gulong handlerID = g_signal_connect(G_OBJECT(saveButton), "pressed", G_CALLBACK(FileActivatedSignalHandler), static_cast(&buttonClickedArgs)); + + /* invoke the dialog (blocks until dialog is closed) */ + gint result = gtk_dialog_run(GTK_DIALOG(widget)); + + /* unset the handler */ + g_signal_handler_disconnect(G_OBJECT(saveButton), handlerID); + + /* free the filter map */ + NFDi_Free(buttonClickedArgs.map); + + if (result == GTK_RESPONSE_ACCEPT) + { + // write out the file name + *outPath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget)); + return nfdresult_t::OKAY; + } + else return nfdresult_t::CANCEL; +} + +nfdresult_t NFD_PickFolderN(char** outPath, const char* defaultPath) +{ + GtkWidget* widget = gtk_file_chooser_dialog_new("Select folder", nullptr, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, "_Cancel", GTK_RESPONSE_CANCEL, "_Select", GTK_RESPONSE_ACCEPT, nullptr); + + // guard to destroy the widget when returning from this function + Widget_Guard widgetGuard(widget); + + /* Set the default path */ + SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath); + + if (gtk_dialog_run(GTK_DIALOG(widget)) == GTK_RESPONSE_ACCEPT) + { + // write out the file name + *outPath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget)); + return nfdresult_t::OKAY; + } + else return nfdresult_t::CANCEL; +} + +nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, uint_t* count) +{ + assert(pathSet); + + // const_cast because methods on GSList aren't const, but it should act like const to the caller + GSList*fileList = const_cast(static_cast(pathSet)); + + *count = g_slist_length(fileList); + return nfdresult_t::OKAY; +} + +nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet, uint_t index, char** outPath) +{ + assert(pathSet); + + // const_cast because methods on GSList aren't const, but it should act like const to the caller + GSList* fileList = const_cast(static_cast(pathSet)); + + // Note: this takes linear time... but should be good enough + *outPath = static_cast(g_slist_nth_data(fileList, index)); + return nfdresult_t::OKAY; +} + +void NFD_PathSet_FreePathN(const char* filePath) +{ + assert(filePath); + // no-op, because NFD_PathSet_Free does the freeing for us +} + +void NFD_PathSet_Free(const nfdpathset_t *pathSet) +{ + assert(pathSet); + + // const_cast because methods on GSList aren't const, but it should act like const to the caller + GSList* fileList = const_cast(static_cast(pathSet)); + + // free all the nodes + for (GSList* node = fileList; node; node = node->next) + { + assert(node->data); + g_free(node->data); + } + + // free the path set memory + g_slist_free(fileList); +} + + + diff --git a/src/monitor/libs/gtkfd.OLD/gtkfd.hpp b/src/monitor/libs/gtkfd.OLD/gtkfd.hpp new file mode 100644 index 0000000..bda3f88 --- /dev/null +++ b/src/monitor/libs/gtkfd.OLD/gtkfd.hpp @@ -0,0 +1,244 @@ +#pragma once + +#include // for std::unique_ptr +#include +#include +#include "monitor/utils/using.hpp" + +namespace monitor::libs +{ + class gtkfd + { + struct item + { + const char* m_name; + const char* m_spec; + }; + + }; +} + +using nfdpathset_t = GSList; + +struct item +{ + const char* m_name; + const char* m_spec; +}; + +enum class nfdresult_t +{ + OKAY, /* user pressed okay, or successful return */ + ERROR, /* programmatic error */ + CANCEL /* user pressed cancel */ +}; + +/* call this to de-initialize NFD, if NFD_Init returned NFD_OKAY */ +void NFD_Quit(void); + +/* free a file path that was returned by the dialogs */ +/* Note: use NFD_PathSet_FreePath to free path from pathset instead of this function */ +void NFD_FreePathN(char* filePath); + +/* single file open dialog */ +/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns NFD_OKAY */ +/* If filterCount is zero, filterList is ignored (you can use NULL) */ +/* If defaultPath is NULL, the operating system will decide */ +nfdresult_t NFD_OpenDialogN(char** outPath, const item* filterList, uint_t filterCount, const char* defaultPath); + +/* multiple file open dialog */ +/* It is the caller's responsibility to free `outPaths` via NFD_PathSet_Free() if this function returns NFD_OKAY */ +/* If filterCount is zero, filterList is ignored (you can use NULL) */ +/* If defaultPath is NULL, the operating system will decide */ +nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths, const item* filterList, uint_t filterCount, const char* defaultPath); + +/* save dialog */ +/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns NFD_OKAY */ +/* If filterCount is zero, filterList is ignored (you can use NULL) */ +/* If defaultPath is NULL, the operating system will decide */ +nfdresult_t NFD_SaveDialogN(char** outPath, const item* filterList, uint_t filterCount, const char* defaultPath, const char* defaultName); + +/* select folder dialog */ +/* It is the caller's responsibility to free `outPath` via NFD_FreePathN() if this function returns NFD_OKAY */ +/* If defaultPath is NULL, the operating system will decide */ +nfdresult_t NFD_PickFolderN(char** outPath, const char* defaultPath); + +/* Get last error -- set when nfdresult_t returns NFD_ERROR */ +/* Returns the last error that was set, or NULL if there is no error. */ +/* The memory is owned by NFD and should not be freed by user code. */ +/* This is *always* ASCII printable characters, so it can be interpreted as UTF-8 without any conversion. */ +const char* NFD_GetError(void); + +nfdresult_t Init(); + +/* clear the error */ +void NFD_ClearError(void); + +/* get the number of entries stored in pathSet */ +/* note that some paths might be invalid (NFD_ERROR will be returned by NFD_PathSet_GetPath), so we might not actually have this number of usable paths */ +nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, uint_t* count); + +/* Get the UTF-8 path at offset index */ +/* It is the caller's responsibility to free `outPath` via NFD_PathSet_FreePathN() if this function returns NFD_OKAY */ +nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet, uint_t index, char** outPath); + +/* Free the path gotten by NFD_PathSet_GetPathN */ +void NFD_PathSet_FreePathN(const char* filePath); + +/* Free the pathSet */ +void NFD_PathSet_Free(const nfdpathset_t* pathSet); + +namespace gtkfd +{ + inline nfdresult_t init() noexcept + { + return ::Init(); + } + + inline void Quit() noexcept + { + ::NFD_Quit(); + } + + inline void FreePath(char *outPath) noexcept + { + ::NFD_FreePathN(outPath); + } + + inline nfdresult_t OpenDialog(char*& outPath, const item* filterList = nullptr, uint_t filterCount = 0, const char* defaultPath = nullptr) noexcept + { + return ::NFD_OpenDialogN(&outPath, filterList, filterCount, defaultPath); + } + + inline nfdresult_t OpenDialogMultiple(const nfdpathset_t*& outPaths, const item* filterList = nullptr, uint_t filterCount = 0, const char* defaultPath = nullptr) noexcept + { + return ::NFD_OpenDialogMultipleN(&outPaths, filterList, filterCount, defaultPath); + } + + inline nfdresult_t SaveDialog(char*& outPath, const item* filterList = nullptr, uint_t filterCount = 0, const char* defaultPath = nullptr, const char* defaultName = nullptr) noexcept + { + return ::NFD_SaveDialogN(&outPath, filterList, filterCount, defaultPath, defaultName); + } + + inline nfdresult_t PickFolder(char*& outPath, const char* defaultPath = nullptr) noexcept + { + return ::NFD_PickFolderN(&outPath, defaultPath); + } + + inline const char* GetError() noexcept + { + return ::NFD_GetError(); + } + + inline void ClearError() noexcept + { + ::NFD_ClearError(); + } + + namespace PathSet + { + inline nfdresult_t Count(const nfdpathset_t* pathSet, uint_t& count) noexcept + { + return ::NFD_PathSet_GetCount(pathSet, &count); + } + + inline nfdresult_t GetPath(const nfdpathset_t* pathSet, uint_t index, char*& outPath) noexcept + { + return ::NFD_PathSet_GetPathN(pathSet, index, &outPath); + } + + inline void FreePath(char* filePath) noexcept + { + ::NFD_PathSet_FreePathN(filePath); + } + + inline void Free(const nfdpathset_t* pathSet) noexcept + { + ::NFD_PathSet_Free(pathSet); + } + } + + class guard + { + public: + inline guard() noexcept { init(); } + inline ~guard() noexcept { Quit(); } + + // Not allowed to copy or move this class + guard(const guard&) = delete; + guard& operator=(const guard&) = delete; + }; + + template + struct PathDeleter + { + inline void operator()(T* ptr) const noexcept { FreePath(ptr); } + }; + + typedef std::unique_ptr> UniquePath; + typedef std::unique_ptr> UniquePathN; + typedef std::unique_ptr> UniquePathU8; + + struct PathSetDeleter + { + inline void operator()(const nfdpathset_t* ptr) const noexcept { PathSet::Free(ptr); } + }; + + typedef std::unique_ptr UniquePathSet; + + template + struct PathSetPathDeleter + { + inline void operator()(T* ptr) const noexcept { PathSet::FreePath(ptr); } + }; + + typedef std::unique_ptr> UniquePathSetPath; + typedef std::unique_ptr> UniquePathSetPathN; + typedef std::unique_ptr> UniquePathSetPathU8; + + inline nfdresult_t OpenDialog(UniquePathN& outPath, const item* filterList = nullptr, uint_t filterCount = 0, const char* defaultPath = nullptr) noexcept + { + char* out; + nfdresult_t res = OpenDialog(out, filterList, filterCount, defaultPath); + if (res == nfdresult_t::OKAY) outPath.reset(out); + return res; + } + + inline nfdresult_t OpenDialogMultiple(UniquePathSet& outPaths, const item* filterList = nullptr, uint_t filterCount = 0, const char* defaultPath = nullptr) noexcept + { + const nfdpathset_t* out; + nfdresult_t res = OpenDialogMultiple(out, filterList, filterCount, defaultPath); + if (res == nfdresult_t::OKAY) outPaths.reset(out); + return res; + } + + inline nfdresult_t SaveDialog(UniquePathN& outPath, const item* filterList = nullptr, uint_t filterCount = 0, const char* defaultPath = nullptr, const char* defaultName = nullptr) noexcept + { + char* out; + nfdresult_t res = SaveDialog(out, filterList, filterCount, defaultPath, defaultName); + if (res == nfdresult_t::OKAY) outPath.reset(out); + return res; + } + + inline nfdresult_t PickFolder(UniquePathN& outPath, const char* defaultPath = nullptr) noexcept + { + char* out; + nfdresult_t res = PickFolder(out, defaultPath); + if (res == nfdresult_t::OKAY) outPath.reset(out); + return res; + } + + namespace PathSet + { + inline nfdresult_t Count(const UniquePathSet& uniquePathSet, uint_t& count) noexcept { return Count(uniquePathSet.get(), count); } + inline nfdresult_t GetPath(const UniquePathSet& uniquePathSet, uint_t index, UniquePathSetPathN& outPath) noexcept + { + char* out; + nfdresult_t res = GetPath(uniquePathSet.get(), index, out); + if (res == nfdresult_t::OKAY) outPath.reset(out); + return res; + } + } +} + + diff --git a/src/monitor/libs/gtkfd/gtkfd.cpp b/src/monitor/libs/gtkfd/gtkfd.cpp new file mode 100644 index 0000000..027d035 --- /dev/null +++ b/src/monitor/libs/gtkfd/gtkfd.cpp @@ -0,0 +1,42 @@ +#include "gtkfd.hpp" +#include + +namespace monitor::libs +{ + void WaitForCleanup() + { + while (gtk_events_pending()) gtk_main_iteration(); + } + + struct widget_guard + { + GtkWidget* m_widget; + widget_guard(GtkWidget* w) : m_widget(w) {} + ~widget_guard() + { + WaitForCleanup(); + gtk_widget_destroy(m_widget); + WaitForCleanup(); + } + }; + + std::filesystem::path gtkfd::open(const std::filesystem::path& default_path) noexcept + { + std::filesystem::path out_path; + if (!gtk_init_check(NULL, NULL)) + { + hack::error()("Failed init gtk"); + return out_path; + } + + GtkWidget* widget = gtk_file_chooser_dialog_new("Open File", nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, "_Cancel", GTK_RESPONSE_CANCEL, "_Open", GTK_RESPONSE_ACCEPT, nullptr); + widget_guard wg(widget); + + // set the default path + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(widget), default_path.c_str()); + + if (gtk_dialog_run(GTK_DIALOG(widget)) == GTK_RESPONSE_ACCEPT) + out_path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget)); + return out_path; + } +} diff --git a/src/monitor/libs/gtkfd/gtkfd.hpp b/src/monitor/libs/gtkfd/gtkfd.hpp new file mode 100644 index 0000000..ab1ce3a --- /dev/null +++ b/src/monitor/libs/gtkfd/gtkfd.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace monitor::libs +{ + class gtkfd + { + public: + gtkfd() = default; + ~gtkfd() = default; + + public: + std::filesystem::path open(const std::filesystem::path& default_path) noexcept; + std::filesystem::path save(const std::filesystem::path& default_path, const std::string& default_name) noexcept; + }; +} + diff --git a/src/monitor/utils/event_type.hpp b/src/monitor/utils/event_type.hpp index 8fbfbd7..8af24e7 100644 --- a/src/monitor/utils/event_type.hpp +++ b/src/monitor/utils/event_type.hpp @@ -5,6 +5,8 @@ namespace monitor::utils enum class event_type { CREATE_SNAPSHOT, + CREATE_SNAPSHOT_COMPLETED, + SET_AUDIO_POSITION, AUDIO_PAUSE, AUDIO_STOP, diff --git a/src/monitor/utils/var.hpp b/src/monitor/utils/var.hpp index 4a801aa..154e038 100644 --- a/src/monitor/utils/var.hpp +++ b/src/monitor/utils/var.hpp @@ -1,10 +1,7 @@ #pragma once -#include #include -#include "noinc.hpp" - namespace monitor::utils::var { const std::size_t MAX_RENDER_SIZE = 1'200'000;