diff --git a/CMakeLists.txt b/CMakeLists.txt index 10b46f26..f86c94e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,8 +145,10 @@ target_sources( # Utility function sources target_sources( ${LIB_NAME} - PRIVATE lib/utils/action-queue.cpp - lib/utils/action-queue.hpp + PRIVATE lib/queue/action-queue.cpp + lib/queue/action-queue.hpp + lib/queue/action-queue-tab.cpp + lib/queue/action-queue-tab.hpp lib/utils/backup.cpp lib/utils/backup.hpp lib/utils/curl-helper.cpp @@ -275,6 +277,7 @@ target_include_directories( PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/lib" "${CMAKE_CURRENT_SOURCE_DIR}/lib/legacy" "${CMAKE_CURRENT_SOURCE_DIR}/lib/macro" + "${CMAKE_CURRENT_SOURCE_DIR}/lib/queue" "${CMAKE_CURRENT_SOURCE_DIR}/lib/utils" "${CMAKE_CURRENT_SOURCE_DIR}/lib/variables" "${CMAKE_CURRENT_BINARY_DIR}/forms") diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 28d0dca7..28779ac8 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -88,6 +88,20 @@ AdvSceneSwitcher.variableTab.lastChanged.text="%1 seconds ago" AdvSceneSwitcher.variableTab.lastChanged.text.none="No change since launch" AdvSceneSwitcher.variableTab.lastChanged.tooltip="Times changed: %1\n\nPrevious value: %2" +; Action Queue Tab +AdvSceneSwitcher.actionQueueTab.title="Action Queues" +AdvSceneSwitcher.actionQueueTab.help="Action queues are executed sequentially but in parallel to the reset of the macro system.\nThe first action added to the queue will be the first one to be processed.\n\nClick on the highlighted plus symbol to add a new queue." +AdvSceneSwitcher.actionQueueTab.queueAddButton.tooltip="Add new action queue" +AdvSceneSwitcher.actionQueueTab.queueRemoveButton.tooltip="Remove selected action queues" +AdvSceneSwitcher.actionQueueTab.name.header="Name" +AdvSceneSwitcher.actionQueueTab.size.header="Size" +AdvSceneSwitcher.actionQueueTab.isRunning.header="Is running" +AdvSceneSwitcher.actionQueueTab.runOnStartup.header="Run on startup" +AdvSceneSwitcher.actionQueueTab.yes="Yes" +AdvSceneSwitcher.actionQueueTab.no="No" +AdvSceneSwitcher.actionQueueTab.removeSingleConnectionPopup.text="Are you sure you want to remove \"%1\"?" +AdvSceneSwitcher.actionQueueTab.removeMultipleConnectionsPopup.text="Are you sure you want to remove %1 action queues?" + ; Websocket Connections Tab AdvSceneSwitcher.websocketConnectionTab.title="Websocket Connections" AdvSceneSwitcher.websocketConnectionTab.help="Websocket connections can be used to communicate with other OBS instances or programs.\n\nClick on the highlighted plus symbol to add a new connection." diff --git a/lib/queue/action-queue-tab.cpp b/lib/queue/action-queue-tab.cpp new file mode 100644 index 00000000..5dfeea05 --- /dev/null +++ b/lib/queue/action-queue-tab.cpp @@ -0,0 +1,260 @@ +#include "action-queue-tab.hpp" +#include "action-queue.hpp" +#include "log-helper.hpp" +#include "obs-module-helper.hpp" +#include "plugin-state-helpers.hpp" +#include "sync-helpers.hpp" +#include "tab-helpers.hpp" +#include "ui-helpers.hpp" + +#include + +namespace advss { + +static bool registerTab(); +static void setupTab(QTabWidget *); +static bool registerTabDone = registerTab(); + +static ActionQueueTable *tabWidget = nullptr; + +static bool registerTab() +{ + AddPluginInitStep([]() { + AddSetupTabCallback("actionQueueTab", ActionQueueTable::Create, + setupTab); + }); + return true; +} + +static void setTabVisible(QTabWidget *tabWidget, bool visible) +{ + SetTabVisibleByName( + tabWidget, visible, + obs_module_text("AdvSceneSwitcher.actionQueueTab.title")); +} + +ActionQueueTable *ActionQueueTable::Create() +{ + tabWidget = new ActionQueueTable(); + return tabWidget; +} + +void ActionQueueTable::Add() +{ + auto newQueue = std::make_shared(); + auto accepted = + ActionQueueSettingsDialog::AskForSettings(this, *newQueue); + if (!accepted) { + return; + } + + { + auto lock = LockContext(); + auto &queues = GetActionQueues(); + queues.emplace_back(newQueue); + } + + ActionQueueSignalManager::Instance()->Add( + QString::fromStdString(newQueue->Name())); +} + +static QStringList getCellLabels(ActionQueue *queue, bool addName = true) +{ + assert(queue); + + auto result = QStringList(); + if (addName) { + result << QString::fromStdString(queue->Name()); + } + result << QString::number(queue->Size()) + << QString::fromStdString(obs_module_text( + queue->IsRunning() + ? "AdvSceneSwitcher.actionQueueTab.yes" + : "AdvSceneSwitcher.actionQueueTab.yes")) + << QString::fromStdString(obs_module_text( + queue->RunsOnStartup() + ? "AdvSceneSwitcher.actionQueueTab.yes" + : "AdvSceneSwitcher.actionQueueTab.yes")); + return result; +} + +static void updateQueueStatus(QTableWidget *table) +{ + for (int row = 0; row < table->rowCount(); row++) { + auto item = table->item(row, 0); + if (!item) { + continue; + } + + auto weakQueue = GetWeakActionQueueByQString(item->text()); + auto queue = weakQueue.lock(); + if (!queue) { + continue; + } + + UpdateItemTableRow(table, row, + getCellLabels(queue.get(), false)); + } +} + +static void openSettingsDialog() +{ + auto selectedRows = + tabWidget->Table()->selectionModel()->selectedRows(); + if (selectedRows.empty()) { + return; + } + + auto cell = tabWidget->Table()->item(selectedRows.last().row(), 0); + if (!cell) { + return; + } + + auto weakQueue = GetWeakActionQueueByQString(cell->text()); + auto queue = weakQueue.lock(); + if (!queue) { + return; + } + + auto oldName = queue->Name(); + bool accepted = ActionQueueSettingsDialog::AskForSettings( + tabWidget->Table(), *queue.get()); + if (accepted && oldName != queue->Name()) { + ActionQueueSignalManager::Instance()->Rename( + QString::fromStdString(oldName), + QString::fromStdString(queue->Name())); + } +} + +static void removeQueuesWithNames(const QStringList &queueNames) +{ + for (const auto &name : queueNames) { + auto queue = GetWeakActionQueueByQString(name).lock(); + if (!queue) { + continue; + } + + auto &queues = GetActionQueues(); + queues.erase( + std::remove_if( + queues.begin(), queues.end(), + [queue](const std::shared_ptr &item) { + return item == queue; + }), + queues.end()); + } +} + +void ActionQueueTable::Remove() +{ + auto selectedRows = + tabWidget->Table()->selectionModel()->selectedRows(); + if (selectedRows.empty()) { + return; + } + + QStringList queueNames; + for (const auto &row : selectedRows) { + auto cell = tabWidget->Table()->item(row.row(), 0); + if (!cell) { + continue; + } + + queueNames << cell->text(); + } + + int queueNameCount = queueNames.size(); + if (queueNameCount == 1) { + QString deleteWarning = obs_module_text( + "AdvSceneSwitcher.actionQueueTab.removeSingleQueuePopup.text"); + if (!DisplayMessage(deleteWarning.arg(queueNames.at(0)), + true)) { + return; + } + } else { + QString deleteWarning = obs_module_text( + "AdvSceneSwitcher.actionQueueTab.removeMultipleQueuesPopup.text"); + if (!DisplayMessage(deleteWarning.arg(queueNameCount), true)) { + return; + } + } + + { + auto lock = LockContext(); + RemoveItemsByName(GetActionQueues(), queueNames); + } + + for (const auto &name : queueNames) { + ActionQueueSignalManager::Instance()->Remove(name); + } +} + +ActionQueueTable::ActionQueueTable(QTabWidget *parent) + : ResourceTable( + parent, + obs_module_text("AdvSceneSwitcher.actionQueueTab.help"), + obs_module_text( + "AdvSceneSwitcher.actionQueueTab.queueAddButton.tooltip"), + obs_module_text( + "AdvSceneSwitcher.actionQueueTab.queueRemoveButton.tooltip"), + QStringList() + << obs_module_text( + "AdvSceneSwitcher.actionQueueTab.name.header") + << obs_module_text( + "AdvSceneSwitcher.actionQueueTab.size.header") + << obs_module_text( + "AdvSceneSwitcher.actionQueueTab.isRunning.header") + << obs_module_text( + "AdvSceneSwitcher.actionQueueTab.runOnStartup.header"), + openSettingsDialog) +{ + for (const auto &queue : GetActionQueues()) { + auto q = std::static_pointer_cast(queue); + AddItemTableRow(Table(), getCellLabels(q.get())); + } + + SetHelpVisible(GetActionQueues().empty()); +} + +static void setupTab(QTabWidget *tab) +{ + if (GetActionQueues().empty()) { + setTabVisible(tab, false); + } + + QWidget::connect(ActionQueueSignalManager::Instance(), + &ActionQueueSignalManager::Rename, + [](const QString &oldName, const QString &newName) { + RenameItemTableRow(tabWidget->Table(), oldName, + newName); + }); + QWidget::connect( + ActionQueueSignalManager::Instance(), + &ActionQueueSignalManager::Add, [tab](const QString &name) { + AddItemTableRow( + tabWidget->Table(), + getCellLabels(GetWeakActionQueueByQString(name) + .lock() + .get())); + tabWidget->SetHelpVisible(false); + tabWidget->HighlightAddButton(false); + setTabVisible(tab, true); + }); + QWidget::connect(ActionQueueSignalManager::Instance(), + &ActionQueueSignalManager::Remove, + [](const QString &name) { + RemoveItemTableRow(tabWidget->Table(), name); + if (tabWidget->Table()->rowCount() == 0) { + tabWidget->SetHelpVisible(true); + tabWidget->HighlightAddButton(true); + } + }); + + auto timer = new QTimer(tabWidget); + timer->setInterval(1000); + QWidget::connect(timer, &QTimer::timeout, + []() { updateQueueStatus(tabWidget->Table()); }); + timer->start(); +} + +} // namespace advss diff --git a/lib/queue/action-queue-tab.hpp b/lib/queue/action-queue-tab.hpp new file mode 100644 index 00000000..551e36a6 --- /dev/null +++ b/lib/queue/action-queue-tab.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "resource-table.hpp" + +namespace advss { + +class ActionQueueTable final : public ResourceTable { + Q_OBJECT + +public: + static ActionQueueTable *Create(); + +private slots: + void Add(); + void Remove(); + +private: + ActionQueueTable(QTabWidget *parent = nullptr); +}; + +} // namespace advss diff --git a/lib/queue/action-queue.cpp b/lib/queue/action-queue.cpp index f427c1b0..a02a65ac 100644 --- a/lib/queue/action-queue.cpp +++ b/lib/queue/action-queue.cpp @@ -6,6 +6,11 @@ namespace advss { static std::deque> queues; +std::deque> &GetActionQueues() +{ + return queues; +} + void SetupActionQueues() { static bool done = false; diff --git a/lib/queue/action-queue.hpp b/lib/queue/action-queue.hpp index 0d4e8c8c..1ac735d5 100644 --- a/lib/queue/action-queue.hpp +++ b/lib/queue/action-queue.hpp @@ -95,6 +95,7 @@ signals: void Remove(const QString &); }; +std::deque> &GetActionQueues(); void SetupActionQueues(); void SaveActionQueues(obs_data_t *); void LoadActionQueues(obs_data_t *);