From 018606adeafb6a8c9d71e786a0f7118ac36df5eb Mon Sep 17 00:00:00 2001 From: Alex Watson Date: Sun, 6 Oct 2024 16:35:46 -0400 Subject: [PATCH 1/3] Dialog -> Widget, Use signals instead of timer, and code cleanup (#1) --- CMakeLists.txt | 15 +- CMakePresets.json | 27 +- data/locale/en-US.ini | 1 + forms/OBSTextMustacheDefinitions.ui | 42 +++ forms/obs-text-mustache-definitions.ui | 52 ---- src/datetime.cpp | 52 ++-- src/datetime.h | 2 + src/obs-text-mustache-definitions.cpp | 350 ++++++++++--------------- src/obs-text-mustache-definitions.hpp | 52 ++-- src/obs-text.cpp | 13 +- src/plugin-main.c | 43 --- src/plugin-main.cpp | 88 +++++++ src/text.cpp | 20 +- src/variables.cpp | 65 +++-- src/variables.hpp | 13 +- 15 files changed, 422 insertions(+), 413 deletions(-) create mode 100644 forms/OBSTextMustacheDefinitions.ui delete mode 100644 forms/obs-text-mustache-definitions.ui delete mode 100644 src/plugin-main.c create mode 100644 src/plugin-main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7babf99..07610cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ include(helpers) add_library(${CMAKE_PROJECT_NAME} MODULE src/datetime.cpp src/datetime.h src/text.cpp src/text.h) find_package(libobs REQUIRED) -target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE OBS::libobs OBS::libobs gdiplus) +target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE OBS::libobs gdiplus) if(ENABLE_FRONTEND_API) find_package(obs-frontend-api REQUIRED) @@ -35,8 +35,15 @@ if(ENABLE_QT) AUTORCC ON) endif() -target_sources(${CMAKE_PROJECT_NAME} PRIVATE forms/obs-text-mustache-definitions.ui) -target_sources(${CMAKE_PROJECT_NAME} PRIVATE src/plugin-main.c src/variables.cpp src/obs-text.cpp - src/obs-text-mustache-definitions.cpp) +target_sources(${CMAKE_PROJECT_NAME} PRIVATE + src/plugin-main.cpp + src/variables.cpp + src/obs-text.cpp + src/obs-text-mustache-definitions.cpp + src/variables.hpp + src/obs-text.hpp + src/obs-text-mustache-definitions.hpp + forms/OBSTextMustacheDefinitions.ui +) set_target_properties_plugin(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME ${_name}) diff --git a/CMakePresets.json b/CMakePresets.json index 44557f4..4ed8e8d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,8 +1,8 @@ { - "version": 3, + "version": 6, "cmakeMinimumRequired": { "major": 3, - "minor": 22, + "minor": 25, "patch": 0 }, "configurePresets": [ @@ -26,7 +26,7 @@ "rhs": "Darwin" }, "generator": "Xcode", - "warnings": {"dev": true, "deprecated": true}, + "warnings": { "dev": true, "deprecated": true }, "cacheVariables": { "QT_VERSION": "6", "CMAKE_OSX_DEPLOYMENT_TARGET": "11.0", @@ -57,7 +57,7 @@ }, "generator": "Visual Studio 17 2022", "architecture": "x64", - "warnings": {"dev": true, "deprecated": true}, + "warnings": { "dev": true, "deprecated": true }, "cacheVariables": { "QT_VERSION": "6", "CMAKE_SYSTEM_VERSION": "10.0.18363.657" @@ -84,7 +84,7 @@ "rhs": "Linux" }, "generator": "Ninja", - "warnings": {"dev": true, "deprecated": true}, + "warnings": { "dev": true, "deprecated": true }, "cacheVariables": { "QT_VERSION": "6", "CMAKE_BUILD_TYPE": "RelWithDebInfo" @@ -112,7 +112,7 @@ "rhs": "Linux" }, "generator": "Ninja", - "warnings": {"dev": true, "deprecated": true}, + "warnings": { "dev": true, "deprecated": true }, "cacheVariables": { "QT_VERSION": "6", "CMAKE_BUILD_TYPE": "RelWithDebInfo" @@ -186,5 +186,20 @@ "description": "Linux CI build for aarch64", "configuration": "RelWithDebInfo" } + ], + "workflowPresets": [ + { + "name": "windows-x64", + "steps": [ + { + "type": "configure", + "name": "windows-x64" + }, + { + "type": "build", + "name": "windows-x64" + } + ] + } ] } diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 7291787..ee63b5e 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -1,3 +1,4 @@ +OBSTextMustacheDefinitions="Text Template Values" TextGDIPlusDefinitions="Edit Text (GDI+) With Templates Variables" TextGDIPlus="Text (GDI+) With Templates" Font="Font" diff --git a/forms/OBSTextMustacheDefinitions.ui b/forms/OBSTextMustacheDefinitions.ui new file mode 100644 index 0000000..c483c53 --- /dev/null +++ b/forms/OBSTextMustacheDefinitions.ui @@ -0,0 +1,42 @@ + + + OBSTextMustacheDefinitions + + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 8 + + + 5 + + + QLayout::SetMinimumSize + + + + + + + + + + diff --git a/forms/obs-text-mustache-definitions.ui b/forms/obs-text-mustache-definitions.ui deleted file mode 100644 index 877e88f..0000000 --- a/forms/obs-text-mustache-definitions.ui +++ /dev/null @@ -1,52 +0,0 @@ - - - OBSTextMustacheDefinitions - - - - 0 - 0 - 697 - 240 - - - - Text Template Values - - - - - - - - QLayout::SetMinimumSize - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - QDialogButtonBox::Close - - - - - - - - - - diff --git a/src/datetime.cpp b/src/datetime.cpp index 63b7738..8cadad4 100644 --- a/src/datetime.cpp +++ b/src/datetime.cpp @@ -10,74 +10,54 @@ #include "datetime.h" using namespace std; +wstring getFormattedTime(const struct tm *const localTime, const wchar_t *format) { + std::wstringstream wss; + wss << std::put_time(localTime, format); + + return wss.str(); +} + wstring getCurrentMonthName(const struct tm *const localTime) { - std::ostringstream oss; - oss << std::put_time(localTime, "%B"); - - return QString::fromStdString(oss.str()).toStdWString(); + return getFormattedTime(localTime, L"%B"); } wstring getCurrentYear(const struct tm *const localTime) { - std::ostringstream oss; - oss << std::put_time(localTime, "%EY"); - - return QString::fromStdString(oss.str()).toStdWString(); + return getFormattedTime(localTime, L"%EY"); } wstring getCurrentDay(const struct tm *const localTime) { - std::ostringstream oss; - oss << std::put_time(localTime, "%e"); - - return QString::fromStdString(oss.str()).toStdWString(); + return getFormattedTime(localTime, L"%e"); } wstring getCurrentDayOfWeek(const struct tm *const localTime) { - std::ostringstream oss; - oss << std::put_time(localTime, "%A"); - - return QString::fromStdString(oss.str()).toStdWString(); + return getFormattedTime(localTime, L"%A"); } wstring getCurrent24Hour(const struct tm *const localTime) { - std::ostringstream oss; - oss << std::put_time(localTime, "%OH"); - - return QString::fromStdString(oss.str()).toStdWString(); + return getFormattedTime(localTime, L"%OH"); } wstring getCurrent12Hour(const struct tm *const localTime) { - std::ostringstream oss; - oss << std::put_time(localTime, "%OI"); - - return QString::fromStdString(oss.str()).toStdWString(); + return getFormattedTime(localTime, L"%OI"); } wstring getCurrentMinute(const struct tm *const localTime) { - std::ostringstream oss; - oss << std::put_time(localTime, "%M"); - - return QString::fromStdString(oss.str()).toStdWString(); + return getFormattedTime(localTime, L"%M"); } wstring getCurrentSecond(const struct tm *const localTime) { - std::ostringstream oss; - oss << std::put_time(localTime, "%S"); - - return QString::fromStdString(oss.str()).toStdWString(); + return getFormattedTime(localTime, L"%S"); } wstring getCurrentAmPm(const struct tm *const localTime) { - std::ostringstream oss; - oss << std::put_time(localTime, "%p"); - - return QString::fromStdString(oss.str()).toStdWString(); + return getFormattedTime(localTime, L"%p"); } diff --git a/src/datetime.h b/src/datetime.h index f950e0e..1a19d1e 100644 --- a/src/datetime.h +++ b/src/datetime.h @@ -6,6 +6,8 @@ #ifndef DATETIME_H #define DATETIME_H +std::wstring getFormattedTime(const tm *localTime, const char *format); + std::wstring getCurrentMonthName(const tm *localTime); std::wstring getCurrentYear(const tm *localTime); diff --git a/src/obs-text-mustache-definitions.cpp b/src/obs-text-mustache-definitions.cpp index 92bae20..54f3da1 100644 --- a/src/obs-text-mustache-definitions.cpp +++ b/src/obs-text-mustache-definitions.cpp @@ -1,270 +1,198 @@ -#include -#include +#include "obs-text-mustache-definitions.hpp" + #include +#include #include #include #include -#include -#include -#include + +#include +#include #include -#include -#include +#include +#include #include -#include +#include #include +#include #include -#include "obs-text-mustache-definitions.hpp" #include "obs-text.hpp" #include "variables.hpp" -using namespace std; +#include "ui_OBSTextMustacheDefinitions.h" -OBSTextMustacheDefinitions *dialog; +using namespace std; const wregex variable_regex(L"\\{\\{(\\w+)\\}\\}"); -static bool findVariables(void *data, obs_source_t *source) +// Constructor +OBSTextMustacheDefinitions::OBSTextMustacheDefinitions(QWidget *parent) + : QWidget(parent), + ui(new Ui_OBSTextMustacheDefinitions) { - VariablesAndValues *variablesAndValues = + ui->setupUi(this); + + VariablesAndValues *const variablesAndValues = VariablesAndValues::getInstance(); - const char *id = obs_source_get_id(source); + //obs_frontend_add_event_callback(OBSEvent, this); - if (!strcmp("text_gdiplus_mustache_v2", id)) { - TextSource *mySource = reinterpret_cast( - obs_obj_get_data(source)); - blog(LOG_DEBUG, - "findVariables: found text_gdiplus_mustache_v2 source with text %s", - QString::fromStdWString(mySource->text) - .toStdString() - .c_str()); - const auto variables_begin = - wsregex_iterator(mySource->text.begin(), - mySource->text.end(), variable_regex); - const auto variables_end = wsregex_iterator(); - for (wsregex_iterator i = variables_begin; i != variables_end; - ++i) { - const wsmatch match = *i; - const wstring match_str = match.str(1); - const QString variable = - QString::fromStdWString(match_str); - blog(LOG_DEBUG, - "findVariables: found variable %s in the scene", - variable.toStdString().c_str()); - if (!variablesAndValues->contains(variable)) { - blog(LOG_DEBUG, - "findVariables: adding variable %s", - variable.toStdString().c_str()); - variablesAndValues->putVariable(variable); - } - } - } - return true; + auto * signalHandler = obs_get_signal_handler(); + //signal_handler_connect_global(obs_get_signal_handler(), OBSSignal, this); + signal_handler_connect(signalHandler, "source_update", obsSourceUpdated, this); + signal_handler_connect(signalHandler, "source_remove", obsSourceRemoved, this); + + hide(); } -static bool updateText(void *data, obs_source_t *source) -{ +// OBS Signal Handlers +void OBSTextMustacheDefinitions::obsSourceSignalHandler(void *data, calldata_t *call_data, const char* callbackMethod, bool includeSourceParam = false) { + obs_source_t *source = + static_cast(calldata_ptr(call_data, "source")); const char *id = obs_source_get_id(source); - if (!strcmp("text_gdiplus_mustache_v2", id)) { - TextSource *mySource = reinterpret_cast( - obs_obj_get_data(source)); - mySource->UpdateTextToRender(); - } - return true; -} -static void loadVariablesAndValues(obs_data_t *data, void *param) -{ - VariablesAndValues *const variablesAndValues = - VariablesAndValues::getInstance(); - const auto variables = variablesAndValues->getVariables(); + if (!source || strcmp("text_gdiplus_mustache_v2", id)) + return; - variablesAndValues->putValue(obs_data_get_string(data, "variable"), - obs_data_get_string(data, "value")); -} + OBSTextMustacheDefinitions *mustache = static_cast(data); -OBSTextMustacheDefinitions::OBSTextMustacheDefinitions(QWidget *parent) - : QDialog(parent), - ui(new Ui_OBSTextMustacheDefinitions) -{ - ui->setupUi(this); - - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + if(includeSourceParam) { + QMetaObject::invokeMethod(mustache, callbackMethod, + Qt::QueuedConnection, source); + } else { + QMetaObject::invokeMethod(mustache, callbackMethod, + Qt::QueuedConnection); + } +} - QObject::connect(ui->buttonBox->button(QDialogButtonBox::Close), - &QPushButton::clicked, this, - &OBSTextMustacheDefinitions::hide); - QObject::connect(ui->buttonBox->button(QDialogButtonBox::Close), - &QPushButton::clicked, this, - &OBSTextMustacheDefinitions::HideDialog); - QObject::connect(&timer, SIGNAL(timeout()), SLOT(TimerTextUpdate())); - timer.start(250); +void OBSTextMustacheDefinitions::obsSourceUpdated(void *data, calldata_t *call_data) { + OBSTextMustacheDefinitions::obsSourceSignalHandler(data, call_data, "SignalSourceUpdate", true); } -void OBSTextMustacheDefinitions::closeEvent(QCloseEvent *) -{ - obs_frontend_save(); +void OBSTextMustacheDefinitions::obsSourceRemoved(void *data, calldata_t *call_data) { + OBSTextMustacheDefinitions::obsSourceSignalHandler(data, call_data, "VerifyKnownTemplateSources"); } -void OBSTextMustacheDefinitions::ShowDialog() -{ - VariablesAndValues *const variablesAndValues = - VariablesAndValues::getInstance(); - ui->gridLayout->setColumnStretch(0, 1); - ui->gridLayout->setColumnStretch(1, 2); - obs_enum_sources(findVariables, NULL); - const auto variables = variablesAndValues->getVariables(); - int currentRow = 0; - textLines.clear(); - for (auto it = variables.begin(); it != variables.end(); - ++it, ++currentRow) { - - QLabel *label = new QLabel(*it); - label->setAlignment(Qt::AlignVCenter); - ui->gridLayout->addWidget(label, currentRow, 0); - QLineEdit *lineEdit = - new QLineEdit(variablesAndValues->getValue(*it)); - textLines[*it] = lineEdit; - ui->gridLayout->addWidget(lineEdit, currentRow, 1); - } - setVisible(true); - QTimer::singleShot(250, this, &OBSTextMustacheDefinitions::show); +void OBSTextMustacheDefinitions::SignalSourceUpdate(obs_source *source) { + blog(LOG_INFO, "OBSTextMustacheDefinitions::SignalSourceUpdate called"); + AddOrUpdateTemplateSource(source); + UpdateUI(); + + UpdateRenderedText(); } -void OBSTextMustacheDefinitions::HideDialog() -{ - VariablesAndValues *const variablesAndValues = - VariablesAndValues::getInstance(); +// Template Searching +void OBSTextMustacheDefinitions::AddOrUpdateTemplateSource(obs_source_t *source) { + const char *id = obs_source_get_id(source); + auto *source_ref = obs_source_get_ref(source); - setVisible(false); - const auto variables = variablesAndValues->getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) { - const auto variable = *it; - const auto value = textLines[*it]->text(); - variablesAndValues->putValue(variable, value); - blog(LOG_DEBUG, "HideDialog: Setting variable %s to %s", - variable.toStdString().c_str(), - value.toStdString().c_str()); + if (!strcmp("text_gdiplus_mustache_v2", id) && !OBSTextMustacheDefinitions::templateSources.count(source_ref)) { + OBSTextMustacheDefinitions::templateSources.insert(source_ref); + } else { + obs_source_release(source_ref); } - obs_enum_sources(updateText, NULL); - - QTimer::singleShot(250, this, &OBSTextMustacheDefinitions::hide); } -void OBSTextMustacheDefinitions::TimerTextUpdate() -{ - obs_enum_sources(updateText, NULL); - timer.start(250); +void OBSTextMustacheDefinitions::VerifyKnownTemplateSources() { + for (auto &source_ref : OBSTextMustacheDefinitions::templateSources) { + if(obs_source_removed(source_ref)) { + obs_source_release(source_ref); + OBSTextMustacheDefinitions::templateSources.erase(source_ref); + } + } } -static void SaveOBSTextMustacheDefinitions(obs_data_t *save_data, bool saving, - void *) +// Variable Handlers +void OBSTextMustacheDefinitions::FindVariables() { VariablesAndValues *variablesAndValues = VariablesAndValues::getInstance(); - if (saving) { - const OBSDataAutoRelease obj = obs_data_create(); - const OBSDataArrayAutoRelease array = obs_data_array_create(); - const auto variables = variablesAndValues->getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) { - const QString variable = *it; - const QString value = variablesAndValues->getValue(*it); - blog(LOG_INFO, - "SaveOBSTextMustacheDefinitions: Considering saving variable %s", - variable.toStdString().c_str()); - if (value.size() > 0) { - const OBSDataAutoRelease keyValue = - obs_data_create(); - blog(LOG_INFO, - "SaveOBSTextMustacheDefinitions: Saving variable %s as %s", - variable.toStdString().c_str(), - value.toStdString().c_str()); - obs_data_set_string( - keyValue, "variable", - variable.toStdString().c_str()); - obs_data_set_string( - keyValue, "value", - value.toStdString().c_str()); - - obs_data_array_push_back(array, keyValue); - - blog(LOG_INFO, - "SaveOBSTextMustacheDefinitions: Done saving variable %s as %s", - variable.toStdString().c_str(), - value.toStdString().c_str()); - } + + std::set newVariablesList; + + for (auto &source_ref : OBSTextMustacheDefinitions::templateSources) { + TextSource *mySource = reinterpret_cast( + obs_obj_get_data(source_ref)); + + const auto variables_begin = + wsregex_iterator(mySource->text.begin(), + mySource->text.end(), variable_regex); + const auto variables_end = wsregex_iterator(); + for (wsregex_iterator i = variables_begin; i != variables_end; + ++i) { + const wsmatch match = *i; + const wstring match_str = match.str(1); + const QString variable = + QString::fromStdWString(match_str); + blog(LOG_INFO, + "findVariables: found new variable %s in the scene", + variable.toStdString()); + newVariablesList.insert(variable); } - obs_data_set_array(obj, "variablesAndValues", array); - blog(LOG_INFO, - "SaveOBSTextMustacheDefinitions: About to save data"); - obs_data_set_obj(save_data, "obs-text-mustache", obj); - // variablesAndValues->clear(); - blog(LOG_INFO, - "SaveOBSTextMustacheDefinitions: Done saving data"); - } else { - OBSDataAutoRelease obj = - obs_data_get_obj(save_data, "obs-text-mustache"); - - if (!obj) - obj = obs_data_create(); - blog(LOG_INFO, - "SaveOBSTextMustacheDefinitions: loading variables"); - variablesAndValues->clear(); - obs_data_array_enum(obs_data_get_array(obj, - "variablesAndValues"), - loadVariablesAndValues, NULL); } + + variablesAndValues->updateVariables(newVariablesList); } -extern "C" void FreeOBSTextMustacheDefinitions() {} +void OBSTextMustacheDefinitions::UpdateTemplatedValue(const QString &variable) { + blog(LOG_DEBUG, "OBSTextMustacheDefinitions::UpdateVariables called"); + VariablesAndValues *const variablesAndValues = + VariablesAndValues::getInstance(); -extern "C" void ResetDialog() -{ - QMainWindow *window = (QMainWindow *)obs_frontend_get_main_window(); + const auto value = textLines[variable]->text(); + variablesAndValues->putValue(variable, value); + blog(LOG_DEBUG, "UpdateVariables: Setting variable %s to %s", + variable.toStdString(), + value.toStdString()); - dialog = new OBSTextMustacheDefinitions(window); + UpdateRenderedText(); } -static void OBSEvent(enum obs_frontend_event event, void *) +// Source Interaction +void OBSTextMustacheDefinitions::UpdateRenderedText() { - blog(LOG_DEBUG, "OBSEvent: %d", event); - switch (event) { - case OBS_FRONTEND_EVENT_EXIT: { - obs_frontend_save(); - FreeOBSTextMustacheDefinitions(); - break; - } - case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP: { - VariablesAndValues *variablesAndValues = - VariablesAndValues::getInstance(); - variablesAndValues->clear(); - ResetDialog(); - } - case OBS_FRONTEND_EVENT_FINISHED_LOADING: { - obs_enum_sources(updateText, NULL); - break; - } + for (const auto &source_ref : OBSTextMustacheDefinitions::templateSources) { + TextSource *mySource = reinterpret_cast( + obs_obj_get_data(source_ref)); + mySource->UpdateTextToRender(); } } -extern "C" void InitOBSTextMustacheDefinitions() +// Widget Rendering +void OBSTextMustacheDefinitions::UpdateUI() { - QAction *const action = (QAction *)obs_frontend_add_tools_menu_qaction( - obs_module_text("TextGDIPlusDefinitions")); - - obs_frontend_push_ui_translation(obs_module_get_string); + VariablesAndValues *const variablesAndValues = + VariablesAndValues::getInstance(); - ResetDialog(); + FindVariables(); - const auto cb = []() { - dialog->ShowDialog(); - }; + // Remove text fields for variables that no longer exist + for (const auto &[textVar, textField] : textLines) { + if(!variablesAndValues->contains(textVar)) { + ui->gridLayout->removeRow(textField); + textLines.erase(textVar); + } + } - obs_frontend_pop_ui_translation(); + // Add text fields for new variables + for(const auto &variable : variablesAndValues->getVariables()) { + if(textLines.count(variable)) { + continue; + } - obs_frontend_add_save_callback(SaveOBSTextMustacheDefinitions, nullptr); - obs_frontend_add_event_callback(OBSEvent, nullptr); + QLabel *label = new QLabel(variable); + label->setAlignment(Qt::AlignVCenter); + QLineEdit *lineEdit = + new QLineEdit(variablesAndValues->getValue(variable)); + QObject::connect(lineEdit, &QLineEdit::textChanged,[=](){this->UpdateTemplatedValue(variable);}); + textLines[variable] = lineEdit; + ui->gridLayout->addRow(label, lineEdit); + } +} - action->connect(action, &QAction::triggered, cb); -} \ No newline at end of file +// Destructor +OBSTextMustacheDefinitions::~OBSTextMustacheDefinitions() { + auto * signalHandler = obs_get_signal_handler(); + signal_handler_disconnect(signalHandler, "source_update", obsSourceUpdated, this); + signal_handler_disconnect(signalHandler, "source_remove", obsSourceRemoved, this); +} diff --git a/src/obs-text-mustache-definitions.hpp b/src/obs-text-mustache-definitions.hpp index 02ee63b..4a535c3 100644 --- a/src/obs-text-mustache-definitions.hpp +++ b/src/obs-text-mustache-definitions.hpp @@ -1,30 +1,42 @@ #pragma once -#include +#include +#include #include +#include +#include #include +#include +#include #include #include +#include -#include "ui_obs-text-mustache-definitions.h" +#include "ui_OBSTextMustacheDefinitions.h" -class QCloseEvent; - -class OBSTextMustacheDefinitions : public QDialog { +class OBSTextMustacheDefinitions : public QWidget { Q_OBJECT -public: - std::unique_ptr ui; - OBSTextMustacheDefinitions(QWidget *parent); - - void closeEvent(QCloseEvent *event) override; - -public slots: - void ShowDialog(); - void HideDialog(); - void TimerTextUpdate(); - -private: - std::map textLines; - QTimer timer; -}; \ No newline at end of file + private: + static void obsSourceSignalHandler(void *data, calldata_t *call_data, const char* callbackMethod, bool includeSourceParam); + static void obsSourceUpdated(void *data, calldata_t *call_data); + static void obsSourceRemoved(void *data, calldata_t *call_data); + void FindVariables(); + void UpdateRenderedText(); + void UpdateUI(); + std::unique_ptr ui; + std::map textLines; + void AddOrUpdateTemplateSource(obs_source_t *source); + + QSignalMapper *lineEditSignalMapper; + + private slots: + void VerifyKnownTemplateSources(); + void SignalSourceUpdate(obs_source *source); + void UpdateTemplatedValue(const QString &variable); + + public: + OBSTextMustacheDefinitions(QWidget *parent = nullptr); + ~OBSTextMustacheDefinitions(); + inline static std::set templateSources; +}; diff --git a/src/obs-text.cpp b/src/obs-text.cpp index a357698..a9d2cd5 100644 --- a/src/obs-text.cpp +++ b/src/obs-text.cpp @@ -764,7 +764,7 @@ inline void TextSource::Render() void TextSource::UpdateTextToRender() { blog(LOG_DEBUG, "UpdateTextToRender: initial text_to_render %s", - QString::fromStdWString(text_to_render).toStdString().c_str()); + QString::fromStdWString(text_to_render).toStdString()); text_to_render = text; text_to_render = evaluateConditionals(text_to_render); text_to_render = replaceVariables(text_to_render); @@ -780,11 +780,6 @@ static ULONG_PTR gdip_token = 0; //OBS_DECLARE_MODULE() //OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US") -MODULE_EXPORT const char *obs_module_description(void) -{ - return "Windows GDI+ text source with templating"; -} - #define set_vis(var, val, show) \ do { \ p = obs_properties_get(props, val); \ @@ -993,7 +988,7 @@ static void missing_file_callback(void *src, const char *new_path, void *data) UNUSED_PARAMETER(data); } -extern "C" bool InitOBSText() +bool InitOBSText() { obs_source_info si = {}; si.id = "text_gdiplus_mustache"; @@ -1072,7 +1067,7 @@ extern "C" bool InitOBSText() return true; } -extern "C" void FreeOBSText() +void FreeOBSText() { GdiplusShutdown(gdip_token); -} \ No newline at end of file +} diff --git a/src/plugin-main.c b/src/plugin-main.c deleted file mode 100644 index 26aa809..0000000 --- a/src/plugin-main.c +++ /dev/null @@ -1,43 +0,0 @@ -/* -Plugin Name -Copyright (C) 2024 Mark Jones - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program. If not, see -*/ - -#include -#include -#include "plugin-lifetime.hpp" -#include "types.h" - -OBS_DECLARE_MODULE() -OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US") - -void InitOBSTextMustacheDefinitions(); -void ResetDialog(); -void FreeOBSTextMustacheDefinitions(); - -bool obs_module_load(void) -{ - InitOBSTextMustacheDefinitions(); - InitOBSText(); - return true; -} - -void obs_module_unload(void) -{ - FreeOBSTextMustacheDefinitions(); - FreeOBSText(); - obs_log(LOG_INFO, "plugin unloaded"); -} diff --git a/src/plugin-main.cpp b/src/plugin-main.cpp new file mode 100644 index 0000000..464be74 --- /dev/null +++ b/src/plugin-main.cpp @@ -0,0 +1,88 @@ +/* +Plugin Name +Copyright (C) 2024 Mark Jones + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "obs-text-mustache-definitions.hpp" +#include "variables.hpp" +#include "plugin-lifetime.hpp" +#include "types.h" + +OBS_DECLARE_MODULE() +OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US") + +bool obs_module_load() +{ + InitOBSText(); + auto *window = (QMainWindow *)obs_frontend_get_main_window(); + + obs_frontend_push_ui_translation(obs_module_get_string); + + char *configPath = obs_module_config_path(""); + try { + if(!std::filesystem::create_directory(configPath)) { + blog(LOG_DEBUG, "obs-text-mustache-definitions data directory already exists, continuing."); + } + } catch(std::exception){ + blog(LOG_INFO, "obs-text-mustache-definitions data directory could not be created."); + } + + bfree(configPath); + + auto *obsTextMustache = new OBSTextMustacheDefinitions(window); + + //const QString title = QString::fromUtf8(obs_module_text("Text Template Values")); + const QString title = QString::fromUtf8(obs_module_text("OBSTextMustacheDefinitions")); + const auto name = "OBSTextMustacheDefinitions"; + + obs_frontend_add_dock_by_id(name, title.toUtf8().constData(), + obsTextMustache); + + obs_frontend_pop_ui_translation(); + + blog(LOG_INFO, "obs-text-mustache-definitions loaded successfully."); + return true; +} + +void obs_module_unload() +{ + VariablesAndValues *const variablesAndValues = + VariablesAndValues::getInstance(); + FreeOBSText(); + variablesAndValues->storeAll(); + obs_log(LOG_INFO, "plugin unloaded"); +} + +MODULE_EXPORT const char *obs_module_description(void) +{ + return "Windows GDI+ text source with templating"; +} + +MODULE_EXPORT const char *obs_module_name(void) +{ + return obs_module_text("OBSTextMustache"); +} diff --git a/src/text.cpp b/src/text.cpp index f0fab3e..dc37916 100644 --- a/src/text.cpp +++ b/src/text.cpp @@ -26,12 +26,12 @@ wstring evaluateConditionals(const std::wstring &initial) const QString variable = QString::fromStdWString(match_str); VariablesAndValues *variablesAndValues = VariablesAndValues::getInstance(); - // blog(LOG_INFO, "found conditional for %s", variable.toStdString().c_str()); + // blog(LOG_INFO, "found conditional for %s", variable.toStdString()); if (variablesAndValues->contains(variable) && !variablesAndValues->getValue(variable) .toStdWString() .empty()) { - // blog(LOG_INFO, "replacing conditional for %s", variable.toStdString().c_str()); + // blog(LOG_INFO, "replacing conditional for %s", variable.toStdString()); wstringstream buf; buf << L"\\{\\{#if " << variable.toStdWString() << L"\\}\\}(.*)\\{\\{/if " @@ -66,17 +66,14 @@ wstring replaceVariables(const wstring &initial) wstring text_to_render = initial; VariablesAndValues *variablesAndValues = VariablesAndValues::getInstance(); - const auto variables = variablesAndValues->getVariables(); - for (auto it = variables.begin(); it != variables.end(); ++it) { - const wstring value = - variablesAndValues->getValue(*it).toStdWString(); + const auto variables = variablesAndValues->getAll(); + for (const auto &[variable, value]: variables) { if (value.size() > 0) { - const wstring variable = (*it).toStdWString(); wstringstream buf; - buf << L"\\{\\{" << variable << L"\\}\\}"; + buf << L"\\{\\{" << (variable.toStdWString()) << L"\\}\\}"; const wregex re(buf.str()); text_to_render = - regex_replace(text_to_render, re, value); + regex_replace(text_to_render, re, value.toStdWString()); blog(LOG_DEBUG, "UpdateTextToRender: text_to_render %s", QString::fromStdWString(text_to_render) .toStdString() @@ -84,7 +81,7 @@ wstring replaceVariables(const wstring &initial) } } blog(LOG_DEBUG, "replaceVariables: final text_to_render %s", - QString::fromStdWString(text_to_render).toStdString().c_str()); + QString::fromStdWString(text_to_render).toStdString()); return text_to_render; } @@ -93,6 +90,7 @@ wstring replaceDateTimes(const wstring &initial) wstring text_to_render = initial; time_t currentTime = time(nullptr); struct tm *localTime = localtime(¤tTime); + // Note: Can't this just formatted using strftime? text_to_render = regex_replace(text_to_render, wregex(L"\\{\\{DateTime month\\}\\}"), @@ -126,6 +124,6 @@ wstring replaceDateTimes(const wstring &initial) getCurrentAmPm(localTime)); blog(LOG_DEBUG, "replaceDateTimes: final text_to_render %s", - QString::fromStdWString(text_to_render).toStdString().c_str()); + QString::fromStdWString(text_to_render).toStdString()); return text_to_render; } diff --git a/src/variables.cpp b/src/variables.cpp index b7e9f1a..f62bc41 100644 --- a/src/variables.cpp +++ b/src/variables.cpp @@ -1,3 +1,5 @@ +#include +#include #include #include "variables.hpp" @@ -9,31 +11,51 @@ VariablesAndValues *VariablesAndValues::getInstance() { if (self == nullptr) { self = new VariablesAndValues(); + + self->dataStorage = obs_data_create(); + self->dataStoragePath = obs_module_config_path("variables.json"); + + auto *existingData = obs_data_create_from_json_file_safe(self->dataStoragePath, "bak"); + obs_data_apply(self->dataStorage, existingData); } return self; } +void VariablesAndValues::updateVariables(std::set updatedList) { + for(auto &variable : variables) { + if(!updatedList.count(variable)) { + obs_data_erase(dataStorage, variable.toUtf8()); + } + } + + variables.clear(); + variables = updatedList; +} + void VariablesAndValues::putVariable(const QString &variable) { - blog(LOG_INFO, "VariablesAndValues::putVariable %s", - variable.toStdString().c_str()); - if (variablesAndValues.find(variable) == variablesAndValues.end()) { - variablesAndValues[variable] = QString(""); + blog(LOG_DEBUG, "VariablesAndValues::putVariable: Parsing found variable %s", + variable.toStdString()); + + if(!variables.count(variable)) { variables.insert(variable); } } bool VariablesAndValues::contains(const QString &variable) { - return variables.find(variable) != variables.end(); + return variables.count(variable) != 0; } void VariablesAndValues::putValue(const QString &variable, const QString &value) { - blog(LOG_INFO, "VariablesAndValues::putValue %s %s", - variable.toStdString().c_str(), value.toStdString().c_str()); - variablesAndValues[variable] = value; - variables.insert(variable); + blog(LOG_INFO, "VariablesAndValues::putValue: Variable %s, Value %s", + variable.toStdString(), value.toStdString()); + + putVariable(variable); + obs_data_set_string( + dataStorage, variable.toUtf8(), + value.toUtf8()); } const set &VariablesAndValues::getVariables() @@ -41,15 +63,24 @@ const set &VariablesAndValues::getVariables() return variables; } -const QString &VariablesAndValues::getValue(const QString &variable) +const QString VariablesAndValues::getValue(const QString &variable) { - return variablesAndValues[variable]; + return QString::fromUtf8(obs_data_get_string(dataStorage, variable.toUtf8())); } -void VariablesAndValues::clear() -{ - blog(LOG_INFO, "VariablesAndValues::clear"); +void VariablesAndValues::storeAll() { + obs_data_save_json_safe(dataStorage, dataStoragePath, "json", "bak"); +} - variablesAndValues.clear(); - variables.clear(); -} \ No newline at end of file +std::map VariablesAndValues::getAll() { + std::map storedValues; + for(const auto &variable : variables) { + storedValues[variable] = getValue(variable); + } + return storedValues; +} + +VariablesAndValues::~VariablesAndValues() { + bfree(dataStoragePath); + obs_data_release(dataStorage); +} diff --git a/src/variables.hpp b/src/variables.hpp index aa7cc45..4d4fa07 100644 --- a/src/variables.hpp +++ b/src/variables.hpp @@ -2,22 +2,27 @@ #include #include #include +#include +#include class VariablesAndValues { public: static VariablesAndValues *getInstance(); - void clear(); + void storeAll(); + std::map getAll(); + void updateVariables(std::set updatedList); void putVariable(const QString &variable); void putValue(const QString &variable, const QString &value); const std::set &getVariables(); - const QString &getValue(const QString &variable); + const QString getValue(const QString &variable); bool contains(const QString &variable); private: static VariablesAndValues *self; - - std::map variablesAndValues; + obs_data_t *dataStorage; + char *dataStoragePath; std::set variables; VariablesAndValues() {} + ~VariablesAndValues(); }; From 7d90ce4a5725ac305d3f861afe10b65706f22da6 Mon Sep 17 00:00:00 2001 From: Alex Watson Date: Sun, 20 Oct 2024 20:12:50 +0000 Subject: [PATCH 2/3] Implementing changes suggested in pull request --- src/plugin-main.cpp | 4 ++-- src/variables.cpp | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/plugin-main.cpp b/src/plugin-main.cpp index 464be74..38fdffd 100644 --- a/src/plugin-main.cpp +++ b/src/plugin-main.cpp @@ -55,10 +55,10 @@ bool obs_module_load() auto *obsTextMustache = new OBSTextMustacheDefinitions(window); - //const QString title = QString::fromUtf8(obs_module_text("Text Template Values")); - const QString title = QString::fromUtf8(obs_module_text("OBSTextMustacheDefinitions")); const auto name = "OBSTextMustacheDefinitions"; + const QString title = QString::fromUtf8(obs_module_text(name)); + obs_frontend_add_dock_by_id(name, title.toUtf8().constData(), obsTextMustache); diff --git a/src/variables.cpp b/src/variables.cpp index f62bc41..313ff50 100644 --- a/src/variables.cpp +++ b/src/variables.cpp @@ -21,7 +21,7 @@ VariablesAndValues *VariablesAndValues::getInstance() return self; } -void VariablesAndValues::updateVariables(std::set updatedList) { +void VariablesAndValues::updateVariables(const std::set &updatedList) { for(auto &variable : variables) { if(!updatedList.count(variable)) { obs_data_erase(dataStorage, variable.toUtf8()); @@ -53,9 +53,7 @@ void VariablesAndValues::putValue(const QString &variable, const QString &value) variable.toStdString(), value.toStdString()); putVariable(variable); - obs_data_set_string( - dataStorage, variable.toUtf8(), - value.toUtf8()); + obs_data_set_string(dataStorage, variable.toUtf8(), value.toUtf8()); } const set &VariablesAndValues::getVariables() From 87b9c7df7b94b8da7be380b9f2d93debeb649fca Mon Sep 17 00:00:00 2001 From: Alex Watson Date: Sun, 20 Oct 2024 16:16:30 -0400 Subject: [PATCH 3/3] Apply signature change to header file --- src/variables.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variables.hpp b/src/variables.hpp index 4d4fa07..05373be 100644 --- a/src/variables.hpp +++ b/src/variables.hpp @@ -10,7 +10,7 @@ class VariablesAndValues { static VariablesAndValues *getInstance(); void storeAll(); std::map getAll(); - void updateVariables(std::set updatedList); + void updateVariables(const std::set &updatedList); void putVariable(const QString &variable); void putValue(const QString &variable, const QString &value); const std::set &getVariables();