diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini
index e920df88..edd9829c 100644
--- a/data/locale/en-US.ini
+++ b/data/locale/en-US.ini
@@ -186,6 +186,8 @@ AdvSceneSwitcher.sceneSequenceTab.fileType="Text files (*.txt)"
AdvSceneSwitcher.sceneSequenceTab.interruptible="interruptible"
AdvSceneSwitcher.sceneSequenceTab.interruptibleHint="Other switching methods are allowed to interrupt this scene sequence"
AdvSceneSwitcher.sceneSequenceTab.entry="When {{startScenes}} is active switch to {{scenes}} after {{delay}} {{delayUnits}} using {{transitions}} {{interruptible}}"
+AdvSceneSwitcher.sceneSequenceTab.extendEdit="Extend Sequence"
+AdvSceneSwitcher.sceneSequenceTab.extendEntry="After {{delay}} {{delayUnits}} switch to {{scenes}} using {{transitions}}"
AdvSceneSwitcher.sceneSequenceTab.help="This tab will allow you to automatically switch to a different scene if a scene was active for a configured period of time.\nFor example, you could automatically cycle back and forth between two scenes automatically.\n\nClick on the highlighted plus symbol to continue."
; Audio Tab
diff --git a/forms/advanced-scene-switcher.ui b/forms/advanced-scene-switcher.ui
index a0f6b8db..ec8cc3db 100644
--- a/forms/advanced-scene-switcher.ui
+++ b/forms/advanced-scene-switcher.ui
@@ -2824,6 +2824,20 @@
+ -
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+ AdvSceneSwitcher.sceneSequenceTab.extendEdit
+
+
+
-
diff --git a/src/advanced-scene-switcher.cpp b/src/advanced-scene-switcher.cpp
index d7ed0161..4d8e735d 100644
--- a/src/advanced-scene-switcher.cpp
+++ b/src/advanced-scene-switcher.cpp
@@ -469,61 +469,17 @@ void SwitcherData::Thread()
linger = 0;
switcher->Prune();
-
if (switcher->stop) {
break;
}
-
if (checkPause()) {
continue;
}
-
- for (int switchFuncName : functionNamesByPriority) {
- switch (switchFuncName) {
- case read_file_func:
- checkSwitchInfoFromFile(match, scene,
- transition);
- checkFileContent(match, scene, transition);
- break;
- case idle_func:
- checkIdleSwitch(match, scene, transition);
- break;
- case exe_func:
- checkExeSwitch(match, scene, transition);
- break;
- case screen_region_func:
- checkScreenRegionSwitch(match, scene,
- transition);
- break;
- case window_title_func:
- checkWindowTitleSwitch(match, scene,
- transition);
- break;
- case round_trip_func:
- checkSceneSequence(match, scene, transition,
- linger);
- break;
- case media_func:
- checkMediaSwitch(match, scene, transition);
- break;
- case time_func:
- checkTimeSwitch(match, scene, transition);
- break;
- case audio_func:
- checkAudioSwitch(match, scene, transition);
- break;
- }
-
- if (switcher->stop) {
- goto endLoop;
- }
- if (match) {
- break;
- }
+ match = checkForMatch(scene, transition, linger);
+ if (switcher->stop) {
+ break;
}
-
checkNoMatchSwitch(match, scene, transition, sleep);
-
checkSwitchCooldown(match);
if (linger) {
@@ -561,10 +517,64 @@ void SwitcherData::Thread()
writeSceneInfoToFile();
}
-endLoop:
+
blog(LOG_INFO, "stopped");
}
+bool SwitcherData::checkForMatch(OBSWeakSource &scene,
+ OBSWeakSource &transition, int &linger)
+{
+ bool match = false;
+
+ if (uninterruptibleSceneSequenceActive) {
+ checkSceneSequence(match, scene, transition, linger);
+ if (match) {
+ return match;
+ }
+ }
+
+ for (int switchFuncName : functionNamesByPriority) {
+ switch (switchFuncName) {
+ case read_file_func:
+ checkSwitchInfoFromFile(match, scene, transition);
+ checkFileContent(match, scene, transition);
+ break;
+ case idle_func:
+ checkIdleSwitch(match, scene, transition);
+ break;
+ case exe_func:
+ checkExeSwitch(match, scene, transition);
+ break;
+ case screen_region_func:
+ checkScreenRegionSwitch(match, scene, transition);
+ break;
+ case window_title_func:
+ checkWindowTitleSwitch(match, scene, transition);
+ break;
+ case round_trip_func:
+ checkSceneSequence(match, scene, transition, linger);
+ break;
+ case media_func:
+ checkMediaSwitch(match, scene, transition);
+ break;
+ case time_func:
+ checkTimeSwitch(match, scene, transition);
+ break;
+ case audio_func:
+ checkAudioSwitch(match, scene, transition);
+ break;
+ }
+
+ if (switcher->stop) {
+ return false;
+ }
+ if (match) {
+ break;
+ }
+ }
+ return match;
+}
+
void switchScene(OBSWeakSource &scene, OBSWeakSource &transition,
bool &transitionOverrideOverride)
{
diff --git a/src/headers/advanced-scene-switcher.hpp b/src/headers/advanced-scene-switcher.hpp
index 04e5c331..6f6e7d24 100644
--- a/src/headers/advanced-scene-switcher.hpp
+++ b/src/headers/advanced-scene-switcher.hpp
@@ -40,6 +40,7 @@ public:
int IgnoreIdleWindowsFindByData(const QString &window);
void UpdateNonMatchingScene(const QString &name);
+ void OpenSequenceExtendEdit(SequenceWidget *sw);
void SetEditSceneGroup(SceneGroup &sg);
void loadUI();
@@ -136,10 +137,12 @@ public slots:
void on_sceneSequenceAdd_clicked();
void on_sceneSequenceRemove_clicked();
- void on_sceneSequenceSave_clicked();
- void on_sceneSequenceLoad_clicked();
void on_sceneSequenceUp_clicked();
void on_sceneSequenceDown_clicked();
+ void on_sceneSequenceSave_clicked();
+ void on_sceneSequenceLoad_clicked();
+ void on_sequenceEdit_clicked();
+ void on_sceneSequenceSwitches_itemDoubleClicked(QListWidgetItem *item);
void on_verboseLogging_stateChanged(int state);
void on_uiHintsDisable_stateChanged(int state);
diff --git a/src/headers/switch-generic.hpp b/src/headers/switch-generic.hpp
index f33788df..372217d9 100644
--- a/src/headers/switch-generic.hpp
+++ b/src/headers/switch-generic.hpp
@@ -73,7 +73,7 @@ public:
static void swapSwitchData(SwitchWidget *s1, SwitchWidget *s2);
-private slots:
+protected slots:
void SceneChanged(const QString &text);
void TransitionChanged(const QString &text);
void SceneGroupAdd(const QString &name);
diff --git a/src/headers/switch-sequence.hpp b/src/headers/switch-sequence.hpp
index 432048cf..10912631 100644
--- a/src/headers/switch-sequence.hpp
+++ b/src/headers/switch-sequence.hpp
@@ -1,4 +1,6 @@
#pragma once
+#include
+
#include "switch-generic.hpp"
constexpr auto round_trip_func = 1;
@@ -12,42 +14,77 @@ typedef enum {
struct SceneSequenceSwitch : SceneSwitcherEntry {
static bool pause;
+ SwitchTargetType startTargetType = SwitchTargetType::Scene;
OBSWeakSource startScene = nullptr;
double delay = 0;
int delayMultiplier = 1;
bool interruptible = false;
unsigned int matchCount = 0;
+ // nullptr marks start point and reaching end of extended sequence
+ SceneSequenceSwitch *activeSequence = nullptr;
+
+ std::unique_ptr extendedSequence = nullptr;
+
const char *getType() { return "sequence"; }
bool initialized();
bool valid();
- void save(obs_data_t *obj);
- void load(obs_data_t *obj);
+ void save(obs_data_t *obj, bool saveExt = true);
+ void load(obs_data_t *obj, bool saveExt = true);
+
+ bool reduce();
+ SceneSequenceSwitch *extend();
+
+ bool checkMatch(OBSWeakSource currentScene, int &linger,
+ SceneSequenceSwitch *root = nullptr);
+ bool checkDurationMatchInterruptible();
+ void prepareUninterruptibleMatch(OBSWeakSource currentScene,
+ int &linger);
+ void advanceActiveSequence();
+ void logAdvanceSequence();
+ void logSequenceCanceled();
};
class SequenceWidget : public SwitchWidget {
Q_OBJECT
public:
- SequenceWidget(QWidget *parent, SceneSequenceSwitch *s);
+ SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
+ bool extendSequence = false,
+ bool editExtendMode = false);
SceneSequenceSwitch *getSwitchData();
void setSwitchData(SceneSequenceSwitch *s);
static void swapSwitchData(SequenceWidget *s1, SequenceWidget *s2);
void UpdateDelay();
+ void UpdateExtendText();
+ void setExtendedSequenceStartScene();
private slots:
+ void SceneChanged(const QString &text);
void DelayChanged(double delay);
void DelayUnitsChanged(int idx);
void StartSceneChanged(const QString &text);
void InterruptibleChanged(int state);
+ void ExtendClicked();
+ void ReduceClicked();
-private:
+protected:
QDoubleSpinBox *delay;
QComboBox *delayUnits;
QComboBox *startScenes;
QCheckBox *interruptible;
+ QVBoxLayout *extendSequenceLayout;
+ QPushButton *extend;
+ QPushButton *reduce;
+
+ // I would prefer having a list of only widgets of type editExtendMode
+ // but I am not sure how to best implement that using a QListWidget.
+ //
+ // So use edit button to bring up edit widget and
+ // add a label to disaplay current extended sequence state.
+ QLabel *extendText;
SceneSequenceSwitch *switchData;
};
diff --git a/src/headers/switcher-data-structs.hpp b/src/headers/switcher-data-structs.hpp
index eec830c5..d02993d6 100644
--- a/src/headers/switcher-data-structs.hpp
+++ b/src/headers/switcher-data-structs.hpp
@@ -85,6 +85,7 @@ struct SwitcherData {
std::vector ignoreWindowsSwitches;
+ bool uninterruptibleSceneSequenceActive = false;
std::deque sceneSequenceSwitches;
std::deque randomSwitches;
@@ -160,11 +161,14 @@ struct SwitcherData {
void Stop();
bool sceneChangedDuringWait();
+
bool prioFuncsValid();
+
void writeSceneInfoToFile();
void writeToStatusFile(QString status);
- bool checkPause();
- void checkDefaultSceneTransitions();
+
+ bool checkForMatch(OBSWeakSource &scene, OBSWeakSource &transition,
+ int &linger);
void checkSceneSequence(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &linger);
void checkIdleSwitch(bool &match, OBSWeakSource &scene,
@@ -193,6 +197,8 @@ struct SwitcherData {
OBSWeakSource &transition, int &sleep);
void checkSwitchCooldown(bool &match);
void checkTriggers();
+ bool checkPause();
+ void checkDefaultSceneTransitions();
void saveSettings(obs_data_t *obj);
void saveWindowTitleSwitches(obs_data_t *obj);
diff --git a/src/switch-sequence.cpp b/src/switch-sequence.cpp
index 1a2bccf3..bc1a8225 100644
--- a/src/switch-sequence.cpp
+++ b/src/switch-sequence.cpp
@@ -4,6 +4,8 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
+constexpr auto max_extend_text_size = 150;
+
bool SceneSequenceSwitch::pause = false;
static QMetaObject::Connection addPulse;
@@ -159,30 +161,41 @@ void AdvSceneSwitcher::on_sceneSequenceLoad_clicked()
close();
}
-bool matchInterruptible(SwitcherData *switcher, SceneSequenceSwitch &s)
+void AdvSceneSwitcher::OpenSequenceExtendEdit(SequenceWidget *sw)
{
- bool durationReached = s.matchCount * (switcher->interval / 1000.0) >=
- s.delay;
+ QDialog edit;
+ SequenceWidget editWidget(this, sw->getSwitchData(), false, true);
+ QHBoxLayout layout;
+ layout.setSizeConstraint(QLayout::SetFixedSize);
+ layout.addWidget(&editWidget);
+ edit.setLayout(&layout);
+ edit.setWindowTitle(obs_module_text(
+ "AdvSceneSwitcher.sceneSequenceTab.extendEdit"));
+ edit.exec();
- s.matchCount++;
-
- if (durationReached) {
- return true;
- }
- return false;
+ sw->UpdateExtendText();
}
-bool matchUninterruptible(SwitcherData *switcher, SceneSequenceSwitch &s,
- obs_source_t *currentSource, int &linger)
+void AdvSceneSwitcher::on_sequenceEdit_clicked()
{
- // scene was already active for the previous cycle so remove this time
- int dur = s.delay * 1000 - switcher->interval;
- if (dur > 0) {
- switcher->waitScene = currentSource;
- linger = dur;
+ int index = ui->sceneSequenceSwitches->currentRow();
+ if (index == -1) {
+ return;
}
- return true;
+ SequenceWidget *currentWidget =
+ (SequenceWidget *)ui->sceneSequenceSwitches->itemWidget(
+ ui->sceneSequenceSwitches->item(index));
+
+ OpenSequenceExtendEdit(currentWidget);
+}
+
+void AdvSceneSwitcher::on_sceneSequenceSwitches_itemDoubleClicked(
+ QListWidgetItem *item)
+{
+ SequenceWidget *currentWidget =
+ (SequenceWidget *)ui->sceneSequenceSwitches->itemWidget(item);
+ OpenSequenceExtendEdit(currentWidget);
}
void SwitcherData::checkSceneSequence(bool &match, OBSWeakSource &scene,
@@ -192,38 +205,53 @@ void SwitcherData::checkSceneSequence(bool &match, OBSWeakSource &scene,
return;
}
- obs_source_t *currentSource = obs_frontend_get_current_scene();
- obs_weak_source_t *ws = obs_source_get_weak_source(currentSource);
+ obs_source_t *currentSceneSource = obs_frontend_get_current_scene();
+ obs_weak_source_t *currentScene =
+ obs_source_get_weak_source(currentSceneSource);
for (SceneSequenceSwitch &s : sceneSequenceSwitches) {
- if (!s.initialized()) {
+ // Continue the active uninterruptible sequence and skip others
+ if (uninterruptibleSceneSequenceActive &&
+ s.activeSequence == nullptr) {
continue;
}
- if (s.startScene == ws) {
- if (!match) {
- if (s.interruptible) {
- match = matchInterruptible(switcher, s);
- } else {
- match = matchUninterruptible(
- switcher, s, currentSource,
- linger);
- }
+ bool matched = s.checkMatch(currentScene, linger);
- if (match) {
- scene = s.getScene();
- transition = s.transition;
- if (switcher->verbose) {
- s.logMatch();
- }
+ if (!match && matched) {
+ match = matched;
+
+ if (s.activeSequence) {
+ scene = s.activeSequence->getScene();
+ transition = s.activeSequence->transition;
+ } else {
+ scene = s.getScene();
+ transition = s.transition;
+ if (verbose) {
+ s.logMatch();
}
}
- } else {
- s.matchCount = 0;
+
+ s.advanceActiveSequence();
+ if (verbose) {
+ s.logAdvanceSequence();
+ }
+
+ // Ignore other switching methods if sequence is not
+ // interruptible and has not reached its end
+ if (s.activeSequence) {
+ uninterruptibleSceneSequenceActive =
+ !s.interruptible;
+ }
}
}
- obs_source_release(currentSource);
- obs_weak_source_release(ws);
+
+ if (!match) {
+ uninterruptibleSceneSequenceActive = false;
+ }
+
+ obs_source_release(currentSceneSource);
+ obs_weak_source_release(currentScene);
}
void SwitcherData::saveSceneSequenceSwitches(obs_data_t *obj)
@@ -291,18 +319,32 @@ bool SceneSequenceSwitch::valid()
(SceneSwitcherEntry::valid() && WeakSourceValid(startScene));
}
-void SceneSequenceSwitch::save(obs_data_t *obj)
+void SceneSequenceSwitch::save(obs_data_t *obj, bool saveExt)
{
SceneSwitcherEntry::save(obj);
+ obs_data_set_int(obj, "startTargetType",
+ static_cast(startTargetType));
obs_data_set_string(obj, "startScene",
GetWeakSourceName(startScene).c_str());
-
obs_data_set_double(obj, "delay", delay);
-
obs_data_set_int(obj, "delayMultiplier", delayMultiplier);
-
obs_data_set_bool(obj, "interruptible", interruptible);
+
+ if (saveExt) {
+ auto cur = extendedSequence.get();
+
+ obs_data_array_t *extendScenes = obs_data_array_create();
+ while (cur) {
+ obs_data_t *array_obj = obs_data_create();
+ cur->save(array_obj, false);
+ obs_data_array_push_back(extendScenes, array_obj);
+ obs_data_release(array_obj);
+ cur = cur->extendedSequence.get();
+ }
+ obs_data_set_array(obj, "extendScenes", extendScenes);
+ obs_data_array_release(extendScenes);
+ }
}
// To be removed in future version
@@ -341,14 +383,15 @@ bool loadOldScequence(obs_data_t *obj, SceneSequenceSwitch *s)
return true;
}
-void SceneSequenceSwitch::load(obs_data_t *obj)
+void SceneSequenceSwitch::load(obs_data_t *obj, bool saveExt)
{
if (loadOldScequence(obj, this)) {
return;
}
SceneSwitcherEntry::load(obj);
-
+ startTargetType = static_cast(
+ obs_data_get_int(obj, "startTargetType"));
const char *scene = obs_data_get_string(obj, "startScene");
startScene = GetWeakSourceByName(scene);
@@ -360,6 +403,173 @@ void SceneSequenceSwitch::load(obs_data_t *obj)
delayMultiplier = 1;
interruptible = obs_data_get_bool(obj, "interruptible");
+
+ if (saveExt) {
+ auto cur = this;
+
+ obs_data_array_t *extendScenes =
+ obs_data_get_array(obj, "extendScenes");
+ size_t count = obs_data_array_count(extendScenes);
+ for (size_t i = 0; i < count; i++) {
+ obs_data_t *array_obj =
+ obs_data_array_item(extendScenes, i);
+
+ cur->extendedSequence =
+ std::make_unique();
+
+ cur->extendedSequence->load(array_obj, false);
+
+ cur = cur->extendedSequence.get();
+ obs_data_release(array_obj);
+ }
+ obs_data_array_release(extendScenes);
+ }
+}
+
+bool SceneSequenceSwitch::reduce()
+{
+ // Reset activeSequence just in case it was one of the deleted entries
+ activeSequence = nullptr;
+
+ if (!extendedSequence) {
+ return true;
+ }
+ if (extendedSequence->reduce()) {
+ extendedSequence.reset(nullptr);
+ }
+ return false;
+}
+
+SceneSequenceSwitch *SceneSequenceSwitch::extend()
+{
+ if (extendedSequence) {
+ return extendedSequence->extend();
+ }
+ extendedSequence = std::make_unique();
+ extendedSequence->startScene = scene;
+ return extendedSequence.get();
+}
+
+bool SceneSequenceSwitch::checkMatch(OBSWeakSource currentScene, int &linger,
+ SceneSequenceSwitch *root)
+{
+ if (!initialized()) {
+ if (root) {
+ root->activeSequence = nullptr;
+ }
+ return false;
+ }
+
+ bool match = false;
+
+ if (activeSequence) {
+ return activeSequence->checkMatch(currentScene, linger, this);
+ }
+
+ if (startScene == currentScene) {
+ if (interruptible) {
+ match = checkDurationMatchInterruptible();
+ } else {
+ match = true;
+ prepareUninterruptibleMatch(currentScene, linger);
+ }
+ } else {
+ matchCount = 0;
+
+ if (root) {
+ root->activeSequence = nullptr;
+ logSequenceCanceled();
+ }
+ }
+
+ return match;
+}
+
+bool SceneSequenceSwitch::checkDurationMatchInterruptible()
+{
+ bool durationReached = matchCount * (switcher->interval / 1000.0) >=
+ delay;
+ matchCount++;
+ if (durationReached) {
+ matchCount = 0;
+ return true;
+ }
+ return false;
+}
+
+void SceneSequenceSwitch::prepareUninterruptibleMatch(
+ OBSWeakSource currentScene, int &linger)
+{
+ int dur = delay * 1000;
+ if (dur > 0) {
+ switcher->waitScene = obs_weak_source_get_source(currentScene);
+ obs_source_release(switcher->waitScene);
+ linger = dur;
+ }
+}
+
+void SceneSequenceSwitch::advanceActiveSequence()
+{
+ // Set start Scene
+ OBSWeakSource currentSceneGroupScene = nullptr;
+ if (targetType == SwitchTargetType::SceneGroup && group) {
+ currentSceneGroupScene = group->getCurrentScene();
+ }
+
+ if (activeSequence) {
+ activeSequence = activeSequence->extendedSequence.get();
+ } else {
+ activeSequence = extendedSequence.get();
+ }
+
+ if (activeSequence) {
+ if (activeSequence->startTargetType ==
+ SwitchTargetType::SceneGroup) {
+ activeSequence->startScene = currentSceneGroupScene;
+ }
+ if (activeSequence->targetType == SwitchTargetType::Scene &&
+ !activeSequence->scene) {
+ blog(LOG_WARNING,
+ "cannot advance sequence - null scene set");
+ activeSequence = nullptr;
+ }
+ if (activeSequence->targetType ==
+ SwitchTargetType::SceneGroup &&
+ activeSequence->group &&
+ activeSequence->group->scenes.empty()) {
+ blog(LOG_WARNING,
+ "cannot advance sequence - no scenes specified in '%s'",
+ activeSequence->group->name.c_str());
+ activeSequence = nullptr;
+ return;
+ }
+
+ // Reinit old matchCount value in case it was previously set
+ activeSequence->matchCount = 0;
+ }
+}
+
+void SceneSequenceSwitch::logAdvanceSequence()
+{
+ if (activeSequence) {
+ std::string targetName =
+ GetWeakSourceName(activeSequence->scene);
+
+ if (activeSequence->targetType ==
+ SwitchTargetType::SceneGroup &&
+ activeSequence->group) {
+ targetName = activeSequence->group->name;
+ }
+
+ blog(LOG_INFO, "continuing sequence with '%s' -> '%s'",
+ GetWeakSourceName(activeSequence->startScene).c_str(),
+ targetName.c_str());
+ }
+}
+
+void SceneSequenceSwitch::logSequenceCanceled()
+{
+ blog(LOG_INFO, "unexpected scene change - cancel sequence");
}
void populateDelayUnits(QComboBox *list)
@@ -369,14 +579,82 @@ void populateDelayUnits(QComboBox *list)
list->addItem(obs_module_text("AdvSceneSwitcher.unit.hours"));
}
-SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s)
- : SwitchWidget(parent, s, true, true)
+QString makeExtendText(SceneSequenceSwitch *s, int curLen = 0)
{
+ if (!s) {
+ return "";
+ }
+
+ QString ext = "";
+
+ ext = QString::number(s->delay / s->delayMultiplier) + " ";
+
+ switch (s->delayMultiplier) {
+ case 1:
+ ext += obs_module_text("AdvSceneSwitcher.unit.secends");
+ break;
+ case 60:
+ ext += obs_module_text("AdvSceneSwitcher.unit.minutes");
+ break;
+ case 60 * 60:
+ ext += obs_module_text("AdvSceneSwitcher.unit.hours");
+ break;
+ default:
+ ext += obs_module_text("?????");
+ break;
+ }
+
+ QString sceneName = GetWeakSourceName(s->scene).c_str();
+ if (s->targetType == SwitchTargetType::SceneGroup && s->group) {
+ sceneName = QString::fromStdString(s->group->name);
+ }
+ if (sceneName.isEmpty()) {
+ sceneName = obs_module_text("AdvSceneSwitcher.selectScene");
+ }
+ ext += " -> [" + sceneName + "]";
+
+ if (ext.length() + curLen > max_extend_text_size) {
+ return "...";
+ }
+
+ if (s->extendedSequence.get()) {
+ return ext +=
+ " | " + makeExtendText(s->extendedSequence.get(),
+ curLen + ext.length());
+ } else {
+ return ext;
+ }
+}
+
+SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
+ bool extendSequence, bool editExtendMode)
+ : SwitchWidget(parent, s, !extendSequence, true)
+{
+ this->setParent(parent);
+
delay = new QDoubleSpinBox();
delayUnits = new QComboBox();
startScenes = new QComboBox();
interruptible = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.sceneSequenceTab.interruptible"));
+ extendText = new QLabel();
+ extend = new QPushButton();
+ reduce = new QPushButton();
+
+ extend->setProperty("themeID",
+ QVariant(QStringLiteral("addIconSmall")));
+ reduce->setProperty("themeID",
+ QVariant(QStringLiteral("removeIconSmall")));
+
+ extend->setMaximumSize(22, 22);
+ reduce->setMaximumSize(22, 22);
+
+ // We need to extend the generic SwitchWidget::SceneChanged()
+ // with our own SequenceWidget::SceneChanged()
+ // so the old singal / slot needs to be disconnected
+ scenes->disconnect();
+ QWidget::connect(scenes, SIGNAL(currentTextChanged(const QString &)),
+ this, SLOT(SceneChanged(const QString &)));
QWidget::connect(delay, SIGNAL(valueChanged(double)), this,
SLOT(DelayChanged(double)));
@@ -388,6 +666,11 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s)
QWidget::connect(interruptible, SIGNAL(stateChanged(int)), this,
SLOT(InterruptibleChanged(int)));
+ QWidget::connect(extend, SIGNAL(clicked()), this,
+ SLOT(ExtendClicked()));
+ QWidget::connect(reduce, SIGNAL(clicked()), this,
+ SLOT(ReduceClicked()));
+
delay->setMaximum(99999.000000);
AdvSceneSwitcher::populateSceneSelection(startScenes, false);
populateDelayUnits(delayUnits);
@@ -413,17 +696,62 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s)
interruptible->setChecked(s->interruptible);
}
- QHBoxLayout *mainLayout = new QHBoxLayout;
- std::unordered_map widgetPlaceholders = {
- {"{{startScenes}}", startScenes},
- {"{{scenes}}", scenes},
- {"{{delay}}", delay},
- {"{{delayUnits}}", delayUnits},
- {"{{transitions}}", transitions},
- {"{{interruptible}}", interruptible}};
- placeWidgets(obs_module_text("AdvSceneSwitcher.sceneSequenceTab.entry"),
- mainLayout, widgetPlaceholders);
- setLayout(mainLayout);
+ if (extendSequence) {
+ QHBoxLayout *mainLayout = new QHBoxLayout;
+ std::unordered_map widgetPlaceholders = {
+ {"{{scenes}}", scenes},
+ {"{{delay}}", delay},
+ {"{{delayUnits}}", delayUnits},
+ {"{{transitions}}", transitions}};
+ placeWidgets(
+ obs_module_text(
+ "AdvSceneSwitcher.sceneSequenceTab.extendEntry"),
+ mainLayout, widgetPlaceholders);
+ setLayout(mainLayout);
+ } else {
+ QHBoxLayout *startSequence = new QHBoxLayout;
+ std::unordered_map widgetPlaceholders = {
+ {"{{startScenes}}", startScenes},
+ {"{{scenes}}", scenes},
+ {"{{delay}}", delay},
+ {"{{delayUnits}}", delayUnits},
+ {"{{transitions}}", transitions},
+ {"{{interruptible}}", interruptible}};
+ placeWidgets(obs_module_text(
+ "AdvSceneSwitcher.sceneSequenceTab.entry"),
+ startSequence, widgetPlaceholders);
+
+ //exetend widgets placed here
+ extendSequenceLayout = new QVBoxLayout;
+ if (s) {
+ if (!editExtendMode) {
+ extendText->setText(makeExtendText(
+ s->extendedSequence.get()));
+ } else {
+ auto cur = s->extendedSequence.get();
+ while (cur != nullptr) {
+ extendSequenceLayout->addWidget(
+ new SequenceWidget(parent, cur,
+ true, true));
+ cur = cur->extendedSequence.get();
+ }
+ }
+ }
+
+ QHBoxLayout *extendSequenceControlsLayout = new QHBoxLayout;
+ if (editExtendMode) {
+ extendSequenceControlsLayout->addWidget(extend);
+ extendSequenceControlsLayout->addWidget(reduce);
+ }
+ extendSequenceControlsLayout->addStretch();
+
+ QVBoxLayout *mainLayout = new QVBoxLayout;
+ mainLayout->addLayout(startSequence);
+ mainLayout->addLayout(extendSequenceLayout);
+ mainLayout->addWidget(extendText);
+ mainLayout->addLayout(extendSequenceControlsLayout);
+ setLayout(mainLayout);
+ }
switchData = s;
@@ -493,6 +821,32 @@ void SequenceWidget::DelayUnitsChanged(int idx)
UpdateDelay();
}
+void SequenceWidget::setExtendedSequenceStartScene()
+{
+ switchData->extendedSequence->startScene = switchData->scene;
+ switchData->extendedSequence->startTargetType = SwitchTargetType::Scene;
+
+ if (switchData->targetType == SwitchTargetType::SceneGroup) {
+ switchData->extendedSequence->startScene = nullptr;
+ switchData->extendedSequence->startTargetType =
+ SwitchTargetType::SceneGroup;
+ }
+}
+
+void SequenceWidget::SceneChanged(const QString &text)
+{
+ if (loading || !switchData) {
+ return;
+ }
+
+ SwitchWidget::SceneChanged(text);
+ std::lock_guard lock(switcher->m);
+
+ if (switchData->extendedSequence) {
+ setExtendedSequenceStartScene();
+ }
+}
+
void SequenceWidget::StartSceneChanged(const QString &text)
{
if (loading || !switchData) {
@@ -511,4 +865,45 @@ void SequenceWidget::InterruptibleChanged(int state)
std::lock_guard lock(switcher->m);
switchData->interruptible = state;
+
+ auto cur = switchData->extendedSequence.get();
+ while (cur != nullptr) {
+ cur->interruptible = state;
+ cur = cur->extendedSequence.get();
+ }
+}
+
+void SequenceWidget::UpdateExtendText()
+{
+ extendText->setText(makeExtendText(switchData->extendedSequence.get()));
+}
+
+void SequenceWidget::ExtendClicked()
+{
+ if (loading || !switchData) {
+ return;
+ }
+
+ std::lock_guard lock(switcher->m);
+ auto es = switchData->extend();
+
+ SequenceWidget *ew = new SequenceWidget(this->parentWidget(), es, true);
+ extendSequenceLayout->addWidget(ew);
+}
+
+void SequenceWidget::ReduceClicked()
+{
+ if (loading || !switchData) {
+ return;
+ }
+
+ std::lock_guard lock(switcher->m);
+ switchData->reduce();
+
+ int count = extendSequenceLayout->count();
+ auto item = extendSequenceLayout->takeAt(count - 1);
+ if (item) {
+ item->widget()->setVisible(false);
+ delete item;
+ }
}
diff --git a/src/switcher-data-structs.cpp b/src/switcher-data-structs.cpp
index e1e257ca..b889bd69 100644
--- a/src/switcher-data-structs.cpp
+++ b/src/switcher-data-structs.cpp
@@ -43,6 +43,16 @@ void SwitcherData::Prune()
sceneSequenceSwitches.erase(
sceneSequenceSwitches.begin() + i--);
}
+
+ auto cur = &s;
+ while (cur != nullptr) {
+ if (cur->extendedSequence &&
+ !cur->extendedSequence->valid()) {
+ cur->extendedSequence.reset(nullptr);
+ s.activeSequence = nullptr;
+ }
+ cur = cur->extendedSequence.get();
+ }
}
for (size_t i = 0; i < sceneTransitions.size(); i++) {