#include "utility.hpp" #include "platform-funcs.hpp" #include "scene-selection.hpp" #include "regex-config.hpp" #include "scene-group.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace advss { bool WeakSourceValid(obs_weak_source_t *ws) { obs_source_t *source = obs_weak_source_get_source(ws); if (source) { obs_source_release(source); } return !!source; } std::string GetWeakSourceName(obs_weak_source_t *weak_source) { std::string name; obs_source_t *source = obs_weak_source_get_source(weak_source); if (source) { name = obs_source_get_name(source); obs_source_release(source); } return name; } OBSWeakSource GetWeakSourceByName(const char *name) { OBSWeakSource weak; obs_source_t *source = obs_get_source_by_name(name); if (source) { weak = obs_source_get_weak_source(source); obs_weak_source_release(weak); obs_source_release(source); } return weak; } OBSWeakSource GetWeakSourceByQString(const QString &name) { return GetWeakSourceByName(name.toUtf8().constData()); } OBSWeakSource GetWeakTransitionByName(const char *transitionName) { OBSWeakSource weak; obs_source_t *source = nullptr; if (strcmp(transitionName, "Default") == 0) { source = obs_frontend_get_current_transition(); weak = obs_source_get_weak_source(source); obs_source_release(source); obs_weak_source_release(weak); return weak; } obs_frontend_source_list *transitions = new obs_frontend_source_list(); obs_frontend_get_transitions(transitions); bool match = false; for (size_t i = 0; i < transitions->sources.num; i++) { const char *name = obs_source_get_name(transitions->sources.array[i]); if (strcmp(transitionName, name) == 0) { match = true; source = transitions->sources.array[i]; break; } } if (match) { weak = obs_source_get_weak_source(source); obs_weak_source_release(weak); } obs_frontend_source_list_free(transitions); return weak; } OBSWeakSource GetWeakTransitionByQString(const QString &name) { return GetWeakTransitionByName(name.toUtf8().constData()); } OBSWeakSource GetWeakFilterByName(OBSWeakSource source, const char *name) { OBSWeakSource weak; auto s = obs_weak_source_get_source(source); if (s) { auto filterSource = obs_source_get_filter_by_name(s, name); weak = obs_source_get_weak_source(filterSource); obs_weak_source_release(weak); obs_source_release(filterSource); obs_source_release(s); } return weak; } OBSWeakSource GetWeakFilterByQString(OBSWeakSource source, const QString &name) { return GetWeakFilterByName(source, name.toUtf8().constData()); } std::string getNextDelim(const std::string &text, std::unordered_map placeholders) { size_t pos = std::string::npos; std::string res = ""; for (const auto &ph : placeholders) { size_t newPos = text.find(ph.first); if (newPos <= pos) { pos = newPos; res = ph.first; } } if (pos == std::string::npos) { return ""; } return res; } std::pair getSceneItemSize(obs_scene_item *item) { std::pair size; obs_source_t *source = obs_sceneitem_get_source(item); size.first = double(obs_source_get_width(source)); size.second = double(obs_source_get_height(source)); return size; } std::string GetSceneItemTransform(obs_scene_item *item) { struct obs_transform_info info; struct obs_sceneitem_crop crop; obs_sceneitem_get_info(item, &info); obs_sceneitem_get_crop(item, &crop); auto size = getSceneItemSize(item); auto data = obs_data_create(); SaveTransformState(data, info, crop); obs_data_t *obj = obs_data_create(); obs_data_set_double(obj, "width", size.first * info.scale.x); obs_data_set_double(obj, "height", size.second * info.scale.y); obs_data_set_obj(data, "size", obj); obs_data_release(obj); auto json = std::string(obs_data_get_json(data)); obs_data_release(data); return json; } void PlaceWidgets(std::string text, QBoxLayout *layout, std::unordered_map placeholders, bool addStretch) { std::vector> labelsWidgetsPairs; std::string delim = getNextDelim(text, placeholders); while (delim != "") { size_t pos = text.find(delim); if (pos != std::string::npos) { labelsWidgetsPairs.emplace_back(text.substr(0, pos), placeholders[delim]); text.erase(0, pos + delim.length()); } delim = getNextDelim(text, placeholders); } if (text != "") { labelsWidgetsPairs.emplace_back(text, nullptr); } for (auto &lw : labelsWidgetsPairs) { if (lw.first != "") { layout->addWidget(new QLabel(lw.first.c_str())); } if (lw.second) { layout->addWidget(lw.second); } } if (addStretch) { layout->addStretch(); } } void DeleteLayoutItemWidget(QLayoutItem *item) { if (item) { auto widget = item->widget(); if (widget) { widget->setVisible(false); widget->deleteLater(); } delete item; } } void ClearLayout(QLayout *layout, int afterIdx) { QLayoutItem *item; while ((item = layout->takeAt(afterIdx))) { if (item->layout()) { ClearLayout(item->layout()); delete item->layout(); } DeleteLayoutItemWidget(item); } } void SetLayoutVisible(QLayout *layout, bool visible) { if (!layout) { return; } for (int i = 0; i < layout->count(); ++i) { QWidget *widget = layout->itemAt(i)->widget(); QLayout *nestedLayout = layout->itemAt(i)->layout(); if (widget) { widget->setVisible(visible); } if (nestedLayout) { SetLayoutVisible(nestedLayout, visible); } } } void MinimizeSizeOfColumn(QGridLayout *layout, int idx) { if (idx >= layout->columnCount()) { return; } for (int i = 0; i < layout->columnCount(); i++) { if (i == idx) { layout->setColumnStretch(i, 0); } else { layout->setColumnStretch(i, 1); } } int columnWidth = 0; for (int row = 0; row < layout->rowCount(); row++) { auto item = layout->itemAtPosition(row, idx); if (!item) { continue; } auto widget = item->widget(); if (!widget) { continue; } columnWidth = std::max(columnWidth, widget->minimumSizeHint().width()); } layout->setColumnMinimumWidth(idx, columnWidth); } bool CompareIgnoringLineEnding(QString &s1, QString &s2) { // Let QT deal with different types of lineendings QTextStream s1stream(&s1); QTextStream s2stream(&s2); while (!s1stream.atEnd() || !s2stream.atEnd()) { QString s1s = s1stream.readLine(); QString s2s = s2stream.readLine(); if (s1s != s2s) { return false; } } if (!s1stream.atEnd() && !s2stream.atEnd()) { return false; } return true; } std::string GetSourceSettings(OBSWeakSource ws) { if (!ws) { return ""; } std::string settings; auto s = obs_weak_source_get_source(ws); obs_data_t *data = obs_source_get_settings(s); auto json = obs_data_get_json(data); if (json) { settings = json; } obs_data_release(data); obs_source_release(s); return settings; } void SetSourceSettings(obs_source_t *s, const std::string &settings) { if (settings.empty()) { return; } obs_data_t *data = obs_data_create_from_json(settings.c_str()); if (!data) { blog(LOG_WARNING, "invalid source settings provided: \n%s", settings.c_str()); return; } obs_source_update(s, data); obs_data_release(data); } // Match json1 with pattern json2 bool MatchJson(const std::string &json1, const std::string &json2, const RegexConfig ®ex) { auto j1 = FormatJsonString(json1).toStdString(); auto j2 = FormatJsonString(json2).toStdString(); if (j1.empty()) { j1 = json1; } if (j2.empty()) { j2 = json2; } if (regex.Enabled()) { auto expr = regex.GetRegularExpression(j2); if (!expr.isValid()) { return false; } auto match = expr.match(QString::fromStdString(j1)); return match.hasMatch(); } return j1 == j2; } bool CompareSourceSettings(const OBSWeakSource &source, const std::string &settings, const RegexConfig ®ex) { std::string currentSettings = GetSourceSettings(source); return MatchJson(currentSettings, settings, regex); } std::string GetDataFilePath(const std::string &file) { std::string root_path = obs_get_module_data_path(obs_current_module()); if (!root_path.empty()) { return root_path + "/" + file; } return ""; } QString FormatJsonString(std::string s) { return FormatJsonString(QString::fromStdString(s)); } QString FormatJsonString(QString json) { QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8()); return QString::fromUtf8(doc.toJson(QJsonDocument::Indented)); } QString EscapeForRegex(QString &s) { std::regex specialChars{R"([-[\]{}()*+?.,\^$|#\s])"}; std::string input = s.toStdString(); return QString::fromStdString( std::regex_replace(input, specialChars, R"(\$&)")); } void LoadTransformState(obs_data_t *obj, struct obs_transform_info &info, struct obs_sceneitem_crop &crop) { obs_data_get_vec2(obj, "pos", &info.pos); obs_data_get_vec2(obj, "scale", &info.scale); info.rot = (float)obs_data_get_double(obj, "rot"); info.alignment = (uint32_t)obs_data_get_int(obj, "alignment"); info.bounds_type = (enum obs_bounds_type)obs_data_get_int(obj, "bounds_type"); info.bounds_alignment = (uint32_t)obs_data_get_int(obj, "bounds_alignment"); obs_data_get_vec2(obj, "bounds", &info.bounds); crop.top = (int)obs_data_get_int(obj, "top"); crop.bottom = (int)obs_data_get_int(obj, "bottom"); crop.left = (int)obs_data_get_int(obj, "left"); crop.right = (int)obs_data_get_int(obj, "right"); } bool SaveTransformState(obs_data_t *obj, const struct obs_transform_info &info, const struct obs_sceneitem_crop &crop) { struct vec2 pos = info.pos; struct vec2 scale = info.scale; float rot = info.rot; uint32_t alignment = info.alignment; uint32_t bounds_type = info.bounds_type; uint32_t bounds_alignment = info.bounds_alignment; struct vec2 bounds = info.bounds; obs_data_set_vec2(obj, "pos", &pos); obs_data_set_vec2(obj, "scale", &scale); obs_data_set_double(obj, "rot", rot); obs_data_set_int(obj, "alignment", alignment); obs_data_set_int(obj, "bounds_type", bounds_type); obs_data_set_vec2(obj, "bounds", &bounds); obs_data_set_int(obj, "bounds_alignment", bounds_alignment); obs_data_set_int(obj, "top", crop.top); obs_data_set_int(obj, "bottom", crop.bottom); obs_data_set_int(obj, "left", crop.left); obs_data_set_int(obj, "right", crop.right); return true; } bool DisplayMessage(const QString &msg, bool question) { if (question) { QMessageBox::StandardButton reply; reply = QMessageBox::question( nullptr, "Advanced Scene Switcher", msg, QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { return true; } else { return false; } } else { QMessageBox Msgbox; Msgbox.setWindowTitle("Advanced Scene Switcher"); Msgbox.setText(msg); Msgbox.exec(); } return false; } void DisplayTrayMessage(const QString &title, const QString &msg) { auto tray = reinterpret_cast( obs_frontend_get_system_tray()); if (!tray) { return; } tray->showMessage(title, msg); } void AddSelectionEntry(QComboBox *sel, const char *description, bool selectable, const char *tooltip) { sel->insertItem(0, description); if (strcmp(tooltip, "") != 0) { sel->setItemData(0, tooltip, Qt::ToolTipRole); } QStandardItemModel *model = qobject_cast(sel->model()); QModelIndex firstIndex = model->index(0, sel->modelColumn(), sel->rootModelIndex()); QStandardItem *firstItem = model->itemFromIndex(firstIndex); if (!selectable) { firstItem->setSelectable(false); firstItem->setEnabled(false); } } QStringList GetSourceNames() { auto sourceEnum = [](void *param, obs_source_t *source) -> bool /* -- */ { QStringList *list = reinterpret_cast(param); *list << obs_source_get_name(source); return true; }; QStringList list; obs_enum_sources(sourceEnum, &list); return list; } void PopulateSourceSelection(QComboBox *list, bool addSelect) { auto sources = GetSourceNames(); sources.sort(); list->addItems(sources); if (addSelect) { AddSelectionEntry( list, obs_module_text("AdvSceneSwitcher.selectSource"), false); } list->setCurrentIndex(0); } void PopulateTransitionSelection(QComboBox *sel, bool addCurrent, bool addAny) { obs_frontend_source_list *transitions = new obs_frontend_source_list(); obs_frontend_get_transitions(transitions); for (size_t i = 0; i < transitions->sources.num; i++) { const char *name = obs_source_get_name(transitions->sources.array[i]); sel->addItem(name); } obs_frontend_source_list_free(transitions); sel->model()->sort(0); AddSelectionEntry(sel, obs_module_text("AdvSceneSwitcher.selectTransition")); sel->setCurrentIndex(0); if (addCurrent) { sel->insertItem( 1, obs_module_text("AdvSceneSwitcher.currentTransition")); } if (addAny) { sel->insertItem( 1, obs_module_text("AdvSceneSwitcher.anyTransition")); } } void PopulateWindowSelection(QComboBox *sel, bool addSelect) { std::vector windows; GetWindowList(windows); for (std::string &window : windows) { sel->addItem(window.c_str()); } sel->model()->sort(0); if (addSelect) { AddSelectionEntry( sel, obs_module_text("AdvSceneSwitcher.selectWindow")); } sel->setCurrentIndex(0); #ifdef WIN32 sel->setItemData(0, obs_module_text("AdvSceneSwitcher.selectWindowTip"), Qt::ToolTipRole); #endif } QStringList GetAudioSourceNames() { auto sourceEnum = [](void *param, obs_source_t *source) -> bool /* -- */ { QStringList *list = reinterpret_cast(param); uint32_t flags = obs_source_get_output_flags(source); if ((flags & OBS_SOURCE_AUDIO) != 0) { *list << obs_source_get_name(source); } return true; }; QStringList list; obs_enum_sources(sourceEnum, &list); return list; } void PopulateAudioSelection(QComboBox *sel, bool addSelect) { auto sources = GetAudioSourceNames(); sources.sort(); sel->addItems(sources); if (addSelect) { AddSelectionEntry( sel, obs_module_text("AdvSceneSwitcher.selectAudioSource"), false, obs_module_text( "AdvSceneSwitcher.invaildEntriesWillNotBeSaved")); } sel->setCurrentIndex(0); } QStringList GetVideoSourceNames() { auto sourceEnum = [](void *param, obs_source_t *source) -> bool /* -- */ { QStringList *list = reinterpret_cast(param); uint32_t flags = obs_source_get_output_flags(source); std::string test = obs_source_get_name(source); if ((flags & (OBS_SOURCE_VIDEO | OBS_SOURCE_ASYNC)) != 0) { *list << obs_source_get_name(source); } return true; }; QStringList list; obs_enum_sources(sourceEnum, &list); return list; } void populateVideoSelection(QComboBox *sel, bool addMainOutput, bool addScenes, bool addSelect) { auto sources = GetVideoSourceNames(); sources.sort(); sel->addItems(sources); if (addScenes) { auto scenes = GetSceneNames(); scenes.sort(); sel->addItems(scenes); } sel->model()->sort(0); if (addMainOutput) { sel->insertItem( 0, obs_module_text("AdvSceneSwitcher.OBSVideoOutput")); } if (addSelect) { AddSelectionEntry( sel, obs_module_text("AdvSceneSwitcher.selectVideoSource"), false, obs_module_text( "AdvSceneSwitcher.invaildEntriesWillNotBeSaved")); } sel->setCurrentIndex(0); } QStringList GetMediaSourceNames() { auto sourceEnum = [](void *param, obs_source_t *source) -> bool /* -- */ { QStringList *list = reinterpret_cast(param); std::string sourceId = obs_source_get_id(source); if (sourceId.compare("ffmpeg_source") == 0 || sourceId.compare("vlc_source") == 0 || sourceId.compare("slideshow") == 0) { *list << obs_source_get_name(source); } return true; }; QStringList list; obs_enum_sources(sourceEnum, &list); return list; } void PopulateMediaSelection(QComboBox *sel, bool addSelect) { auto sources = GetMediaSourceNames(); sources.sort(); sel->addItems(sources); if (addSelect) { AddSelectionEntry( sel, obs_module_text("AdvSceneSwitcher.selectMediaSource"), false, obs_module_text( "AdvSceneSwitcher.invaildEntriesWillNotBeSaved")); } sel->setCurrentIndex(0); } void PopulateProcessSelection(QComboBox *sel, bool addSelect) { QStringList processes; GetProcessList(processes); processes.sort(); for (QString &process : processes) { sel->addItem(process); } sel->model()->sort(0); if (addSelect) { AddSelectionEntry( sel, obs_module_text("AdvSceneSwitcher.selectProcess")); } sel->setCurrentIndex(0); } QStringList GetSceneNames() { QStringList list; char **scenes = obs_frontend_get_scene_names(); char **temp = scenes; while (*temp) { const char *name = *temp; list << name; temp++; } bfree(scenes); return list; } void PopulateSceneSelection(QComboBox *sel, bool addPrevious, bool addCurrent, bool addAny, bool addSceneGroup, std::deque *sceneGroups, bool addSelect, std::string selectText, bool selectable) { auto sceneNames = GetSceneNames(); sel->addItems(sceneNames); if (addSceneGroup && sceneGroups) { for (auto &sg : *sceneGroups) { sel->addItem(QString::fromStdString(sg.name)); } } sel->model()->sort(0); if (addSelect) { if (selectText.empty()) { AddSelectionEntry( sel, obs_module_text("AdvSceneSwitcher.selectScene"), selectable, obs_module_text( "AdvSceneSwitcher.invaildEntriesWillNotBeSaved")); } else { AddSelectionEntry(sel, selectText.c_str(), selectable); } } sel->setCurrentIndex(0); if (addPrevious) { sel->insertItem( 1, obs_module_text( "AdvSceneSwitcher.selectPreviousScene")); } if (addCurrent) { sel->insertItem( 1, obs_module_text("AdvSceneSwitcher.selectCurrentScene")); } if (addAny) { sel->insertItem( 1, obs_module_text("AdvSceneSwitcher.selectAnyScene")); } } static inline void hasFilterEnum(obs_source_t *, obs_source_t *filter, void *ptr) { if (!filter) { return; } bool *hasFilter = reinterpret_cast(ptr); *hasFilter = true; } QStringList GetSourcesWithFilterNames() { auto enumSourcesWithFilters = [](void *param, obs_source_t *source) { if (!source) { return true; } QStringList *list = reinterpret_cast(param); bool hasFilter = false; obs_source_enum_filters(source, hasFilterEnum, &hasFilter); if (hasFilter) { *list << obs_source_get_name(source); } return true; }; QStringList list; obs_enum_sources(enumSourcesWithFilters, &list); obs_enum_scenes(enumSourcesWithFilters, &list); return list; } void PopulateSourcesWithFilterSelection(QComboBox *list) { auto sources = GetSourcesWithFilterNames(); sources.sort(); list->addItems(sources); AddSelectionEntry(list, obs_module_text("AdvSceneSwitcher.selectSource")); list->setCurrentIndex(0); } void PopulateFilterSelection(QComboBox *list, OBSWeakSource weakSource) { auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *ptr) { QComboBox *list = reinterpret_cast(ptr); auto name = obs_source_get_name(filter); list->addItem(name); }; auto s = obs_weak_source_get_source(weakSource); obs_source_enum_filters(s, enumFilters, list); list->model()->sort(0); AddSelectionEntry(list, obs_module_text("AdvSceneSwitcher.selectFilter")); obs_source_release(s); list->setCurrentIndex(0); } void populateTypeList(std::set &list, std::function enumFunc) { size_t idx = 0; char buffer[512] = {}; const char **idPtr = (const char **)(&buffer); while (enumFunc(idx++, idPtr)) { // The audio_line source type is only used OBS internally if (strcmp(*idPtr, "audio_line") == 0) { continue; } QString name = obs_source_get_display_name(*idPtr); if (name.isEmpty()) { list.insert(*idPtr); } else { list.insert(name); } } } void PopulateSourceGroupSelection(QComboBox *list) { std::set sourceTypes; populateTypeList(sourceTypes, obs_enum_source_types); std::set filterTypes; populateTypeList(filterTypes, obs_enum_filter_types); std::set transitionTypes; populateTypeList(transitionTypes, obs_enum_transition_types); for (const auto &name : sourceTypes) { if (name.isEmpty()) { continue; } if (filterTypes.find(name) == filterTypes.end() && transitionTypes.find(name) == transitionTypes.end()) { list->addItem(name); } } list->model()->sort(0); AddSelectionEntry(list, obs_module_text("AdvSceneSwitcher.selectItem")); list->setCurrentIndex(0); } void PopulateProfileSelection(QComboBox *box) { auto profiles = obs_frontend_get_profiles(); char **temp = profiles; while (*temp) { const char *name = *temp; box->addItem(name); temp++; } bfree(profiles); box->model()->sort(0); AddSelectionEntry( box, obs_module_text("AdvSceneSwitcher.selectProfile"), false); box->setCurrentIndex(0); } void PopulateMonitorTypeSelection(QComboBox *list) { list->addItem(obs_module_text("AdvSceneSwitcher.audio.monitor.none")); list->addItem( obs_module_text("AdvSceneSwitcher.audio.monitor.monitorOnly")); list->addItem(obs_module_text("AdvSceneSwitcher.audio.monitor.both")); } bool WindowPosValid(QPoint pos) { return !!QGuiApplication::screenAt(pos); } std::string GetThemeTypeName() { #if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(29, 0, 0) return obs_frontend_is_theme_dark() ? "Dark" : "Light"; #else auto mainWindow = static_cast(obs_frontend_get_main_window()); if (!mainWindow) { return "Dark"; } QColor color = mainWindow->palette().text().color(); const bool themeDarkMode = !(color.redF() < 0.5); return themeDarkMode ? "Dark" : "Light"; #endif } QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor, QColor endColor, bool once) { QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect(widget); widget->setGraphicsEffect(effect); QPropertyAnimation *animation = new QPropertyAnimation(effect, "color", widget); animation->setStartValue(startColor); animation->setEndValue(endColor); animation->setDuration(1000); QMetaObject::Connection con; if (once) { auto widgetPtr = widget; con = QWidget::connect( animation, &QPropertyAnimation::finished, [widgetPtr]() { if (widgetPtr) { widgetPtr->setGraphicsEffect(nullptr); } }); animation->start(QPropertyAnimation::DeleteWhenStopped); } else { auto widgetPtr = widget; con = QWidget::connect( animation, &QPropertyAnimation::finished, [animation, widgetPtr]() { QTimer *timer = new QTimer(widgetPtr); QWidget::connect(timer, &QTimer::timeout, [animation] { animation->start(); }); timer->setSingleShot(true); timer->start(1000); }); animation->start(); } return con; } void listAddClicked(QListWidget *list, QWidget *newWidget, QPushButton *addButton, QMetaObject::Connection *addHighlight) { if (!list || !newWidget) { blog(LOG_WARNING, "listAddClicked called without valid list or widget"); return; } if (addButton && addHighlight) { addButton->disconnect(*addHighlight); } QListWidgetItem *item; item = new QListWidgetItem(list); list->addItem(item); item->setSizeHint(newWidget->minimumSizeHint()); list->setItemWidget(item, newWidget); list->scrollToItem(item); } bool listMoveUp(QListWidget *list) { int index = list->currentRow(); if (index == -1 || index == 0) { return false; } QWidget *row = list->itemWidget(list->currentItem()); QListWidgetItem *itemN = list->currentItem()->clone(); list->insertItem(index - 1, itemN); list->setItemWidget(itemN, row); list->takeItem(index + 1); list->setCurrentRow(index - 1); return true; } bool listMoveDown(QListWidget *list) { int index = list->currentRow(); if (index == -1 || index == list->count() - 1) { return false; } QWidget *row = list->itemWidget(list->currentItem()); QListWidgetItem *itemN = list->currentItem()->clone(); list->insertItem(index + 2, itemN); list->setItemWidget(itemN, row); list->takeItem(index); list->setCurrentRow(index + 1); return true; } void SetHeightToContentHeight(QListWidget *list) { auto nrItems = list->count(); if (nrItems == 0) { list->setMaximumHeight(0); } else { list->setMaximumHeight(list->sizeHintForRow(0) * nrItems + 2 * list->frameWidth()); } } bool DoubleEquals(double left, double right, double epsilon) { return (fabs(left - right) < epsilon); } void SetButtonIcon(QPushButton *button, const char *path) { QIcon icon; icon.addFile(QString::fromUtf8(path), QSize(), QIcon::Normal, QIcon::Off); button->setIcon(icon); } void AddSelectionGroup(QComboBox *selection, const QStringList &group, bool addSeparator) { selection->addItems(group); if (addSeparator) { selection->insertSeparator(selection->count()); } } int FindIdxInRagne(QComboBox *list, int start, int stop, const std::string &value) { if (value.empty()) { return 0; } auto model = list->model(); auto startIdx = model->index(start, 0); auto match = model->match(startIdx, Qt::DisplayRole, QString::fromStdString(value), 1, Qt::MatchExactly | Qt::MatchCaseSensitive); if (match.isEmpty()) { return 0; } int foundIdx = match.first().row(); if (foundIdx > stop) { return 0; } return foundIdx; } std::pair GetCursorPos() { auto cursorPos = QCursor::pos(); return {cursorPos.x(), cursorPos.y()}; } void ReplaceAll(std::string &str, const std::string &from, const std::string &to) { if (from.empty()) { return; } size_t start_pos = 0; while ((start_pos = str.find(from, start_pos)) != std::string::npos) { str.replace(start_pos, from.length(), to); start_pos += to.length(); } } QString GetDefaultSettingsSaveLocation() { QString desktopPath = QStandardPaths::writableLocation( QStandardPaths::DesktopLocation); auto scName = obs_frontend_get_current_scene_collection(); QString sceneCollectionName(scName); bfree(scName); auto timestamp = QDateTime::currentDateTime(); auto path = desktopPath + "/adv-ss-" + sceneCollectionName + "-" + timestamp.toString("yyyy.MM.dd.hh.mm.ss"); // Check if scene collection name contains invalid path characters QFile file(path); if (file.exists()) { return path; } bool validPath = file.open(QIODevice::WriteOnly); if (validPath) { file.remove(); return path; } return desktopPath + "/adv-ss-" + timestamp.toString("yyyy.MM.dd.hh.mm.ss"); } } // namespace advss