From abd6fd6b7e1f839f1b82bca34e7d8ee69a3aa55c Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Sat, 22 Jan 2022 09:07:26 -0800 Subject: [PATCH] Add sequence action (#395) --- CMakeLists.txt | 2 + data/locale/en-US.ini | 6 + src/headers/macro-action-random.hpp | 12 - src/headers/macro-action-sequence.hpp | 77 ++++++ src/headers/macro-selection.hpp | 12 + src/macro-action-random.cpp | 54 +--- src/macro-action-sequence.cpp | 382 ++++++++++++++++++++++++++ src/macro-selection.cpp | 46 ++++ 8 files changed, 529 insertions(+), 62 deletions(-) create mode 100644 src/headers/macro-action-sequence.hpp create mode 100644 src/macro-action-sequence.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cee94a99..793524c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -211,6 +211,7 @@ set(advanced-scene-switcher_HEADERS src/headers/macro-action-scene-transform.hpp src/headers/macro-action-scene-visibility.hpp src/headers/macro-action-screenshot.hpp + src/headers/macro-action-sequence.hpp src/headers/macro-action-source.hpp src/headers/macro-action-streaming.hpp src/headers/macro-action-systray.hpp @@ -310,6 +311,7 @@ set(advanced-scene-switcher_SOURCES src/macro-action-scene-transform.cpp src/macro-action-scene-visibility.cpp src/macro-action-screenshot.cpp + src/macro-action-sequence.cpp src/macro-action-source.cpp src/macro-action-streaming.cpp src/macro-action-systray.cpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 7c7d8801..c8a44aaf 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -398,6 +398,12 @@ AdvSceneSwitcher.action.profile.entry="Switch active profile to {{profiles}}" AdvSceneSwitcher.action.sceneCollection="Scene collection" AdvSceneSwitcher.action.sceneCollection.entry="Switch active scene collection to {{sceneCollections}}" AdvSceneSwitcher.action.sceneCollection.warning="Note: Any actions following after this will not be executed as the changing scene collection will also reload the scene switcher settings.\nThe scene collection action will be ignored while the settings window is opened." +AdvSceneSwitcher.action.sequence="Sequence" +AdvSceneSwitcher.action.sequence.entry="Each time this action is performed run the next macro in the list (paused macros are ignored)" +AdvSceneSwitcher.action.sequence.status="Last executed macro: %1 - Next macro to be executed: %2" +AdvSceneSwitcher.action.sequence.status.none="none" +AdvSceneSwitcher.action.sequence.restart="Restart from beginning once end of list is reached" +AdvSceneSwitcher.action.sequence.continueFrom="Continue with selected item" ; Transition Tab AdvSceneSwitcher.transitionTab.title="Transition" diff --git a/src/headers/macro-action-random.hpp b/src/headers/macro-action-random.hpp index a6f3f97c..868c08fb 100644 --- a/src/headers/macro-action-random.hpp +++ b/src/headers/macro-action-random.hpp @@ -4,7 +4,6 @@ #include #include -#include #include class MacroActionRandom : public MultiMacroRefAction { @@ -29,17 +28,6 @@ private: static const std::string id; }; -class MacroDialog : public QDialog { - Q_OBJECT - -public: - MacroDialog(QWidget *parent); - static bool AskForMacro(QWidget *parent, std::string ¯oName); - -private: - MacroSelection *_macroSelection; -}; - class MacroActionRandomEdit : public QWidget { Q_OBJECT diff --git a/src/headers/macro-action-sequence.hpp b/src/headers/macro-action-sequence.hpp new file mode 100644 index 00000000..bccf1491 --- /dev/null +++ b/src/headers/macro-action-sequence.hpp @@ -0,0 +1,77 @@ +#pragma once +#include "macro-action-edit.hpp" +#include "macro-selection.hpp" + +#include +#include +#include +#include + +class MacroActionSequence : public MultiMacroRefAction { +public: + MacroActionSequence(Macro *m) : MultiMacroRefAction(m) {} + bool PerformAction(); + void LogAction(); + bool Save(obs_data_t *obj); + bool Load(obs_data_t *obj); + std::string GetId() { return id; }; + MacroRef GetNextMacro(bool advance = true); + static std::shared_ptr Create(Macro *m) + { + return std::make_shared(m); + } + + bool _restart = true; + MacroRef _lastSequenceMacro; + int _lastIdx = -1; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroActionSequenceEdit : public QWidget { + Q_OBJECT + +public: + MacroActionSequenceEdit( + QWidget *parent, + std::shared_ptr entryData = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr action) + { + return new MacroActionSequenceEdit( + parent, + std::dynamic_pointer_cast(action)); + } + +private slots: + void MacroRemove(const QString &name); + void MacroRename(const QString &oldName, const QString &newName); + void Add(); + void Remove(); + void Up(); + void Down(); + void ContinueFromClicked(); + void RestartChanged(int state); + void UpdateStatusLine(); + +protected: + std::shared_ptr _entryData; + +private: + int FindEntry(const std::string ¯o); + void SetMacroListSize(); + + QListWidget *_macroList; + QPushButton *_add; + QPushButton *_remove; + QPushButton *_up; + QPushButton *_down; + QPushButton *_continueFrom; + QCheckBox *_restart; + QLabel *_statusLine; + QTimer _statusTimer; + bool _loading = true; +}; diff --git a/src/headers/macro-selection.hpp b/src/headers/macro-selection.hpp index c1c9219d..6d5dc15f 100644 --- a/src/headers/macro-selection.hpp +++ b/src/headers/macro-selection.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include class Macro; @@ -17,3 +18,14 @@ private slots: void MacroRemove(const QString &name); void MacroRename(const QString &oldName, const QString &newName); }; + +class MacroSelectionDialog : public QDialog { + Q_OBJECT + +public: + MacroSelectionDialog(QWidget *parent); + static bool AskForMacro(QWidget *parent, std::string ¯oName); + +private: + MacroSelection *_macroSelection; +}; diff --git a/src/macro-action-random.cpp b/src/macro-action-random.cpp index 5bcd39fc..0091b55d 100644 --- a/src/macro-action-random.cpp +++ b/src/macro-action-random.cpp @@ -1,9 +1,7 @@ #include "headers/macro-action-random.hpp" #include "headers/advanced-scene-switcher.hpp" -#include "headers/name-dialog.hpp" #include "headers/utility.hpp" -#include #include const std::string MacroActionRandom::id = "random"; @@ -13,8 +11,8 @@ bool MacroActionRandom::_registered = MacroActionFactory::Register( {MacroActionRandom::Create, MacroActionRandomEdit::Create, "AdvSceneSwitcher.action.random"}); -std::vector getPossibleMacros(std::vector ¯os, - MacroRef &lastRandomMacro) +std::vector getNextMacro(std::vector ¯os, + MacroRef &lastRandomMacro) { std::vector res; if (macros.size() == 1) { @@ -40,7 +38,7 @@ bool MacroActionRandom::PerformAction() return true; } - auto macros = getPossibleMacros(_macros, lastRandomMacro); + auto macros = getNextMacro(_macros, lastRandomMacro); if (macros.size() == 0) { return true; } @@ -190,7 +188,7 @@ void MacroActionRandomEdit::AddMacro() } std::string macroName; - bool accepted = MacroDialog::AskForMacro(this, macroName); + bool accepted = MacroSelectionDialog::AskForMacro(this, macroName); if (!accepted || macroName.empty()) { return; @@ -262,47 +260,3 @@ void MacroActionRandomEdit::SetMacroListSize() setHeightToContentHeight(_macroList); adjustSize(); } - -MacroDialog::MacroDialog(QWidget *) -{ - setModal(true); - setWindowModality(Qt::WindowModality::ApplicationModal); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - setMinimumWidth(350); - setMinimumHeight(70); - - QDialogButtonBox *buttonbox = new QDialogButtonBox( - QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - - buttonbox->setCenterButtons(true); - connect(buttonbox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(buttonbox, &QDialogButtonBox::rejected, this, &QDialog::reject); - - _macroSelection = new MacroSelection(window()); - auto *selectionLayout = new QHBoxLayout; - std::unordered_map widgetPlaceholders = { - {"{{macroSelection}}", _macroSelection}, - }; - placeWidgets(obs_module_text("AdvSceneSwitcher.askForMacro"), - selectionLayout, widgetPlaceholders); - auto *layout = new QVBoxLayout(); - layout->addLayout(selectionLayout); - layout->addWidget(buttonbox); - setLayout(layout); -} - -bool MacroDialog::AskForMacro(QWidget *parent, std::string ¯oName) -{ - MacroDialog dialog(parent); - dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle")); - - if (dialog.exec() != DialogCode::Accepted) { - return false; - } - macroName = dialog._macroSelection->currentText().toUtf8().constData(); - if (macroName == obs_module_text("AdvSceneSwitcher.selectMacro")) { - return false; - } - - return true; -} diff --git a/src/macro-action-sequence.cpp b/src/macro-action-sequence.cpp new file mode 100644 index 00000000..9a01f7c2 --- /dev/null +++ b/src/macro-action-sequence.cpp @@ -0,0 +1,382 @@ +#include "headers/macro-action-sequence.hpp" +#include "headers/advanced-scene-switcher.hpp" +#include "headers/utility.hpp" + +const std::string MacroActionSequence::id = "sequence"; + +bool MacroActionSequence::_registered = MacroActionFactory::Register( + MacroActionSequence::id, + {MacroActionSequence::Create, MacroActionSequenceEdit::Create, + "AdvSceneSwitcher.action.sequence"}); + +int getNextUnpausedMacroIdx(std::vector ¯os, int startIdx) +{ + for (; macros.size() > startIdx; ++startIdx) { + if (!macros[startIdx]->Paused()) { + return startIdx; + } + } + return -1; +} + +MacroRef MacroActionSequence::GetNextMacro(bool advance) +{ + int idx = _lastIdx; + MacroRef res; + + int nextUnpausedIdx = getNextUnpausedMacroIdx(_macros, idx + 1); + if (nextUnpausedIdx != -1) { + res = _macros[nextUnpausedIdx]; + idx = nextUnpausedIdx; + } else { + if (_restart) { + idx = getNextUnpausedMacroIdx(_macros, 0); + if (idx != -1) { + res = _macros[idx]; + } else { + // End of sequence reached, as all available + // macros are either paused or the macro list + // is empty + idx = _macros.size(); + } + } else { + // End of sequence reached + idx = _macros.size(); + } + } + + if (advance) { + _lastIdx = idx; + _lastSequenceMacro = res; + } + return res; +} + +bool MacroActionSequence::PerformAction() +{ + if (_macros.size() == 0) { + return true; + } + + auto macro = GetNextMacro(); + if (!macro.get()) { + return true; + } + + return macro->PerformAction(); +} + +void MacroActionSequence::LogAction() +{ + vblog(LOG_INFO, "running macro sequence"); +} + +bool MacroActionSequence::Save(obs_data_t *obj) +{ + MacroAction::Save(obj); + obs_data_array_t *args = obs_data_array_create(); + for (auto &m : _macros) { + obs_data_t *array_obj = obs_data_create(); + m.Save(array_obj); + obs_data_array_push_back(args, array_obj); + obs_data_release(array_obj); + } + obs_data_set_array(obj, "macros", args); + obs_data_array_release(args); + obs_data_set_bool(obj, "restart", _restart); + return true; +} + +bool MacroActionSequence::Load(obs_data_t *obj) +{ + MacroAction::Load(obj); + obs_data_array_t *args = obs_data_get_array(obj, "macros"); + size_t count = obs_data_array_count(args); + for (size_t i = 0; i < count; i++) { + obs_data_t *array_obj = obs_data_array_item(args, i); + MacroRef ref; + ref.Load(array_obj); + _macros.push_back(ref); + obs_data_release(array_obj); + } + obs_data_array_release(args); + _restart = obs_data_get_bool(obj, "restart"); + return true; +} + +MacroActionSequenceEdit::MacroActionSequenceEdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent) +{ + _macroList = new QListWidget(); + _add = new QPushButton(); + _add->setMaximumSize(QSize(22, 22)); + _add->setProperty("themeID", + QVariant(QString::fromUtf8("addIconSmall"))); + _add->setFlat(true); + _remove = new QPushButton(); + _remove->setMaximumSize(QSize(22, 22)); + _remove->setProperty("themeID", + QVariant(QString::fromUtf8("removeIconSmall"))); + _remove->setFlat(true); + _up = new QPushButton(); + _up->setMaximumSize(QSize(22, 22)); + _up->setProperty("themeID", + QVariant(QString::fromUtf8("upArrowIconSmall"))); + _up->setFlat(true); + _down = new QPushButton(); + _down->setMaximumSize(QSize(22, 22)); + _down->setProperty("themeID", + QVariant(QString::fromUtf8("downArrowIconSmall"))); + _down->setFlat(true); + _continueFrom = new QPushButton(obs_module_text( + "AdvSceneSwitcher.action.sequence.continueFrom")); + _restart = new QCheckBox( + obs_module_text("AdvSceneSwitcher.action.sequence.restart")); + _statusLine = new QLabel(); + + QWidget::connect(_add, SIGNAL(clicked()), this, SLOT(Add())); + QWidget::connect(_remove, SIGNAL(clicked()), this, SLOT(Remove())); + QWidget::connect(_up, SIGNAL(clicked()), this, SLOT(Up())); + QWidget::connect(_down, SIGNAL(clicked()), this, SLOT(Down())); + QWidget::connect(_continueFrom, SIGNAL(clicked()), this, + SLOT(ContinueFromClicked())); + QWidget::connect(_macroList, SIGNAL(currentRowChanged(int)), this, + SLOT(MacroSelectionChanged(int))); + QWidget::connect(window(), + SIGNAL(MacroRenamed(const QString &, const QString &)), + this, + SLOT(MacroRename(const QString &, const QString &))); + QWidget::connect(_restart, SIGNAL(stateChanged(int)), this, + SLOT(RestartChanged(int))); + + auto *entryLayout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = {}; + placeWidgets(obs_module_text("AdvSceneSwitcher.action.sequence.entry"), + entryLayout, widgetPlaceholders); + + auto *argButtonLayout = new QHBoxLayout; + argButtonLayout->addWidget(_add); + argButtonLayout->addWidget(_remove); + QFrame *line = new QFrame(); + line->setFrameShape(QFrame::VLine); + line->setFrameShadow(QFrame::Sunken); + argButtonLayout->addWidget(line); + argButtonLayout->addWidget(_up); + argButtonLayout->addWidget(_down); + QFrame *line2 = new QFrame(); + line2->setFrameShape(QFrame::VLine); + line2->setFrameShadow(QFrame::Sunken); + argButtonLayout->addWidget(line2); + argButtonLayout->addWidget(_continueFrom); + argButtonLayout->addStretch(); + + auto *mainLayout = new QVBoxLayout; + mainLayout->addLayout(entryLayout); + mainLayout->addWidget(_macroList); + mainLayout->addLayout(argButtonLayout); + mainLayout->addLayout(argButtonLayout); + mainLayout->addWidget(_restart); + mainLayout->addWidget(_statusLine); + setLayout(mainLayout); + + UpdateStatusLine(); + connect(&_statusTimer, SIGNAL(timeout()), this, + SLOT(UpdateStatusLine())); + _statusTimer.start(1000); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroActionSequenceEdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + + for (auto &m : _entryData->_macros) { + if (!m.get()) { + continue; + } + auto name = QString::fromStdString(m->Name()); + QListWidgetItem *item = new QListWidgetItem(name, _macroList); + item->setData(Qt::UserRole, name); + } + SetMacroListSize(); + _restart->setChecked(_entryData->_restart); +} + +void MacroActionSequenceEdit::MacroRemove(const QString &name) +{ + if (_entryData) { + auto it = _entryData->_macros.begin(); + while (it != _entryData->_macros.end()) { + if (it->get()->Name() == name.toStdString()) { + it = _entryData->_macros.erase(it); + } else { + ++it; + } + } + } +} + +void MacroActionSequenceEdit::MacroRename(const QString &oldName, + const QString &newName) +{ + auto count = _macroList->count(); + for (int idx = 0; idx < count; ++idx) { + QListWidgetItem *item = _macroList->item(idx); + QString itemString = item->data(Qt::UserRole).toString(); + if (oldName == itemString) { + item->setData(Qt::UserRole, newName); + item->setText(newName); + break; + } + } +} + +void MacroActionSequenceEdit::Add() +{ + if (_loading || !_entryData) { + return; + } + + std::string macroName; + bool accepted = MacroSelectionDialog::AskForMacro(this, macroName); + + if (!accepted || macroName.empty()) { + return; + } + + MacroRef macro(macroName); + + if (!macro.get()) { + return; + } + + QVariant v = QVariant::fromValue(QString::fromStdString(macroName)); + new QListWidgetItem(QString::fromStdString(macroName), _macroList); + std::lock_guard lock(switcher->m); + _entryData->_macros.push_back(macro); + SetMacroListSize(); +} + +void MacroActionSequenceEdit::Remove() +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + auto item = _macroList->currentItem(); + int idx = _macroList->currentRow(); + if (!item || idx == -1) { + return; + } + + _entryData->_macros.erase(std::next(_entryData->_macros.begin(), idx)); + + delete item; + SetMacroListSize(); +} + +void MacroActionSequenceEdit::Up() +{ + if (_loading || !_entryData) { + return; + } + + int idx = _macroList->currentRow(); + if (idx != -1 && idx != 0) { + _macroList->insertItem(idx - 1, _macroList->takeItem(idx)); + _macroList->setCurrentRow(idx - 1); + + std::lock_guard lock(switcher->m); + std::swap(_entryData->_macros[idx], + _entryData->_macros[idx - 1]); + } +} + +void MacroActionSequenceEdit::Down() +{ + int idx = _macroList->currentRow(); + if (idx != -1 && idx != _macroList->count() - 1) { + _macroList->insertItem(idx + 1, _macroList->takeItem(idx)); + _macroList->setCurrentRow(idx + 1); + + std::lock_guard lock(switcher->m); + std::swap(_entryData->_macros[idx], + _entryData->_macros[idx + 1]); + } +} + +void MacroActionSequenceEdit::ContinueFromClicked() +{ + if (_loading || !_entryData) { + return; + } + + int idx = _macroList->currentRow(); + if (idx == -1) { + return; + } + std::lock_guard lock(switcher->m); + _entryData->_lastIdx = idx - 1; +} + +void MacroActionSequenceEdit::RestartChanged(int state) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(switcher->m); + _entryData->_restart = state; +} + +void MacroActionSequenceEdit::UpdateStatusLine() +{ + QString lastMacroName = + obs_module_text("AdvSceneSwitcher.action.sequence.status.none"); + QString nextMacroName = + obs_module_text("AdvSceneSwitcher.action.sequence.status.none"); + if (_entryData) { + if (_entryData->_lastSequenceMacro.get()) { + lastMacroName = QString::fromStdString( + _entryData->_lastSequenceMacro->Name()); + } + auto next = _entryData->GetNextMacro(false); + if (next.get()) { + nextMacroName = QString::fromStdString(next->Name()); + } + } + + QString format{ + obs_module_text("AdvSceneSwitcher.action.sequence.status")}; + _statusLine->setText(format.arg(lastMacroName, nextMacroName)); +} + +int MacroActionSequenceEdit::FindEntry(const std::string ¯o) +{ + int count = _macroList->count(); + int idx = -1; + + for (int i = 0; i < count; i++) { + QListWidgetItem *item = _macroList->item(i); + QString itemString = item->data(Qt::UserRole).toString(); + if (QString::fromStdString(macro) == itemString) { + idx = i; + break; + } + } + + return idx; +} + +void MacroActionSequenceEdit::SetMacroListSize() +{ + setHeightToContentHeight(_macroList); + adjustSize(); +} diff --git a/src/macro-selection.cpp b/src/macro-selection.cpp index f9e53cb4..c3183914 100644 --- a/src/macro-selection.cpp +++ b/src/macro-selection.cpp @@ -1,7 +1,9 @@ #include "headers/macro-selection.hpp" #include "headers/advanced-scene-switcher.hpp" +#include "headers/utility.hpp" #include +#include MacroSelection::MacroSelection(QWidget *parent) : QComboBox(parent) { @@ -88,3 +90,47 @@ void MacroSelection::MacroRename(const QString &oldName, const QString &newName) setCurrentIndex(findText(newName)); } } + +MacroSelectionDialog::MacroSelectionDialog(QWidget *) +{ + setModal(true); + setWindowModality(Qt::WindowModality::ApplicationModal); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + setMinimumWidth(350); + setMinimumHeight(70); + + QDialogButtonBox *buttonbox = new QDialogButtonBox( + QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + buttonbox->setCenterButtons(true); + connect(buttonbox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonbox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + _macroSelection = new MacroSelection(window()); + auto *selectionLayout = new QHBoxLayout; + std::unordered_map widgetPlaceholders = { + {"{{macroSelection}}", _macroSelection}, + }; + placeWidgets(obs_module_text("AdvSceneSwitcher.askForMacro"), + selectionLayout, widgetPlaceholders); + auto *layout = new QVBoxLayout(); + layout->addLayout(selectionLayout); + layout->addWidget(buttonbox); + setLayout(layout); +} + +bool MacroSelectionDialog::AskForMacro(QWidget *parent, std::string ¯oName) +{ + MacroSelectionDialog dialog(parent); + dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle")); + + if (dialog.exec() != DialogCode::Accepted) { + return false; + } + macroName = dialog._macroSelection->currentText().toUtf8().constData(); + if (macroName == obs_module_text("AdvSceneSwitcher.selectMacro")) { + return false; + } + + return true; +}