diff --git a/CMakeLists.txt b/CMakeLists.txt index ed7477a1..5a29ae60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -223,6 +223,7 @@ target_sources( lib/variables/variable-spinbox.hpp lib/variables/variable-string.cpp lib/variables/variable-string.hpp + lib/variables/variable-tab.cpp lib/variables/variable-text-edit.cpp lib/variables/variable-text-edit.hpp lib/variables/variable.cpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 4b7eb76a..383f165c 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -65,6 +65,18 @@ AdvSceneSwitcher.generalTab.transitionOverride="Set transition overrides" AdvSceneSwitcher.generalTab.adjustActiveTransitionType="Change active transition type" AdvSceneSwitcher.generalTab.transitionBehaviorSelectionError="At least one option must be enabled:\n\n - Use transition overrides\n\n - Change active transition type" +; Variables Tab +AdvSceneSwitcher.variableTab.title="Variables" +AdvSceneSwitcher.variableTab.help="Variables can be used in many places throughout the plugin.\n\nClick on the highlighted plus symbol to add a new variable." +AdvSceneSwitcher.variableTab.tooltip.variableAddButton="Add new variable" +AdvSceneSwitcher.variableTab.tooltip.variableRemoveButton="Remove selected variables" +AdvSceneSwitcher.variableTab.header.name="Name" +AdvSceneSwitcher.variableTab.header.value="Value" +AdvSceneSwitcher.variableTab.header.saveLoadBehavior="Save/Load behavior" +AdvSceneSwitcher.variableTab.header.lastUse="Last used" +AdvSceneSwitcher.variableTab.neverNused="Never" +AdvSceneSwitcher.variableTab.lastUsed="%1 seconds ago" + ; Macro Tab AdvSceneSwitcher.macroTab.title="Macro" AdvSceneSwitcher.macroTab.macros="Macros" diff --git a/forms/advanced-scene-switcher.ui b/forms/advanced-scene-switcher.ui index 9b1ef626..c9e788a2 100644 --- a/forms/advanced-scene-switcher.ui +++ b/forms/advanced-scene-switcher.ui @@ -1577,6 +1577,104 @@ + + + AdvSceneSwitcher.variableTab.title + + + + + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SelectRows + + + false + + + false + + + false + + + + + + + AdvSceneSwitcher.variableTab.help + + + Qt::AlignCenter + + + true + + + + + + + + + + + + 22 + 22 + + + + AdvSceneSwitcher.variableTab.tooltip.variableAddButton + + + true + + + addIconSmall + + + + + + + + 22 + 22 + + + + AdvSceneSwitcher.variableTab.tooltip.variableRemoveButton + + + true + + + removeIconSmall + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + AdvSceneSwitcher.networkTab.title diff --git a/lib/advanced-scene-switcher.cpp b/lib/advanced-scene-switcher.cpp index 7d258813..c91444f0 100644 --- a/lib/advanced-scene-switcher.cpp +++ b/lib/advanced-scene-switcher.cpp @@ -111,6 +111,7 @@ void AdvSceneSwitcher::LoadUI() SetupSceneGroupTab(); SetupTriggerTab(); SetupMacroTab(); + SetupVariableTab(); SetDeprecationWarnings(); SetTabOrder(); diff --git a/lib/advanced-scene-switcher.hpp b/lib/advanced-scene-switcher.hpp index f84fc6c2..90556315 100644 --- a/lib/advanced-scene-switcher.hpp +++ b/lib/advanced-scene-switcher.hpp @@ -208,6 +208,16 @@ private: /* --- End of macro tab section --- */ + /* --- Begin of variable tab section --- */ +public: + void SetupVariableTab(); + +public slots: + void on_variableAdd_clicked(); + void on_variableRemove_clicked(); + + /* --- End of variable tab section --- */ + /* --- Begin of legacy tab section --- */ public: void ClearFrames(QListWidget *list); diff --git a/lib/general.cpp b/lib/general.cpp index 7b7e5d21..0567f7b9 100644 --- a/lib/general.cpp +++ b/lib/general.cpp @@ -18,6 +18,17 @@ namespace advss { +static constexpr std::array tabNames = { + "generalTab", "macroTab", "variableTab", + "windowTitleTab", "executableTab", "screenRegionTab", + "mediaTab", "fileTab", "randomTab", + "timeTab", "idleTab", "sceneSequenceTab", + "audioTab", "videoTab", "networkTab", + "sceneGroupTab", "transitionsTab", "pauseTab", + "sceneTriggerTab"}; + +static std::vector tabOrder = std::vector(tabNames.size()); + void AdvSceneSwitcher::reject() { close(); @@ -355,65 +366,7 @@ void AdvSceneSwitcher::on_importSettings_clicked() static int findTabIndex(QTabWidget *tabWidget, int pos) { int at = -1; - - QString tabName = ""; - switch (pos) { - case 0: - tabName = "generalTab"; - break; - case 1: - tabName = "macroTab"; - break; - case 2: - tabName = "transitionsTab"; - break; - case 3: - tabName = "pauseTab"; - break; - case 4: - tabName = "windowTitleTab"; - break; - case 5: - tabName = "executableTab"; - break; - case 6: - tabName = "screenRegionTab"; - break; - case 7: - tabName = "mediaTab"; - break; - case 8: - tabName = "fileTab"; - break; - case 9: - tabName = "randomTab"; - break; - case 10: - tabName = "timeTab"; - break; - case 11: - tabName = "idleTab"; - break; - case 12: - tabName = "sceneSequenceTab"; - break; - case 13: - tabName = "audioTab"; - break; - case 14: - tabName = "videoTab"; - break; - case 15: - tabName = "networkTab"; - break; - case 16: - tabName = "sceneGroupTab"; - break; - case 17: - tabName = "sceneTriggerTab"; - break; - } - + QString tabName = tabNames.at(pos); QWidget *page = tabWidget->findChild(tabName); if (page) { at = tabWidget->indexOf(page); @@ -426,15 +379,35 @@ static int findTabIndex(QTabWidget *tabWidget, int pos) return at; } +static bool tabWidgetOrderValid() +{ + auto tmp = std::vector(tabNames.size()); + std::iota(tmp.begin(), tmp.end(), 0); + + for (auto &p : tmp) { + auto it = std::find(tabOrder.begin(), tabOrder.end(), p); + if (it == tabOrder.end()) { + return false; + } + } + return true; +} + +static void resetTabWidgetOrder() +{ + tabOrder = std::vector(tabNames.size()); + std::iota(tabOrder.begin(), tabOrder.end(), 0); +} + void AdvSceneSwitcher::SetTabOrder() { - if (!switcher->TabOrderValid()) { - switcher->ResetTabOrder(); + if (!tabWidgetOrderValid()) { + resetTabWidgetOrder(); } QTabBar *bar = ui->tabWidget->tabBar(); for (int i = 0; i < bar->count(); ++i) { - int curPos = findTabIndex(ui->tabWidget, switcher->tabOrder[i]); + int curPos = findTabIndex(ui->tabWidget, tabOrder[i]); if (i != curPos && curPos != -1) { bar->moveTab(curPos, i); @@ -479,7 +452,7 @@ void AdvSceneSwitcher::on_tabMoved(int from, int to) return; } - std::swap(switcher->tabOrder[from], switcher->tabOrder[to]); + std::swap(tabOrder[from], tabOrder[to]); } void AdvSceneSwitcher::on_tabWidget_currentChanged(int) @@ -712,24 +685,13 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj) void SwitcherData::SaveUISettings(obs_data_t *obj) { - obs_data_set_int(obj, "generalTabPos", tabOrder[0]); - obs_data_set_int(obj, "macroTabPos", tabOrder[1]); - obs_data_set_int(obj, "transitionTabPos", tabOrder[2]); - obs_data_set_int(obj, "pauseTabPos", tabOrder[3]); - obs_data_set_int(obj, "titleTabPos", tabOrder[4]); - obs_data_set_int(obj, "exeTabPos", tabOrder[5]); - obs_data_set_int(obj, "regionTabPos", tabOrder[6]); - obs_data_set_int(obj, "mediaTabPos", tabOrder[7]); - obs_data_set_int(obj, "fileTabPos", tabOrder[8]); - obs_data_set_int(obj, "randomTabPos", tabOrder[9]); - obs_data_set_int(obj, "timeTabPos", tabOrder[10]); - obs_data_set_int(obj, "idleTabPos", tabOrder[11]); - obs_data_set_int(obj, "sequenceTabPos", tabOrder[12]); - obs_data_set_int(obj, "audioTabPos", tabOrder[13]); - obs_data_set_int(obj, "videoTabPos", tabOrder[14]); - obs_data_set_int(obj, "networkTabPos", tabOrder[15]); - obs_data_set_int(obj, "sceneGroupTabPos", tabOrder[16]); - obs_data_set_int(obj, "triggerTabPos", tabOrder[17]); + OBSDataArrayAutoRelease tabWidgetOrder = obs_data_array_create(); + for (size_t i = 0; i < tabNames.size(); i++) { + OBSDataAutoRelease entry = obs_data_create(); + obs_data_set_int(entry, tabNames[i], tabOrder[i]); + obs_data_array_push_back(tabWidgetOrder, entry); + } + obs_data_set_array(obj, "tabWidgetOrder", tabWidgetOrder); obs_data_set_bool(obj, "saveWindowGeo", saveWindowGeo); obs_data_set_int(obj, "windowPosX", windowPos.x()); @@ -743,47 +705,27 @@ void SwitcherData::SaveUISettings(obs_data_t *obj) void SwitcherData::LoadUISettings(obs_data_t *obj) { - obs_data_set_default_int(obj, "generalTabPos", 0); - obs_data_set_default_int(obj, "macroTabPos", 1); - obs_data_set_default_int(obj, "networkTabPos", 13); - obs_data_set_default_int(obj, "sceneGroupTabPos", 14); - obs_data_set_default_int(obj, "transitionTabPos", 15); - obs_data_set_default_int(obj, "pauseTabPos", 16); - obs_data_set_default_int(obj, "titleTabPos", 2); - obs_data_set_default_int(obj, "exeTabPos", 3); - obs_data_set_default_int(obj, "regionTabPos", 4); - obs_data_set_default_int(obj, "mediaTabPos", 5); - obs_data_set_default_int(obj, "fileTabPos", 6); - obs_data_set_default_int(obj, "randomTabPos", 7); - obs_data_set_default_int(obj, "timeTabPos", 8); - obs_data_set_default_int(obj, "idleTabPos", 9); - obs_data_set_default_int(obj, "sequenceTabPos", 10); - obs_data_set_default_int(obj, "audioTabPos", 11); - obs_data_set_default_int(obj, "videoTabPos", 12); - obs_data_set_default_int(obj, "triggerTabPos", 17); + OBSDataArrayAutoRelease defaultTabWidgetOrder = obs_data_array_create(); + for (size_t i = 0; i < tabNames.size(); i++) { + OBSDataAutoRelease entry = obs_data_create(); + obs_data_set_default_int(entry, tabNames[i], i); + obs_data_array_push_back(defaultTabWidgetOrder, entry); + } + obs_data_set_default_array(obj, "tabWidgetOrder", + defaultTabWidgetOrder); tabOrder.clear(); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "generalTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "macroTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "transitionTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "pauseTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "titleTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "exeTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "regionTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "mediaTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "fileTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "randomTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "timeTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "idleTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "sequenceTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "audioTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "videoTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "networkTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "sceneGroupTabPos"))); - tabOrder.emplace_back((int)(obs_data_get_int(obj, "triggerTabPos"))); + OBSDataArrayAutoRelease tabWidgetOrder = + obs_data_get_array(obj, "tabWidgetOrder"); + for (size_t i = 0; i < tabNames.size(); i++) { + OBSDataAutoRelease entry = + obs_data_array_item(tabWidgetOrder, i); + tabOrder.emplace_back( + (int)(obs_data_get_int(entry, tabNames[i]))); + } - if (!TabOrderValid()) { - ResetTabOrder(); + if (!tabWidgetOrderValid()) { + resetTabWidgetOrder(); } saveWindowGeo = obs_data_get_bool(obj, "saveWindowGeo"); @@ -796,26 +738,6 @@ void SwitcherData::LoadUISettings(obs_data_t *obj) "macroListMacroEditSplitterPosition"); } -bool SwitcherData::TabOrderValid() -{ - auto tmp = std::vector(tab_count); - std::iota(tmp.begin(), tmp.end(), 0); - - for (auto &p : tmp) { - auto it = std::find(tabOrder.begin(), tabOrder.end(), p); - if (it == tabOrder.end()) { - return false; - } - } - return true; -} - -void SwitcherData::ResetTabOrder() -{ - tabOrder = std::vector(tab_count); - std::iota(tabOrder.begin(), tabOrder.end(), 0); -} - void SwitcherData::CheckNoMatchSwitch(bool &match, OBSWeakSource &scene, OBSWeakSource &transition, int &sleep) { diff --git a/lib/switcher-data.hpp b/lib/switcher-data.hpp index d8faafb4..a83c2bf4 100644 --- a/lib/switcher-data.hpp +++ b/lib/switcher-data.hpp @@ -32,7 +32,6 @@ namespace advss { constexpr auto default_interval = 300; -constexpr auto tab_count = 18; typedef const char *(*translateFunc)(const char *); @@ -89,8 +88,6 @@ public: void LoadUISettings(obs_data_t *obj); bool VersionChanged(obs_data_t *obj, std::string currentVersion); - bool TabOrderValid(); - void ResetTabOrder(); bool PrioFuncsValid(); /* --- End of saving / loading section --- */ @@ -179,7 +176,6 @@ public: bool disableHints = false; bool disableFilterComboboxFilter = false; bool hideLegacyTabs = true; - std::vector tabOrder = std::vector(tab_count); bool saveWindowGeo = false; QPoint windowPos = {}; QSize windowSize = {}; diff --git a/lib/variables/variable-tab.cpp b/lib/variables/variable-tab.cpp new file mode 100644 index 00000000..169bb301 --- /dev/null +++ b/lib/variables/variable-tab.cpp @@ -0,0 +1,296 @@ +#include "advanced-scene-switcher.hpp" +#include "variable.hpp" + +#include + +namespace advss { + +static void setVariableTabVisible(QTabWidget *tabWidget, bool visible) +{ + for (int idx = 0; idx < tabWidget->count(); idx++) { + if (tabWidget->tabText(idx) != + obs_module_text("AdvSceneSwitcher.variableTab.title")) { + continue; + } +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + // TODO: Switch to setTabVisible() once QT 5.15 is more wide spread + tabWidget->setTabEnabled(idx, visible); + tabWidget->setStyleSheet( + "QTabBar::tab::disabled {width: 0; height: 0; margin: 0; padding: 0; border: none;} "); +#else + tabWidget->setTabVisible(idx, visible); +#endif + } +} + +static QString getSaveActionString(Variable *variable) +{ + QString saveAction; + switch (variable->GetSaveAction()) { + case Variable::SaveAction::DONT_SAVE: + saveAction = obs_module_text( + "AdvSceneSwitcher.variable.save.dontSave"); + break; + case Variable::SaveAction::SAVE: + saveAction = + obs_module_text("AdvSceneSwitcher.variable.save.save"); + break; + case Variable::SaveAction::SET_DEFAULT: + saveAction = + QString(obs_module_text( + "AdvSceneSwitcher.variable.save.default")) + + " \"" + + QString::fromStdString(variable->GetDefaultValue()) + + "\""; + break; + default: + break; + } + return saveAction; +} + +static QString getLastUsedString(Variable *variable) +{ + auto lastUsed = variable->SecondsSinceLastUse(); + if (!lastUsed) { + return obs_module_text( + "AdvSceneSwitcher.variableTab.neverNused"); + } + QString fmt = obs_module_text("AdvSceneSwitcher.variableTab.lastUsed"); + return fmt.arg(QString::number(*lastUsed)); +} + +static void addVariableRow(QTableWidget *table, Variable *variable) +{ + if (!variable) { + blog(LOG_INFO, "%s called with nullptr", __func__); + assert(false); + return; + } + + int row = table->rowCount(); + table->setRowCount(row + 1); + + int col = 0; + auto *item = + new QTableWidgetItem(QString::fromStdString(variable->Name())); + table->setItem(row, col, item); + col++; + item = new QTableWidgetItem( + QString::fromStdString(variable->Value(false))); + table->setItem(row, col, item); + col++; + item = new QTableWidgetItem(getSaveActionString(variable)); + table->setItem(row, col, item); + col++; + item = new QTableWidgetItem(getLastUsedString(variable)); + table->setItem(row, col, item); + table->sortByColumn(0, Qt::AscendingOrder); +} + +static void removeVariableRow(QTableWidget *table, const QString &name) +{ + for (int row = 0; row < table->rowCount(); ++row) { + auto item = table->item(row, 0); + if (item && item->text() == name) { + table->removeRow(row); + return; + } + } + table->sortByColumn(0, Qt::AscendingOrder); +} + +static void updateVaribleStatus(QTableWidget *table) +{ + auto lock = LockContext(); + for (int row = 0; row < table->rowCount(); row++) { + auto item = table->item(row, 0); + if (!item) { + continue; + } + auto weakVariable = GetWeakVariableByQString(item->text()); + auto variable = weakVariable.lock(); + if (!variable) { + continue; + } + item = table->item(row, 1); + item->setText(QString::fromStdString(variable->Value(false))); + item = table->item(row, 2); + item->setText(getSaveActionString(variable.get())); + item = table->item(row, 3); + item->setText(getLastUsedString(variable.get())); + } +} + +static void renameVariable(QTableWidget *table, const QString &oldName, + const QString &newName) +{ + for (int row = 0; row < table->rowCount(); row++) { + auto item = table->item(row, 0); + if (!item) { + continue; + } + if (item->text() == oldName) { + item->setText(newName); + table->sortByColumn(0, Qt::AscendingOrder); + return; + } + } + blog(LOG_INFO, "%s called but entry \"%s\" not found", __func__, + oldName.toStdString().c_str()); + assert(false); +} + +static void openSettingsDialogAtRow(QTableWidget *table, int row) +{ + auto item = table->item(row, 0); + if (!item) { + return; + } + auto weakVariable = GetWeakVariableByQString(item->text()); + auto variable = weakVariable.lock(); + if (!variable) { + return; + } + auto oldName = variable->Name(); + + bool accepted = + VariableSettingsDialog::AskForSettings(table, *variable.get()); + if (accepted && oldName != variable->Name()) { + VariableSignalManager::Instance()->Rename( + QString::fromStdString(oldName), + QString::fromStdString(variable->Name())); + } +} + +void AdvSceneSwitcher::SetupVariableTab() +{ + if (GetVariables().empty()) { + setVariableTabVisible(ui->tabWidget, false); + } else { + ui->variablesHelp->hide(); + } + + static const QStringList horizontalHeaders = + QStringList() + << obs_module_text("AdvSceneSwitcher.variableTab.header.name") + << obs_module_text("AdvSceneSwitcher.variableTab.header.value") + << obs_module_text( + "AdvSceneSwitcher.variableTab.header.saveLoadBehavior") + << obs_module_text( + "AdvSceneSwitcher.variableTab.header.lastUse"); + + auto &variables = GetVariables(); + + ui->variables->setColumnCount(horizontalHeaders.size()); + ui->variables->horizontalHeader()->setSectionResizeMode( + QHeaderView::ResizeMode::Stretch); + ui->variables->setHorizontalHeaderLabels(horizontalHeaders); + + for (const auto &var : variables) { + auto variable = std::static_pointer_cast(var); + addVariableRow(ui->variables, variable.get()); + } + + ui->variables->resizeColumnsToContents(); + ui->variables->resizeRowsToContents(); + + QWidget::connect( + VariableSignalManager::Instance(), + &VariableSignalManager::Rename, + [this](const QString &oldName, const QString &newName) { + renameVariable(ui->variables, oldName, newName); + }); + QWidget::connect(VariableSignalManager::Instance(), + &VariableSignalManager::Add, this, + [this](const QString &name) { + addVariableRow(ui->variables, + GetVariableByQString(name)); + ui->variablesHelp->hide(); + setVariableTabVisible(ui->tabWidget, true); + }); + QWidget::connect(VariableSignalManager::Instance(), + &VariableSignalManager::Remove, this, + [this](const QString &name) { + removeVariableRow(ui->variables, name); + if (ui->variables->rowCount() == 0) { + ui->variablesHelp->show(); + } + }); + QWidget::connect(ui->variables, &QTableWidget::cellDoubleClicked, + [this](int row, int _) { + openSettingsDialogAtRow(ui->variables, row); + }); + + auto timer = new QTimer(this); + timer->setInterval(1000); + QWidget::connect(timer, &QTimer::timeout, + [this]() { updateVaribleStatus(ui->variables); }); + timer->start(); +} + +void AdvSceneSwitcher::on_variableAdd_clicked() +{ + auto newVariable = std::make_shared(); + auto accepted = + VariableSettingsDialog::AskForSettings(this, *newVariable); + if (!accepted) { + return; + } + { + auto lock = LockContext(); + auto &variables = GetVariables(); + variables.emplace_back(newVariable); + } + + VariableSignalManager::Instance()->Add( + QString::fromStdString(newVariable->Name())); +} + +void AdvSceneSwitcher::on_variableRemove_clicked() +{ + auto selectedItems = ui->variables->selectedItems(); + QList selectedRows; + for (auto item : selectedItems) { + int row = item->row(); + if (!selectedRows.contains(row)) { + selectedRows.append(row); + } + } + + if (selectedRows.empty()) { + return; + } + + QStringList names; + for (int row : selectedRows) { + auto item = ui->variables->item(row, 0); + if (!item) { + continue; + } + names << item->text(); + } + + for (const auto &name : names) { + VariableSignalManager::Instance()->Remove(name); + } + + auto lock = LockContext(); + for (const auto &name : names) { + auto variable = GetVariableByQString(name); + if (!variable) { + continue; + } + + auto &variables = GetVariables(); + variables.erase( + std::remove_if( + variables.begin(), variables.end(), + [variable](const std::shared_ptr &item) { + return item.get() == variable; + }), + variables.end()); + } +} + +} // namespace advss