add creator

This commit is contained in:
2026-03-13 16:44:31 +03:00
parent eb3f5c3bf4
commit 05738cb839
21 changed files with 897 additions and 246 deletions

View File

@@ -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',

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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<int>(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<int>(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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -1,85 +0,0 @@
#include "monitor/gui/components/creator/creator.hpp"
#include <harmonica.hpp>
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();
}
}

View File

@@ -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);

View File

@@ -3,6 +3,7 @@
#include <VE.hpp>
#include <harmonica.hpp>
#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<std::string> m_dirs;
std::vector<std::string> 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);
};
}

View File

@@ -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);
}
}

View File

@@ -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;
};
}

View File

@@ -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)

View File

@@ -0,0 +1,500 @@
#include <stdio.h>
#include <stddef.h>
#include <assert.h>
#include <string.h>
#include "gtkfd.hpp"
template <typename T>
struct Free_Guard
{
T* data;
Free_Guard(T* freeable) noexcept : data(freeable) {}
~Free_Guard() { NFDi_Free(data); }
};
template <typename T>
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 <typename T = void>
T* NFDi_Malloc(size_t bytes)
{
void* ptr = malloc(bytes);
if (!ptr) NFDi_SetError("NFDi_Malloc failed.");
return static_cast<T*>(ptr);
}
template <typename T>
void NFDi_Free(T *ptr)
{
assert(ptr);
free(static_cast<void*>(ptr));
}
template <typename T>
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<char>(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<char>(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<Pair_GtkFileFilter_FileExtension>(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<char>(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<char>(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<ButtonClickedArgs*>(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<Pair_GtkFileFilter_FileExtension*>(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<char>(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<void*>(&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<GSList*>(static_cast<const GSList*>(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<GSList*>(static_cast<const GSList*>(pathSet));
// Note: this takes linear time... but should be good enough
*outPath = static_cast<char*>(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<GSList*>(static_cast<const GSList*>(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);
}

View File

@@ -0,0 +1,244 @@
#pragma once
#include <memory> // for std::unique_ptr
#include <stddef.h>
#include <gtk/gtk.h>
#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 <typename T>
struct PathDeleter
{
inline void operator()(T* ptr) const noexcept { FreePath(ptr); }
};
typedef std::unique_ptr<char, PathDeleter<char>> UniquePath;
typedef std::unique_ptr<char, PathDeleter<char>> UniquePathN;
typedef std::unique_ptr<char, PathDeleter<char>> UniquePathU8;
struct PathSetDeleter
{
inline void operator()(const nfdpathset_t* ptr) const noexcept { PathSet::Free(ptr); }
};
typedef std::unique_ptr<const nfdpathset_t, PathSetDeleter> UniquePathSet;
template <typename T>
struct PathSetPathDeleter
{
inline void operator()(T* ptr) const noexcept { PathSet::FreePath(ptr); }
};
typedef std::unique_ptr<char, PathSetPathDeleter<char>> UniquePathSetPath;
typedef std::unique_ptr<char, PathSetPathDeleter<char>> UniquePathSetPathN;
typedef std::unique_ptr<char, PathSetPathDeleter<char>> 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;
}
}
}

View File

@@ -0,0 +1,42 @@
#include "gtkfd.hpp"
#include <hack/logger/logger.hpp>
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;
}
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include <filesystem>
#include <gtk/gtk.h>
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;
};
}

View File

@@ -5,6 +5,8 @@ namespace monitor::utils
enum class event_type
{
CREATE_SNAPSHOT,
CREATE_SNAPSHOT_COMPLETED,
SET_AUDIO_POSITION,
AUDIO_PAUSE,
AUDIO_STOP,

View File

@@ -1,10 +1,7 @@
#pragma once
#include <string>
#include <hack/logger/logger.hpp>
#include "noinc.hpp"
namespace monitor::utils::var
{
const std::size_t MAX_RENDER_SIZE = 1'200'000;