#include "advanced-scene-switcher.hpp" #include "action-queue.hpp" #include "cursor-shape-changer.hpp" #include "macro-action-edit.hpp" #include "macro-condition-edit.hpp" #include "macro-export-import-dialog.hpp" #include "macro-settings.hpp" #include "macro-segment-copy-paste.hpp" #include "macro-tree.hpp" #include "macro.hpp" #include "math-helpers.hpp" #include "name-dialog.hpp" #include "path-helpers.hpp" #include "switcher-data.hpp" #include "ui-helpers.hpp" #include "utility.hpp" #include "version.h" #include #include #include #include #include namespace advss { static QObject *addPulse = nullptr; static QTimer onChangeHighlightTimer; static bool macroNameExists(const std::string &name) { return !!GetMacroByName(name.c_str()); } static bool newMacroNameIsValid(const std::string &name) { if (!macroNameExists(name)) { return true; } auto macro = GetMacroByName(name.c_str()); if (!macro) { return false; } QString fmt = obs_module_text( macro->IsGroup() ? "AdvSceneSwitcher.macroTab.groupNameExists" : "AdvSceneSwitcher.macroTab.macroNameExists"); DisplayMessage(fmt.arg(QString::fromStdString(name))); return false; } bool AdvSceneSwitcher::AddNewMacro(std::shared_ptr &res, std::string &name, std::string format) { QString fmt; int i = 1; if (format.empty()) { fmt = QString(obs_module_text( "AdvSceneSwitcher.macroTab.defaultname")); } else { fmt = QString::fromStdString(format); i = 2; } QString placeHolderText = fmt.arg(i); while ((macroNameExists(placeHolderText.toUtf8().constData()))) { placeHolderText = fmt.arg(++i); } bool accepted = NameDialog::AskForName( this, obs_module_text("AdvSceneSwitcher.macroTab.add"), obs_module_text("AdvSceneSwitcher.macroTab.name"), name, placeHolderText); if (!accepted) { return false; } if (name.empty()) { return false; } if (!newMacroNameIsValid(name)) { return false; } res = std::make_shared( name, GetGlobalMacroSettings()._newMacroRegisterHotkeys); return true; } void AdvSceneSwitcher::on_macroAdd_clicked() { std::string name; std::shared_ptr newMacro; if (!AddNewMacro(newMacro, name)) { return; } ui->macros->Add(newMacro); if (addPulse) { addPulse->deleteLater(); addPulse = nullptr; } emit MacroAdded(QString::fromStdString(name)); } void AdvSceneSwitcher::RemoveMacro(std::shared_ptr ¯o) { if (!macro) { return; } auto name = QString::fromStdString(macro->Name()); if (macro->IsGroup() && macro->GroupSize() > 0) { QString deleteWarning = obs_module_text( "AdvSceneSwitcher.macroTab.removeGroupPopup.text"); if (!DisplayMessage(deleteWarning.arg(name), true)) { return; } } ui->macros->Remove(macro); emit MacroRemoved(name); } void AdvSceneSwitcher::RemoveSelectedMacros() { auto macros = GetSelectedMacros(); if (macros.empty()) { return; } int macroCount = macros.size(); if (macroCount == 1) { QString deleteWarning = obs_module_text( "AdvSceneSwitcher.macroTab.removeSingleMacroPopup.text"); auto macro = macros.at(0); deleteWarning = deleteWarning.arg( QString::fromStdString(macro->Name())); if ((!macro->IsGroup() || macro->GroupSize() == 0) && !DisplayMessage(deleteWarning, true)) { return; } RemoveMacro(macro); return; } QString deleteWarning = obs_module_text( "AdvSceneSwitcher.macroTab.removeMultipleMacrosPopup.text"); if (!DisplayMessage(deleteWarning.arg(macroCount), true)) { return; } for (auto ¯o : macros) { RemoveMacro(macro); } } void AdvSceneSwitcher::RenameMacro(std::shared_ptr ¯o, const QString &name) { auto oldName = QString::fromStdString(macro->Name()); { auto lock = LockContext(); macro->SetName(name.toStdString()); } emit MacroRenamed(oldName, name); } void AdvSceneSwitcher::on_macroRemove_clicked() { RemoveSelectedMacros(); } void AdvSceneSwitcher::on_macroUp_clicked() { auto macro = GetSelectedMacro(); if (!macro) { return; } ui->macros->Up(macro); } void AdvSceneSwitcher::on_macroDown_clicked() { auto macro = GetSelectedMacro(); if (!macro) { return; } ui->macros->Down(macro); } void AdvSceneSwitcher::RenameSelectedMacro() { auto macro = GetSelectedMacro(); if (!macro) { return; } std::string oldName = macro->Name(); std::string name; if (!NameDialog::AskForName( this, obs_module_text("AdvSceneSwitcher.windowTitle"), obs_module_text("AdvSceneSwitcher.item.newName"), name, QString::fromStdString(oldName))) { return; } if (name.empty() || name == oldName || !newMacroNameIsValid(name)) { return; } RenameMacro(macro, QString::fromStdString(name)); const QSignalBlocker b(ui->macroName); ui->macroName->setText(QString::fromStdString(name)); } static void addGroupSubitems(std::vector> ¯os, const std::shared_ptr &group) { std::vector> subitems; subitems.reserve(group->GroupSize()); // Find all subitems auto allMacros = GetMacros(); for (auto it = allMacros.begin(); it < allMacros.end(); it++) { if ((*it)->Name() == group->Name()) { for (uint32_t i = 1; i <= group->GroupSize(); i++) { subitems.emplace_back(*std::next(it, i)); } break; } } // Remove subitems which were already selected to avoid duplicates for (const auto &subitem : subitems) { auto it = std::find(macros.begin(), macros.end(), subitem); if (it == macros.end()) { continue; } macros.erase(it); } // Add group subitems auto it = std::find(macros.begin(), macros.end(), group); if (it == macros.end()) { return; } it = std::next(it); macros.insert(it, subitems.begin(), subitems.end()); } void AdvSceneSwitcher::ExportMacros() { auto selectedMacros = GetSelectedMacros(); auto macros = selectedMacros; for (const auto ¯o : selectedMacros) { if (macro->IsGroup() && macro->GroupSize() > 0) { addGroupSubitems(macros, macro); } } OBSDataAutoRelease data = obs_data_create(); OBSDataArrayAutoRelease macroArray = obs_data_array_create(); for (const auto ¯o : macros) { OBSDataAutoRelease obj = obs_data_create(); macro->Save(obj); obs_data_array_push_back(macroArray, obj); } obs_data_set_array(data, "macros", macroArray); SaveVariables(data); SaveActionQueues(data); obs_data_set_string(data, "version", g_GIT_TAG); auto json = obs_data_get_json(data); QString exportString(json); MacroExportImportDialog::ExportMacros(exportString); } static bool isValidMacroSegmentIdx(const std::deque> &list, int idx) { return (idx > 0 || (unsigned)idx < list.size()); } void AdvSceneSwitcher::SetupMacroSegmentSelection(MacroSection type, int idx) { auto macro = GetSelectedMacro(); if (!macro) { return; } MacroSegmentList *setList = nullptr, *resetList1 = nullptr, *resetList2 = nullptr; int *setIdx = nullptr, *resetIdx1 = nullptr, *resetIdx2 = nullptr; std::deque> segements; switch (type) { case AdvSceneSwitcher::MacroSection::CONDITIONS: setList = ui->conditionsList; setIdx = ¤tConditionIdx; segements = {macro->Conditions().begin(), macro->Conditions().end()}; resetList1 = ui->actionsList; resetList2 = ui->elseActionsList; resetIdx1 = ¤tActionIdx; resetIdx2 = ¤tElseActionIdx; break; case AdvSceneSwitcher::MacroSection::ACTIONS: setList = ui->actionsList; setIdx = ¤tActionIdx; segements = {macro->Actions().begin(), macro->Actions().end()}; resetList1 = ui->conditionsList; resetList2 = ui->elseActionsList; resetIdx1 = ¤tConditionIdx; resetIdx2 = ¤tElseActionIdx; break; case AdvSceneSwitcher::MacroSection::ELSE_ACTIONS: setList = ui->elseActionsList; setIdx = ¤tElseActionIdx; segements = {macro->ElseActions().begin(), macro->ElseActions().end()}; resetList1 = ui->actionsList; resetList2 = ui->conditionsList; resetIdx1 = ¤tActionIdx; resetIdx2 = ¤tConditionIdx; break; default: break; } setList->SetSelection(idx); resetList1->SetSelection(-1); resetList2->SetSelection(-1); if (isValidMacroSegmentIdx(segements, idx)) { *setIdx = idx; } else { *setIdx = -1; } *resetIdx1 = -1; *resetIdx2 = -1; lastInteracted = type; HighlightControls(); } bool AdvSceneSwitcher::ResolveMacroImportNameConflict( std::shared_ptr ¯o) { QString errorMesg = obs_module_text( "AdvSceneSwitcher.macroTab.import.nameConflict"); errorMesg = errorMesg.arg(QString::fromStdString(macro->Name()), QString::fromStdString(macro->Name())); bool continueResolve = DisplayMessage(errorMesg, true); if (!continueResolve) { return false; } QString format = QString::fromStdString(macro->Name()) + " %1"; int i = 2; QString placeHolderText = format.arg(i); while ((macroNameExists(placeHolderText.toUtf8().constData()))) { placeHolderText = format.arg(++i); } std::string newName; bool accepted = NameDialog::AskForName( this, obs_module_text(macro->IsGroup() ? "AdvSceneSwitcher.macroTab.addGroup" : "AdvSceneSwitcher.macroTab.add"), obs_module_text("AdvSceneSwitcher.macroTab.name"), newName, placeHolderText); if (!accepted) { return false; } if (newName.empty()) { return false; } if (!newMacroNameIsValid(newName)) { return ResolveMacroImportNameConflict(macro); } macro->SetName(newName); return true; } void AdvSceneSwitcher::ImportMacros() { QString json; if (!MacroExportImportDialog::ImportMacros(json)) { return; } OBSDataAutoRelease data = obs_data_create_from_json(json.toStdString().c_str()); if (!data) { DisplayMessage(obs_module_text( "AdvSceneSwitcher.macroTab.import.invalid")); ImportMacros(); return; } ImportVariables(data); ImportQueues(data); auto version = obs_data_get_string(data, "version"); if (strcmp(version, g_GIT_TAG) != 0) { blog(LOG_WARNING, "importing macros from non matching plugin version \"%s\"", version); } OBSDataArrayAutoRelease array = obs_data_get_array(data, "macros"); size_t count = obs_data_array_count(array); int groupSize = 0; std::shared_ptr group; std::vector> importedMacros; auto lock = LockContext(); for (size_t i = 0; i < count; i++) { OBSDataAutoRelease array_obj = obs_data_array_item(array, i); auto macro = std::make_shared(); macro->Load(array_obj); RunPostLoadSteps(); if (macroNameExists(macro->Name()) && !ResolveMacroImportNameConflict(macro)) { groupSize--; continue; } importedMacros.emplace_back(macro); GetMacros().emplace_back(macro); if (groupSize > 0 && !macro->IsGroup()) { Macro::PrepareMoveToGroup(group, macro); groupSize--; } if (macro->IsGroup()) { group = macro; groupSize = macro->GroupSize(); // We are not sure if all elements will be added so we // have to reset the group size to zero and add elements // to the group as they come up. macro->ResetGroupSize(); } } for (const auto ¯o : importedMacros) { macro->PostLoad(); } RunPostLoadSteps(); ui->macros->Reset(GetMacros(), GetGlobalMacroSettings()._highlightExecuted); } void AdvSceneSwitcher::on_macroName_editingFinished() { auto macro = GetSelectedMacro(); if (!macro) { return; } QString newName = ui->macroName->text(); QString oldName = QString::fromStdString(macro->Name()); if (newName.isEmpty() || newName == oldName || !newMacroNameIsValid(newName.toStdString())) { ui->macroName->setText(oldName); return; } RenameMacro(macro, newName); } void AdvSceneSwitcher::on_runMacroInParallel_stateChanged(int value) { auto macro = GetSelectedMacro(); if (!macro) { return; } auto lock = LockContext(); macro->SetRunInParallel(value); } void AdvSceneSwitcher::on_runMacroOnChange_stateChanged(int value) { auto macro = GetSelectedMacro(); if (!macro) { return; } auto lock = LockContext(); macro->SetMatchOnChange(value); } void AdvSceneSwitcher::PopulateMacroActions(Macro &m, uint32_t afterIdx) { auto &actions = m.Actions(); for (; afterIdx < actions.size(); afterIdx++) { auto newEntry = new MacroActionEdit(this, &actions[afterIdx], actions[afterIdx]->GetId()); ui->actionsList->Add(newEntry); } ui->actionsList->SetHelpMsgVisible(actions.size() == 0); } void AdvSceneSwitcher::PopulateMacroElseActions(Macro &m, uint32_t afterIdx) { auto &actions = m.ElseActions(); for (; afterIdx < actions.size(); afterIdx++) { auto newEntry = new MacroActionEdit(this, &actions[afterIdx], actions[afterIdx]->GetId()); ui->elseActionsList->Add(newEntry); } ui->elseActionsList->SetHelpMsgVisible(actions.size() == 0); } void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, uint32_t afterIdx) { bool root = afterIdx == 0; auto &conditions = m.Conditions(); for (; afterIdx < conditions.size(); afterIdx++) { auto newEntry = new MacroConditionEdit( this, &conditions[afterIdx], conditions[afterIdx]->GetId(), root); ui->conditionsList->Add(newEntry); root = false; } ui->conditionsList->SetHelpMsgVisible(conditions.size() == 0); } void AdvSceneSwitcher::SetActionData(Macro &m) { auto &actions = m.Actions(); for (int idx = 0; idx < ui->actionsList->ContentLayout()->count(); idx++) { auto item = ui->actionsList->ContentLayout()->itemAt(idx); if (!item) { continue; } auto widget = static_cast(item->widget()); if (!widget) { continue; } widget->SetEntryData(&*(actions.begin() + idx)); } } void AdvSceneSwitcher::SetElseActionData(Macro &m) { auto &actions = m.ElseActions(); for (int idx = 0; idx < ui->elseActionsList->ContentLayout()->count(); idx++) { auto item = ui->elseActionsList->ContentLayout()->itemAt(idx); if (!item) { continue; } auto widget = static_cast(item->widget()); if (!widget) { continue; } widget->SetEntryData(&*(actions.begin() + idx)); } } void AdvSceneSwitcher::SetConditionData(Macro &m) { auto &conditions = m.Conditions(); for (int idx = 0; idx < ui->conditionsList->ContentLayout()->count(); idx++) { auto item = ui->conditionsList->ContentLayout()->itemAt(idx); if (!item) { continue; } auto widget = static_cast(item->widget()); if (!widget) { continue; } widget->SetEntryData(&*(conditions.begin() + idx)); } } static void maximizeFirstSplitterEntry(QSplitter *splitter) { QList newSizes; newSizes << 999999; for (int i = 0; i < splitter->sizes().size() - 1; i++) { newSizes << 0; } splitter->setSizes(newSizes); } static void centerSplitterPosition(QSplitter *splitter) { splitter->setSizes(QList() << 999999 << 999999); } void AdvSceneSwitcher::SetEditMacro(Macro &m) { { const QSignalBlocker b1(ui->macroName); const QSignalBlocker b2(ui->runMacroInParallel); const QSignalBlocker b3(ui->runMacroOnChange); ui->macroName->setText(m.Name().c_str()); ui->runMacroInParallel->setChecked(m.RunInParallel()); ui->runMacroOnChange->setChecked(m.MatchOnChange()); } ui->conditionsList->Clear(); ui->actionsList->Clear(); ui->elseActionsList->Clear(); m.ResetUIHelpers(); PopulateMacroConditions(m); PopulateMacroActions(m); PopulateMacroElseActions(m); SetMacroEditAreaDisabled(false); currentActionIdx = -1; currentElseActionIdx = -1; currentConditionIdx = -1; HighlightControls(); if (m.IsGroup()) { SetMacroEditAreaDisabled(true); ui->macroName->setEnabled(true); centerSplitterPosition(ui->macroActionConditionSplitter); maximizeFirstSplitterEntry(ui->macroElseActionSplitter); return; } if (!m.HasValidSplitterPositions()) { centerSplitterPosition(ui->macroActionConditionSplitter); maximizeFirstSplitterEntry(ui->macroElseActionSplitter); return; } ui->macroActionConditionSplitter->setSizes( m.GetActionConditionSplitterPosition()); ui->macroElseActionSplitter->setSizes( m.GetElseActionSplitterPosition()); } void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable) { ui->macroName->setDisabled(disable); ui->runMacro->setDisabled(disable); ui->runMacroInParallel->setDisabled(disable); ui->runMacroOnChange->setDisabled(disable); ui->macroActions->setDisabled(disable); ui->macroConditions->setDisabled(disable); ui->macroActionConditionSplitter->setDisabled(disable); } void AdvSceneSwitcher::HighlightAction(int idx, QColor color) { ui->actionsList->Highlight(idx, color); } void AdvSceneSwitcher::HighlightElseAction(int idx, QColor color) { ui->elseActionsList->Highlight(idx, color); } void AdvSceneSwitcher::HighlightCondition(int idx, QColor color) { ui->conditionsList->Highlight(idx, color); } std::shared_ptr AdvSceneSwitcher::GetSelectedMacro() { return ui->macros->GetCurrentMacro(); } std::vector> AdvSceneSwitcher::GetSelectedMacros() { return ui->macros->GetCurrentMacros(); } void AdvSceneSwitcher::MacroSelectionAboutToChange() { if (loading) { return; } if (!ui->macroName->isEnabled()) { // No macro is selected return; } auto macro = GetMacroByQString(ui->macroName->text()); if (!macro) { return; } macro->SetActionConditionSplitterPosition( ui->macroActionConditionSplitter->sizes()); auto elsePos = ui->macroElseActionSplitter->sizes(); // If only conditions are visible maximize the actions to avoid neither // actions nor elseActions being visible when the condition <-> action // splitter is moved if (elsePos[0] == 0 && elsePos[1] == 0) { maximizeFirstSplitterEntry(ui->macroElseActionSplitter); return; } macro->SetElseActionSplitterPosition( ui->macroElseActionSplitter->sizes()); } void AdvSceneSwitcher::MacroSelectionChanged() { if (loading) { return; } auto macro = GetSelectedMacro(); if (!macro) { SetMacroEditAreaDisabled(true); ui->conditionsList->Clear(); ui->actionsList->Clear(); ui->elseActionsList->Clear(); ui->conditionsList->SetHelpMsgVisible(true); ui->actionsList->SetHelpMsgVisible(true); ui->elseActionsList->SetHelpMsgVisible(true); centerSplitterPosition(ui->macroActionConditionSplitter); maximizeFirstSplitterEntry(ui->macroElseActionSplitter); return; } SetEditMacro(*macro); obs_frontend_save(); } void AdvSceneSwitcher::HighlightOnChange() { if (!GetGlobalMacroSettings()._highlightActions && !GetGlobalMacroSettings()._highlightExecuted) { return; } auto macro = GetSelectedMacro(); if (!macro) { return; } if (macro->OnChangePreventedActionsRecently()) { HighlightWidget(ui->runMacroOnChange, Qt::yellow, Qt::transparent, true); } } void AdvSceneSwitcher::on_macroSettings_clicked() { GlobalMacroSettings prop = GetGlobalMacroSettings(); bool accepted = MacroSettingsDialog::AskForSettings( this, prop, GetSelectedMacro().get()); if (!accepted) { return; } GetGlobalMacroSettings() = prop; emit HighlightMacrosChanged(prop._highlightExecuted); } static void moveControlsToSplitter(QSplitter *splitter, int idx, QLayoutItem *item) { static int splitterHandleWidth = 32; auto handle = splitter->handle(idx); auto layout = item->layout(); int leftMargin, rightMargin; layout->getContentsMargins(&leftMargin, nullptr, &rightMargin, nullptr); layout->setContentsMargins(leftMargin, 0, rightMargin, 0); handle->setLayout(layout); splitter->setHandleWidth(splitterHandleWidth); splitter->setStyleSheet("QSplitter::handle {background: transparent;}"); } static bool shouldRestoreSplitter(const QList &pos) { if (pos.size() == 0) { return false; } for (int i = 0; i < pos.size(); ++i) { if (pos[i] == 0) { return false; } } return true; } static void runSegmentHighligtChecksHelper(MacroSegmentList *list) { MacroSegmentEdit *widget = nullptr; for (int i = 0; (widget = list->WidgetAt(i)); i++) { if (widget->Data() && widget->Data()->GetHighlightAndReset()) { list->Highlight(i); } } } static void runSegmentHighligtChecks(AdvSceneSwitcher *ss) { if (!ss || !HighlightUIElementsEnabled()) { return; } auto macro = ss->GetSelectedMacro(); if (!macro) { return; } const auto &settings = GetGlobalMacroSettings(); if (settings._highlightConditions) { runSegmentHighligtChecksHelper(ss->ui->conditionsList); } if (settings._highlightActions) { runSegmentHighligtChecksHelper(ss->ui->actionsList); runSegmentHighligtChecksHelper(ss->ui->elseActionsList); } } static QToolBar * setupToolBar(const std::initializer_list> &widgetGroups) { auto toolbar = new QToolBar(); toolbar->setIconSize({16, 16}); QAction *lastSeperator = nullptr; for (const auto &widgetGroup : widgetGroups) { for (const auto &widget : widgetGroup) { toolbar->addWidget(widget); } lastSeperator = toolbar->addSeparator(); } if (lastSeperator) { toolbar->removeAction(lastSeperator); } // Prevent "extension" button from showing up toolbar->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); return toolbar; } void AdvSceneSwitcher::SetupMacroTab() { ui->macroElseActions->installEventFilter(this); ui->macros->installEventFilter(this); if (GetMacros().size() == 0 && !switcher->disableHints) { addPulse = HighlightWidget(ui->macroAdd, QColor(Qt::green)); } auto macroControls = setupToolBar({{ui->macroAdd, ui->macroRemove}, {ui->macroUp, ui->macroDown}}); ui->macroControlLayout->addWidget(macroControls); ui->macros->Reset(GetMacros(), GetGlobalMacroSettings()._highlightExecuted); connect(ui->macros, SIGNAL(MacroSelectionAboutToChange()), this, SLOT(MacroSelectionAboutToChange())); connect(ui->macros, SIGNAL(MacroSelectionChanged()), this, SLOT(MacroSelectionChanged())); ui->runMacro->SetMacroTree(ui->macros); ui->conditionsList->SetHelpMsg( obs_module_text("AdvSceneSwitcher.macroTab.editConditionHelp")); connect(ui->conditionsList, &MacroSegmentList::SelectionChanged, this, &AdvSceneSwitcher::MacroConditionSelectionChanged); connect(ui->conditionsList, &MacroSegmentList::Reorder, this, &AdvSceneSwitcher::MacroConditionReorder); ui->actionsList->SetHelpMsg( obs_module_text("AdvSceneSwitcher.macroTab.editActionHelp")); connect(ui->actionsList, &MacroSegmentList::SelectionChanged, this, &AdvSceneSwitcher::MacroActionSelectionChanged); connect(ui->actionsList, &MacroSegmentList::Reorder, this, &AdvSceneSwitcher::MacroActionReorder); ui->elseActionsList->SetHelpMsg(obs_module_text( "AdvSceneSwitcher.macroTab.editElseActionHelp")); connect(ui->elseActionsList, &MacroSegmentList::SelectionChanged, this, &AdvSceneSwitcher::MacroElseActionSelectionChanged); connect(ui->elseActionsList, &MacroSegmentList::Reorder, this, &AdvSceneSwitcher::MacroElseActionReorder); ui->macros->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->macros, &QWidget::customContextMenuRequested, this, &AdvSceneSwitcher::ShowMacroContextMenu); ui->actionsList->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->actionsList, &QWidget::customContextMenuRequested, this, &AdvSceneSwitcher::ShowMacroActionsContextMenu); ui->elseActionsList->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->elseActionsList, &QWidget::customContextMenuRequested, this, &AdvSceneSwitcher::ShowMacroElseActionsContextMenu); ui->conditionsList->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->conditionsList, &QWidget::customContextMenuRequested, this, &AdvSceneSwitcher::ShowMacroConditionsContextMenu); SetMacroEditAreaDisabled(true); ui->macroPriorityWarning->setVisible( switcher->functionNamesByPriority[0] != macro_func); onChangeHighlightTimer.setInterval(1500); connect(&onChangeHighlightTimer, SIGNAL(timeout()), this, SLOT(HighlightOnChange())); onChangeHighlightTimer.start(); // Set action and condition toolbars const std::string pathPrefix = GetDataFilePath("res/images/" + GetThemeTypeName()); SetButtonIcon(ui->actionTop, (pathPrefix + "DoubleUp.svg").c_str()); SetButtonIcon(ui->actionBottom, (pathPrefix + "DoubleDown.svg").c_str()); SetButtonIcon(ui->elseActionTop, (pathPrefix + "DoubleUp.svg").c_str()); SetButtonIcon(ui->elseActionBottom, (pathPrefix + "DoubleDown.svg").c_str()); SetButtonIcon(ui->conditionTop, (pathPrefix + "DoubleUp.svg").c_str()); SetButtonIcon(ui->conditionBottom, (pathPrefix + "DoubleDown.svg").c_str()); SetButtonIcon(ui->toggleElseActions, (pathPrefix + "NotEqual.svg").c_str()); auto conditionToolbar = setupToolBar({{ui->conditionAdd, ui->conditionRemove}, {ui->conditionTop, ui->conditionUp, ui->conditionDown, ui->conditionBottom}}); auto actionToolbar = setupToolBar({{ui->actionAdd, ui->actionRemove}, {ui->actionTop, ui->actionUp, ui->actionDown, ui->actionBottom}}); auto elseActionToolbar = setupToolBar({{ui->elseActionAdd, ui->elseActionRemove}, {ui->elseActionTop, ui->elseActionUp, ui->elseActionDown, ui->elseActionBottom}}); ui->conditionControlsLayout->addWidget(conditionToolbar); ui->actionControlsLayout->insertWidget(0, actionToolbar); ui->elseActionControlsLayout->addWidget(elseActionToolbar); // Move condition controls into splitter handle layout moveControlsToSplitter(ui->macroActionConditionSplitter, 1, ui->macroConditionsLayout->takeAt(1)); moveControlsToSplitter(ui->macroElseActionSplitter, 1, ui->macroActionsLayout->takeAt(1)); // Override splitter cursor icon when hovering over controls in splitter SetCursorOnWidgetHover(ui->conditionAdd, Qt::CursorShape::ArrowCursor); SetCursorOnWidgetHover(ui->conditionRemove, Qt::CursorShape::ArrowCursor); SetCursorOnWidgetHover(ui->conditionTop, Qt::CursorShape::ArrowCursor); SetCursorOnWidgetHover(ui->conditionUp, Qt::CursorShape::ArrowCursor); SetCursorOnWidgetHover(ui->conditionDown, Qt::CursorShape::ArrowCursor); SetCursorOnWidgetHover(ui->conditionBottom, Qt::CursorShape::ArrowCursor); SetCursorOnWidgetHover(ui->actionAdd, Qt::CursorShape::ArrowCursor); SetCursorOnWidgetHover(ui->actionRemove, Qt::CursorShape::ArrowCursor); SetCursorOnWidgetHover(ui->actionTop, Qt::CursorShape::ArrowCursor); SetCursorOnWidgetHover(ui->actionUp, Qt::CursorShape::ArrowCursor); SetCursorOnWidgetHover(ui->actionDown, Qt::CursorShape::ArrowCursor); SetCursorOnWidgetHover(ui->actionBottom, Qt::CursorShape::ArrowCursor); // Reserve more space for macro edit area than for the macro list ui->macroListMacroEditSplitter->setStretchFactor(0, 1); ui->macroListMacroEditSplitter->setStretchFactor(1, 4); centerSplitterPosition(ui->macroActionConditionSplitter); maximizeFirstSplitterEntry(ui->macroElseActionSplitter); if (switcher->saveWindowGeo) { if (shouldRestoreSplitter( switcher->macroListMacroEditSplitterPosition)) { ui->macroListMacroEditSplitter->setSizes( switcher->macroListMacroEditSplitterPosition); } } SetupSegmentCopyPasteShortcutHandlers(this); // Macro segment highlight auto timer = new QTimer(this); connect(timer, &QTimer::timeout, [this]() { runSegmentHighligtChecks(this); }); timer->start(1500); } void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos) { QPoint globalPos = ui->macros->mapToGlobal(pos); QMenu menu; menu.addAction( obs_module_text("AdvSceneSwitcher.macroTab.contextMenuAdd"), this, &AdvSceneSwitcher::on_macroAdd_clicked); auto copy = menu.addAction( obs_module_text("AdvSceneSwitcher.macroTab.copy"), this, &AdvSceneSwitcher::CopyMacro); copy->setEnabled(ui->macros->SingleItemSelected() && !ui->macros->GroupsSelected()); menu.addSeparator(); auto group = menu.addAction( obs_module_text("AdvSceneSwitcher.macroTab.group"), ui->macros, &MacroTree::GroupSelectedItems); group->setDisabled(ui->macros->GroupedItemsSelected() || ui->macros->GroupsSelected() || ui->macros->SelectionEmpty()); auto ungroup = menu.addAction( obs_module_text("AdvSceneSwitcher.macroTab.ungroup"), ui->macros, &MacroTree::UngroupSelectedGroups); ungroup->setEnabled(ui->macros->GroupsSelected()); menu.addSeparator(); auto rename = menu.addAction( obs_module_text("AdvSceneSwitcher.macroTab.rename"), this, &AdvSceneSwitcher::RenameSelectedMacro); rename->setEnabled(ui->macros->SingleItemSelected()); auto remove = menu.addAction( obs_module_text("AdvSceneSwitcher.macroTab.remove"), this, &AdvSceneSwitcher::on_macroRemove_clicked); remove->setDisabled(ui->macros->SelectionEmpty()); menu.addSeparator(); auto exportAction = menu.addAction( obs_module_text("AdvSceneSwitcher.macroTab.export"), this, &AdvSceneSwitcher::ExportMacros); exportAction->setDisabled(ui->macros->SelectionEmpty()); menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.import"), this, &AdvSceneSwitcher::ImportMacros); menu.exec(globalPos); } static bool handleCustomLabelRename(MacroSegmentEdit *segmentEdit) { std::string label; auto segment = segmentEdit->Data(); if (!segment) { return false; } bool accepted = NameDialog::AskForName( GetSettingsWindow(), obs_module_text( "AdvSceneSwitcher.macroTab.segment.setCustomLabel"), "", label, QString::fromStdString(segment->GetCustomLabel())); if (!accepted) { return false; } segment->SetCustomLabel(label); segmentEdit->HeaderInfoChanged(""); return true; } static void handleCustomLabelEnableChange(MacroSegmentEdit *segmentEdit, QAction *contextMenuOption) { bool enable = contextMenuOption->isChecked(); auto segment = segmentEdit->Data(); segment->SetUseCustomLabel(enable); if (!enable) { segmentEdit->HeaderInfoChanged( QString::fromStdString(segment->GetShortDesc())); return; } if (!handleCustomLabelRename(segmentEdit)) { segment->SetUseCustomLabel(false); } } static void setupSegmentLabelContextMenuEntries(MacroSegmentEdit *segmentEdit, QMenu &menu) { if (!segmentEdit) { return; } auto segment = segmentEdit ? segmentEdit->Data() : nullptr; const bool customLabelIsEnabled = segment && segment->GetUseCustomLabel(); auto enableCustomLabel = menu.addAction(obs_module_text( "AdvSceneSwitcher.macroTab.segment.useCustomLabel")); enableCustomLabel->setCheckable(true); enableCustomLabel->setChecked(customLabelIsEnabled); QWidget::connect(enableCustomLabel, &QAction::triggered, [segmentEdit, enableCustomLabel]() { handleCustomLabelEnableChange( segmentEdit, enableCustomLabel); }); if (!customLabelIsEnabled) { return; } auto customLabelRename = menu.addAction(obs_module_text( "AdvSceneSwitcher.macroTab.segment.customLabelRename")); QWidget::connect(customLabelRename, &QAction::triggered, [segmentEdit]() { handleCustomLabelRename(segmentEdit); }); } static void setupCopyPasteContextMenuEnry(AdvSceneSwitcher *ss, MacroSegmentEdit *segmentEdit, QMenu &menu) { auto copy = menu.addAction( obs_module_text("AdvSceneSwitcher.macroTab.segment.copy"), ss, [ss]() { ss->CopyMacroSegment(); }); copy->setEnabled(!!segmentEdit); const char *pasteText = "AdvSceneSwitcher.macroTab.segment.paste"; if (MacroActionIsInClipboard()) { if (IsCursorInWidgetArea(ss->ui->macroActions)) { pasteText = "AdvSceneSwitcher.macroTab.segment.pasteAction"; } else if (IsCursorInWidgetArea(ss->ui->macroElseActions)) { pasteText = "AdvSceneSwitcher.macroTab.segment.pasteElseAction"; } } auto paste = menu.addAction(obs_module_text(pasteText), ss, [ss]() { ss->PasteMacroSegment(); }); paste->setEnabled(MacroSegmentIsInClipboard()); } static void setupConextMenu(AdvSceneSwitcher *ss, const QPoint &pos, std::function expand, std::function collapse, std::function maximize, std::function minimize, MacroSegmentList *list) { QMenu menu; auto segmentEdit = list->WidgetAt(pos); setupCopyPasteContextMenuEnry(ss, segmentEdit, menu); menu.addSeparator(); setupSegmentLabelContextMenuEntries(segmentEdit, menu); menu.addSeparator(); menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"), ss, [ss, expand]() { expand(ss); }); menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"), ss, [ss, collapse]() { collapse(ss); }); menu.addSeparator(); menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"), ss, [ss, maximize]() { maximize(ss); }); menu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"), ss, [ss, minimize]() { minimize(ss); }); menu.exec(list->mapToGlobal(pos)); } void AdvSceneSwitcher::ShowMacroActionsContextMenu(const QPoint &pos) { setupConextMenu(this, pos, &AdvSceneSwitcher::ExpandAllActions, &AdvSceneSwitcher::CollapseAllActions, &AdvSceneSwitcher::MaximizeActions, &AdvSceneSwitcher::MinimizeActions, ui->actionsList); } void AdvSceneSwitcher::ShowMacroElseActionsContextMenu(const QPoint &pos) { setupConextMenu(this, pos, &AdvSceneSwitcher::ExpandAllElseActions, &AdvSceneSwitcher::CollapseAllElseActions, &AdvSceneSwitcher::MaximizeElseActions, &AdvSceneSwitcher::MinimizeElseActions, ui->elseActionsList); } void AdvSceneSwitcher::ShowMacroConditionsContextMenu(const QPoint &pos) { setupConextMenu(this, pos, &AdvSceneSwitcher::ExpandAllConditions, &AdvSceneSwitcher::CollapseAllConditions, &AdvSceneSwitcher::MaximizeConditions, &AdvSceneSwitcher::MinimizeConditions, ui->conditionsList); } void AdvSceneSwitcher::CopyMacro() { const auto macro = GetSelectedMacro(); if (!macro) { return; } std::string format = macro->Name() + " %1"; std::string name; std::shared_ptr newMacro; if (!AddNewMacro(newMacro, name, format)) { return; } OBSDataAutoRelease data = obs_data_create(); macro->Save(data, true); newMacro->Load(data); newMacro->PostLoad(); newMacro->SetName(name); RunPostLoadSteps(); Macro::PrepareMoveToGroup(macro->Parent(), newMacro); ui->macros->Add(newMacro, macro); if (addPulse) { addPulse->deleteLater(); addPulse = nullptr; } emit MacroAdded(QString::fromStdString(name)); } void setCollapsedHelper(const std::shared_ptr &m, MacroSegmentList *list, bool collapsed) { if (!m) { return; } list->SetCollapsed(collapsed); } void AdvSceneSwitcher::ExpandAllActions() { setCollapsedHelper(GetSelectedMacro(), ui->actionsList, false); } void AdvSceneSwitcher::ExpandAllElseActions() { setCollapsedHelper(GetSelectedMacro(), ui->elseActionsList, false); } void AdvSceneSwitcher::ExpandAllConditions() { setCollapsedHelper(GetSelectedMacro(), ui->conditionsList, false); } void AdvSceneSwitcher::CollapseAllActions() { setCollapsedHelper(GetSelectedMacro(), ui->actionsList, true); } void AdvSceneSwitcher::CollapseAllElseActions() { setCollapsedHelper(GetSelectedMacro(), ui->elseActionsList, true); } void AdvSceneSwitcher::CollapseAllConditions() { setCollapsedHelper(GetSelectedMacro(), ui->conditionsList, true); } static void reduceSizeOfSplitterIdx(QSplitter *splitter, int idx) { auto sizes = splitter->sizes(); int sum = sizes[0] + sizes[1]; int reducedSize = sum / 10; sizes[idx] = reducedSize; sizes[(idx + 1) % 2] = sum - reducedSize; splitter->setSizes(sizes); } void AdvSceneSwitcher::MinimizeActions() { auto macro = GetSelectedMacro(); if (!macro) { return; } if (macro->ElseActions().size() == 0) { reduceSizeOfSplitterIdx(ui->macroActionConditionSplitter, 1); } else { maximizeFirstSplitterEntry(ui->macroElseActionSplitter); reduceSizeOfSplitterIdx(ui->macroActionConditionSplitter, 1); } } void AdvSceneSwitcher::MaximizeActions() { MinimizeElseActions(); MinimizeConditions(); } void AdvSceneSwitcher::MinimizeElseActions() { auto macro = GetSelectedMacro(); if (!macro) { return; } if (macro->ElseActions().size() == 0) { maximizeFirstSplitterEntry(ui->macroElseActionSplitter); } else { reduceSizeOfSplitterIdx(ui->macroElseActionSplitter, 1); } } void AdvSceneSwitcher::MaximizeElseActions() { MinimizeConditions(); reduceSizeOfSplitterIdx(ui->macroElseActionSplitter, 0); } void AdvSceneSwitcher::MinimizeConditions() { reduceSizeOfSplitterIdx(ui->macroActionConditionSplitter, 0); } void AdvSceneSwitcher::MaximizeConditions() { MinimizeElseActions(); MinimizeActions(); } void AdvSceneSwitcher::on_toggleElseActions_clicked() { auto elsePosition = ui->macroElseActionSplitter->sizes(); if (elsePosition[1] == 0) { centerSplitterPosition(ui->macroElseActionSplitter); return; } maximizeFirstSplitterEntry(ui->macroElseActionSplitter); } void AdvSceneSwitcher::SetElseActionsStateToHidden() { ui->toggleElseActions->setToolTip(obs_module_text( "AdvSceneSwitcher.macroTab.toggleElseActions.show.tooltip")); ui->toggleElseActions->setChecked(false); } void AdvSceneSwitcher::SetElseActionsStateToVisible() { ui->toggleElseActions->setToolTip(obs_module_text( "AdvSceneSwitcher.macroTab.toggleElseActions.hide.tooltip")); ui->toggleElseActions->setChecked(true); } bool AdvSceneSwitcher::MacroTabIsInFocus() { return isActiveWindow() && isAncestorOf(focusWidget()) && (ui->tabWidget->currentWidget()->objectName() == "macroTab"); } void AdvSceneSwitcher::UpMacroSegementHotkey() { if (!MacroTabIsInFocus()) { return; } auto macro = GetSelectedMacro(); if (!macro) { return; } int actionSize = macro->Actions().size(); int conditionSize = macro->Conditions().size(); if (currentActionIdx == -1 && currentConditionIdx == -1) { if (lastInteracted == MacroSection::CONDITIONS) { if (conditionSize == 0) { MacroActionSelectionChanged(0); } else { MacroConditionSelectionChanged(0); } } else { if (actionSize == 0) { MacroConditionSelectionChanged(0); } else { MacroActionSelectionChanged(0); } } return; } if (currentActionIdx > 0) { MacroActionSelectionChanged(currentActionIdx - 1); return; } if (currentConditionIdx > 0) { MacroConditionSelectionChanged(currentConditionIdx - 1); return; } if (currentActionIdx == 0) { if (conditionSize == 0) { MacroActionSelectionChanged(actionSize - 1); } else { MacroConditionSelectionChanged(conditionSize - 1); } return; } if (currentConditionIdx == 0) { if (actionSize == 0) { MacroConditionSelectionChanged(conditionSize - 1); } else { MacroActionSelectionChanged(actionSize - 1); } return; } } void AdvSceneSwitcher::DownMacroSegementHotkey() { if (!MacroTabIsInFocus()) { return; } auto macro = GetSelectedMacro(); if (!macro) { return; } int actionSize = macro->Actions().size(); int conditionSize = macro->Conditions().size(); if (currentActionIdx == -1 && currentConditionIdx == -1) { if (lastInteracted == MacroSection::CONDITIONS) { if (conditionSize == 0) { MacroActionSelectionChanged(0); } else { MacroConditionSelectionChanged(0); } } else { if (actionSize == 0) { MacroConditionSelectionChanged(0); } else { MacroActionSelectionChanged(0); } } return; } if (currentActionIdx < actionSize - 1) { MacroActionSelectionChanged(currentActionIdx + 1); return; } if (currentConditionIdx < conditionSize - 1) { MacroConditionSelectionChanged(currentConditionIdx + 1); return; } if (currentActionIdx == actionSize - 1) { if (conditionSize == 0) { MacroActionSelectionChanged(0); } else { MacroConditionSelectionChanged(0); } return; } if (currentConditionIdx == conditionSize - 1) { if (actionSize == 0) { MacroConditionSelectionChanged(0); } else { MacroActionSelectionChanged(0); } return; } } void AdvSceneSwitcher::DeleteMacroSegementHotkey() { if (!MacroTabIsInFocus()) { return; } if (currentActionIdx != -1) { RemoveMacroAction(currentActionIdx); } else if (currentConditionIdx != -1) { RemoveMacroCondition(currentConditionIdx); } } static void fade(QWidget *widget, bool fadeOut) { const double fadeOutOpacity = 0.3; // Don't use exactly 1.0 as for some reason this causes buttons in // macroSplitter handle layout to not be redrawn unless mousing over // them const double fadeInOpacity = 0.99; auto curEffect = widget->graphicsEffect(); if (curEffect) { auto curOpacity = dynamic_cast(curEffect); if (curOpacity && ((fadeOut && DoubleEquals(curOpacity->opacity(), fadeOutOpacity, 0.0001)) || (!fadeOut && DoubleEquals(curOpacity->opacity(), fadeInOpacity, 0.0001)))) { return; } } else if (!fadeOut) { return; } delete curEffect; QGraphicsOpacityEffect *opacityEffect = new QGraphicsOpacityEffect(); widget->setGraphicsEffect(opacityEffect); QPropertyAnimation *animation = new QPropertyAnimation(opacityEffect, "opacity"); animation->setDuration(350); animation->setStartValue(fadeOut ? fadeInOpacity : fadeOutOpacity); animation->setEndValue(fadeOut ? fadeOutOpacity : fadeInOpacity); animation->setEasingCurve(QEasingCurve::OutQuint); animation->start(QPropertyAnimation::DeleteWhenStopped); } static void fadeWidgets(const std::vector &widgets, bool fadeOut) { for (const auto &widget : widgets) { fade(widget, fadeOut); } } void AdvSceneSwitcher::HighlightControls() { const std::vector conditionControls{ ui->conditionAdd, ui->conditionRemove, ui->conditionTop, ui->conditionUp, ui->conditionDown, ui->conditionBottom, }; const std::vector actionControls{ ui->actionAdd, ui->actionRemove, ui->actionTop, ui->actionUp, ui->actionDown, ui->actionBottom, }; const std::vector elseActionControls{ ui->elseActionAdd, ui->elseActionRemove, ui->elseActionTop, ui->elseActionUp, ui->elseActionDown, ui->elseActionBottom, }; if ((currentActionIdx == -1 && currentConditionIdx == -1 && currentElseActionIdx == -1)) { fadeWidgets(conditionControls, false); fadeWidgets(actionControls, false); fadeWidgets(elseActionControls, false); } else if (currentConditionIdx != -1) { fadeWidgets(conditionControls, false); fadeWidgets(actionControls, true); fadeWidgets(elseActionControls, true); } else if (currentActionIdx != -1) { fadeWidgets(conditionControls, true); fadeWidgets(actionControls, false); fadeWidgets(elseActionControls, true); } else if (currentElseActionIdx != -1) { fadeWidgets(conditionControls, true); fadeWidgets(actionControls, true); fadeWidgets(elseActionControls, false); } else { assert(false); } } } // namespace advss