OBSBasicSourceSelect.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875
  1. /******************************************************************************
  2. Copyright (C) 2023 by Lain Bailey <[email protected]>
  3. Copyright (C) 2025 by Taylor Giampaolo <[email protected]>
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. ******************************************************************************/
  15. #include "OBSBasicSourceSelect.hpp"
  16. #include <OBSApp.hpp>
  17. #include <utility/ResizeSignaler.hpp>
  18. #include <utility/ThumbnailManager.hpp>
  19. #include "qt-wrappers.hpp"
  20. #include <QList>
  21. #include <QMessageBox>
  22. #include "moc_OBSBasicSourceSelect.cpp"
  23. struct AddSourceData {
  24. // Input data
  25. obs_source_t *source;
  26. bool visible;
  27. obs_transform_info *transform = nullptr;
  28. obs_sceneitem_crop *crop = nullptr;
  29. obs_blending_method *blend_method = nullptr;
  30. obs_blending_type *blend_mode = nullptr;
  31. obs_scale_type *scale_type = nullptr;
  32. const char *show_transition_id = nullptr;
  33. const char *hide_transition_id = nullptr;
  34. OBSData show_transition_settings;
  35. OBSData hide_transition_settings;
  36. uint32_t show_transition_duration = 300;
  37. uint32_t hide_transition_duration = 300;
  38. OBSData private_settings;
  39. // Return data
  40. obs_sceneitem_t *scene_item = nullptr;
  41. };
  42. namespace {
  43. QString getSourceDisplayName(QString type)
  44. {
  45. if (type == "scene") {
  46. return QTStr("Basic.Scene");
  47. }
  48. const char *inputChar = obs_get_latest_input_type_id(type.toUtf8().constData());
  49. const char *displayChar = obs_source_get_display_name(inputChar);
  50. std::string displayId = (displayChar) ? displayChar : "";
  51. if (!displayId.empty()) {
  52. return QString::fromStdString(displayId);
  53. } else {
  54. return QString();
  55. }
  56. }
  57. std::string getNewSourceName(std::string_view name)
  58. {
  59. std::string newName{name};
  60. int suffix = 1;
  61. for (;;) {
  62. OBSSourceAutoRelease existing_source = obs_get_source_by_name(newName.c_str());
  63. if (!existing_source) {
  64. break;
  65. }
  66. char nextName[256];
  67. std::snprintf(nextName, sizeof(nextName), "%s %d", name.data(), ++suffix);
  68. newName = nextName;
  69. }
  70. return newName;
  71. }
  72. } // namespace
  73. static void AddSource(void *_data, obs_scene_t *scene)
  74. {
  75. AddSourceData *data = (AddSourceData *)_data;
  76. obs_sceneitem_t *sceneitem;
  77. sceneitem = obs_scene_add(scene, data->source);
  78. if (data->transform != nullptr) {
  79. obs_sceneitem_set_info2(sceneitem, data->transform);
  80. }
  81. if (data->crop != nullptr) {
  82. obs_sceneitem_set_crop(sceneitem, data->crop);
  83. }
  84. if (data->blend_method != nullptr) {
  85. obs_sceneitem_set_blending_method(sceneitem, *data->blend_method);
  86. }
  87. if (data->blend_mode != nullptr) {
  88. obs_sceneitem_set_blending_mode(sceneitem, *data->blend_mode);
  89. }
  90. if (data->scale_type != nullptr) {
  91. obs_sceneitem_set_scale_filter(sceneitem, *data->scale_type);
  92. }
  93. if (data->show_transition_id && *data->show_transition_id) {
  94. OBSSourceAutoRelease source = obs_source_create(data->show_transition_id, data->show_transition_id,
  95. data->show_transition_settings, nullptr);
  96. if (source)
  97. obs_sceneitem_set_transition(sceneitem, true, source);
  98. }
  99. if (data->hide_transition_id && *data->hide_transition_id) {
  100. OBSSourceAutoRelease source = obs_source_create(data->hide_transition_id, data->hide_transition_id,
  101. data->hide_transition_settings, nullptr);
  102. if (source)
  103. obs_sceneitem_set_transition(sceneitem, false, source);
  104. }
  105. obs_sceneitem_set_transition_duration(sceneitem, true, data->show_transition_duration);
  106. obs_sceneitem_set_transition_duration(sceneitem, false, data->hide_transition_duration);
  107. obs_sceneitem_set_visible(sceneitem, data->visible);
  108. if (data->private_settings) {
  109. OBSDataAutoRelease newPrivateSettings = obs_sceneitem_get_private_settings(sceneitem);
  110. obs_data_apply(newPrivateSettings, data->private_settings);
  111. }
  112. data->scene_item = sceneitem;
  113. }
  114. static void AddExisting(OBSSource source, bool visible, bool duplicate, SourceCopyInfo *info = nullptr)
  115. {
  116. OBSBasic *main = OBSBasic::Get();
  117. OBSScene scene = main->GetCurrentScene();
  118. if (!scene) {
  119. return;
  120. }
  121. if (duplicate) {
  122. OBSSource from = source;
  123. std::string new_name = getNewSourceName(obs_source_get_name(source));
  124. source = obs_source_duplicate(from, new_name.c_str(), false);
  125. obs_source_release(source);
  126. if (!source) {
  127. return;
  128. }
  129. }
  130. AddSourceData data;
  131. data.source = source;
  132. data.visible = visible;
  133. if (info) {
  134. data.transform = &info->transform;
  135. data.crop = &info->crop;
  136. data.blend_method = &info->blend_method;
  137. data.blend_mode = &info->blend_mode;
  138. data.scale_type = &info->scale_type;
  139. data.show_transition_id = info->show_transition_id;
  140. data.hide_transition_id = info->hide_transition_id;
  141. data.show_transition_settings = std::move(info->show_transition_settings);
  142. data.hide_transition_settings = std::move(info->hide_transition_settings);
  143. data.show_transition_duration = info->show_transition_duration;
  144. data.hide_transition_duration = info->hide_transition_duration;
  145. data.private_settings = std::move(info->private_settings);
  146. }
  147. obs_enter_graphics();
  148. obs_scene_atomic_update(scene, AddSource, &data);
  149. obs_leave_graphics();
  150. }
  151. static void AddExisting(const char *name, bool visible, bool duplicate)
  152. {
  153. OBSSourceAutoRelease source = obs_get_source_by_name(name);
  154. if (source) {
  155. AddExisting(source.Get(), visible, duplicate);
  156. }
  157. }
  158. bool AddNew(QWidget *parent, const char *id, const char *name, const bool visible, OBSSource &newSource,
  159. OBSSceneItem &newSceneItem)
  160. {
  161. OBSBasic *main = OBSBasic::Get();
  162. OBSScene scene = main->GetCurrentScene();
  163. bool success = false;
  164. if (!scene) {
  165. return false;
  166. }
  167. OBSSourceAutoRelease source = obs_get_source_by_name(name);
  168. if (source && parent) {
  169. OBSMessageBox::information(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text"));
  170. } else {
  171. const char *v_id = obs_get_latest_input_type_id(id);
  172. source = obs_source_create(v_id, name, NULL, nullptr);
  173. if (source) {
  174. AddSourceData data;
  175. data.source = source;
  176. data.visible = visible;
  177. obs_enter_graphics();
  178. obs_scene_atomic_update(scene, AddSource, &data);
  179. obs_leave_graphics();
  180. newSource = source;
  181. newSceneItem = data.scene_item;
  182. // Set monitoring if source monitors by default
  183. uint32_t flags = obs_source_get_output_flags(source);
  184. if ((flags & OBS_SOURCE_MONITOR_BY_DEFAULT) != 0) {
  185. obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_MONITOR_ONLY);
  186. }
  187. success = true;
  188. }
  189. }
  190. return success;
  191. }
  192. OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, undo_stack &undo_s)
  193. : QDialog(parent),
  194. ui(new Ui::OBSBasicSourceSelect),
  195. undo_s(undo_s)
  196. {
  197. setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
  198. ui->setupUi(this);
  199. existingFlowLayout = ui->existingListFrame->flowLayout();
  200. existingFlowLayout->setContentsMargins(0, 0, 0, 0);
  201. existingFlowLayout->setSpacing(0);
  202. /* The scroll viewport is not accessible via Designer, so we have to disable autoFillBackground here.
  203. *
  204. * Additionally when Qt calls setWidget on a scrollArea to set the contents widget, it force sets
  205. * autoFillBackground to true overriding whatever is set in Designer so we have to do that here too.
  206. */
  207. ui->existingScrollArea->viewport()->setAutoFillBackground(false);
  208. ui->existingScrollContents->setAutoFillBackground(false);
  209. auto resizeSignaler = new ResizeSignaler(ui->existingScrollArea);
  210. ui->existingScrollArea->installEventFilter(resizeSignaler);
  211. connect(resizeSignaler, &ResizeSignaler::resized, this, &OBSBasicSourceSelect::checkSourceVisibility);
  212. connect(ui->existingScrollArea->verticalScrollBar(), &QScrollBar::valueChanged, this,
  213. &OBSBasicSourceSelect::checkSourceVisibility);
  214. connect(ui->existingScrollArea->horizontalScrollBar(), &QScrollBar::valueChanged, this,
  215. &OBSBasicSourceSelect::checkSourceVisibility);
  216. ui->createNewFrame->setVisible(false);
  217. ui->deprecatedCreateLabel->setVisible(false);
  218. ui->deprecatedCreateLabel->setProperty("class", "text-muted");
  219. getSourceTypes();
  220. getSources();
  221. updateExistingSources(16);
  222. connect(ui->sourceTypeList, &QListWidget::itemDoubleClicked, this, &OBSBasicSourceSelect::createNewSource);
  223. connect(ui->newSourceName, &QLineEdit::returnPressed, this, &OBSBasicSourceSelect::createNewSource);
  224. connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
  225. connect(ui->addExistingButton, &QAbstractButton::clicked, this, &OBSBasicSourceSelect::addSelectedSources);
  226. connect(this, &OBSBasicSourceSelect::selectedItemsChanged, this, [=]() {
  227. ui->addExistingButton->setEnabled(selectedItems.size() > 0);
  228. if (selectedItems.size() > 0) {
  229. ui->addExistingButton->setText(QTStr("Add %1 Existing").arg(selectedItems.size()));
  230. } else {
  231. ui->addExistingButton->setText("Add Existing");
  232. }
  233. });
  234. App()->DisableHotkeys();
  235. }
  236. OBSBasicSourceSelect::~OBSBasicSourceSelect()
  237. {
  238. App()->UpdateHotkeyFocusSetting();
  239. }
  240. void OBSBasicSourceSelect::checkSourceVisibility()
  241. {
  242. QList<QAbstractButton *> buttons = sourceButtons->buttons();
  243. // Allow some room for previous/next rows to make scrolling a bit more seamless
  244. QRect scrollAreaRect(QPoint(0, 0), ui->existingScrollArea->size());
  245. scrollAreaRect.setTop(scrollAreaRect.top() - Thumbnail::size.width());
  246. scrollAreaRect.setBottom(scrollAreaRect.bottom() + Thumbnail::size.height());
  247. for (QAbstractButton *button : buttons) {
  248. SourceSelectButton *sourceButton = qobject_cast<SourceSelectButton *>(button->parent());
  249. if (sourceButton) {
  250. QRect buttonRect = button->rect();
  251. buttonRect.moveTo(button->mapTo(ui->existingScrollArea, buttonRect.topLeft()));
  252. if (scrollAreaRect.intersects(buttonRect)) {
  253. sourceButton->setPreload(true);
  254. } else {
  255. sourceButton->setPreload(false);
  256. }
  257. }
  258. }
  259. scrollAreaRect = QRect(QPoint(0, 0), ui->existingScrollArea->size());
  260. for (QAbstractButton *button : buttons) {
  261. SourceSelectButton *sourceButton = qobject_cast<SourceSelectButton *>(button->parent());
  262. if (sourceButton) {
  263. QRect buttonRect = button->rect();
  264. buttonRect.moveTo(button->mapTo(ui->existingScrollArea, buttonRect.topLeft()));
  265. if (scrollAreaRect.intersects(buttonRect)) {
  266. sourceButton->setRectVisible(true);
  267. } else {
  268. sourceButton->setRectVisible(false);
  269. }
  270. }
  271. }
  272. }
  273. void OBSBasicSourceSelect::getSources()
  274. {
  275. sources.clear();
  276. obs_enum_sources(enumSourcesCallback, this);
  277. emit sourcesUpdated();
  278. }
  279. void OBSBasicSourceSelect::updateExistingSources(int limit)
  280. {
  281. delete sourceButtons;
  282. sourceButtons = new QButtonGroup(this);
  283. sourceButtons->setExclusive(false);
  284. std::vector<obs_source_t *> matchingSources{};
  285. std::copy_if(sources.begin(), sources.end(), std::back_inserter(matchingSources), [this](obs_source_t *source) {
  286. if (!source || obs_source_removed(source)) {
  287. return false;
  288. }
  289. const char *id = obs_source_get_unversioned_id(source);
  290. QString stringId = QString(id);
  291. if (stringId.compare("group") == 0) {
  292. return false;
  293. }
  294. if (sourceTypeId.compare(stringId) == 0 || sourceTypeId.isNull()) {
  295. return true;
  296. }
  297. return false;
  298. });
  299. QWidget *prevTabWidget = ui->sourceTypeList;
  300. auto createSourceButton = [this, &prevTabWidget](obs_source_t *source) {
  301. SourceSelectButton *newButton = new SourceSelectButton(source, ui->existingListFrame);
  302. std::string name = obs_source_get_name(source);
  303. existingFlowLayout->addWidget(newButton);
  304. sourceButtons->addButton(newButton->getButton());
  305. if (!prevTabWidget) {
  306. setTabOrder(ui->existingListFrame, newButton->getButton());
  307. } else {
  308. setTabOrder(prevTabWidget, newButton->getButton());
  309. }
  310. prevTabWidget = newButton->getButton();
  311. };
  312. bool isReverseListOrder = sourceTypeId.isNull();
  313. size_t iterationLimit = limit > 0 ? std::min(static_cast<size_t>(limit), matchingSources.size())
  314. : matchingSources.size();
  315. if (isReverseListOrder) {
  316. std::for_each(matchingSources.rbegin(), matchingSources.rbegin() + iterationLimit, createSourceButton);
  317. } else {
  318. std::for_each(matchingSources.begin(), matchingSources.begin() + iterationLimit, createSourceButton);
  319. }
  320. setTabOrder(prevTabWidget, ui->addExistingContainer);
  321. connect(sourceButtons, &QButtonGroup::buttonToggled, this, &OBSBasicSourceSelect::sourceButtonToggled);
  322. ui->existingListFrame->adjustSize();
  323. QTimer::singleShot(10, this, [this] { checkSourceVisibility(); });
  324. }
  325. bool OBSBasicSourceSelect::enumSourcesCallback(void *data, obs_source_t *source)
  326. {
  327. if (obs_source_is_hidden(source)) {
  328. return true;
  329. }
  330. OBSBasicSourceSelect *window = static_cast<OBSBasicSourceSelect *>(data);
  331. window->sources.push_back(source);
  332. return true;
  333. }
  334. bool OBSBasicSourceSelect::enumGroupsCallback(void *data, obs_source_t *source)
  335. {
  336. OBSBasicSourceSelect *window = static_cast<OBSBasicSourceSelect *>(data);
  337. const char *name = obs_source_get_name(source);
  338. const char *id = obs_source_get_unversioned_id(source);
  339. if (window->sourceTypeId.compare(QString(id)) == 0) {
  340. OBSBasic *main = OBSBasic::Get();
  341. OBSScene scene = main->GetCurrentScene();
  342. obs_sceneitem_t *existing = obs_scene_get_group(scene, name);
  343. if (!existing) {
  344. QPushButton *button = new QPushButton(name);
  345. connect(button, &QPushButton::clicked, window, &OBSBasicSourceSelect::addSelectedSources);
  346. }
  347. }
  348. return true;
  349. }
  350. void OBSBasicSourceSelect::OBSSourceAdded(void *data, calldata_t *calldata)
  351. {
  352. OBSBasicSourceSelect *window = static_cast<OBSBasicSourceSelect *>(data);
  353. obs_source_t *source = (obs_source_t *)calldata_ptr(calldata, "source");
  354. QMetaObject::invokeMethod(window, "SourceAdded", Q_ARG(OBSSource, source));
  355. }
  356. void OBSBasicSourceSelect::getSourceTypes()
  357. {
  358. OBSBasic *main = qobject_cast<OBSBasic *>(App()->GetMainWindow());
  359. const char *unversioned_type;
  360. const char *type;
  361. size_t idx = 0;
  362. while (obs_enum_input_types2(idx++, &type, &unversioned_type)) {
  363. const char *name = obs_source_get_display_name(type);
  364. uint32_t caps = obs_get_source_output_flags(type);
  365. if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) {
  366. continue;
  367. }
  368. QListWidgetItem *newItem = new QListWidgetItem(ui->sourceTypeList);
  369. newItem->setData(Qt::DisplayRole, name);
  370. newItem->setData(UNVERSIONED_ID_ROLE, unversioned_type);
  371. if ((caps & OBS_SOURCE_DEPRECATED) != 0) {
  372. newItem->setData(DEPRECATED_ROLE, true);
  373. } else {
  374. newItem->setData(DEPRECATED_ROLE, false);
  375. QIcon icon;
  376. icon = main->GetSourceIcon(type);
  377. newItem->setIcon(icon);
  378. }
  379. }
  380. QListWidgetItem *newItem = new QListWidgetItem(ui->sourceTypeList);
  381. newItem->setData(Qt::DisplayRole, Str("Basic.Scene"));
  382. newItem->setData(UNVERSIONED_ID_ROLE, "scene");
  383. QIcon icon;
  384. icon = main->GetSceneIcon();
  385. newItem->setIcon(icon);
  386. ui->sourceTypeList->sortItems();
  387. // Shift Deprecated sources to the bottom
  388. QList<QListWidgetItem *> deprecatedItems;
  389. for (int i = 0; i < ui->sourceTypeList->count(); i++) {
  390. QListWidgetItem *item = ui->sourceTypeList->item(i);
  391. if (!item) {
  392. break;
  393. }
  394. bool isDeprecated = item->data(DEPRECATED_ROLE).toBool();
  395. if (isDeprecated) {
  396. ui->sourceTypeList->takeItem(i);
  397. deprecatedItems.append(item);
  398. }
  399. }
  400. for (auto &item : deprecatedItems) {
  401. ui->sourceTypeList->addItem(item);
  402. }
  403. QListWidgetItem *allSources = new QListWidgetItem();
  404. allSources->setData(Qt::DisplayRole, Str("Basic.SourceSelect.Recent"));
  405. allSources->setData(UNVERSIONED_ID_ROLE, QVariant());
  406. ui->sourceTypeList->insertItem(0, allSources);
  407. ui->sourceTypeList->setCurrentItem(allSources);
  408. ui->sourceTypeList->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
  409. connect(ui->sourceTypeList, &QListWidget::currentItemChanged, this, &OBSBasicSourceSelect::sourceTypeSelected);
  410. }
  411. void OBSBasicSourceSelect::setSelectedSourceType(QListWidgetItem *item)
  412. {
  413. setSelectedSource(nullptr);
  414. QLayout *layout = ui->existingListFrame->flowLayout();
  415. // Clear existing buttons when switching types
  416. QLayoutItem *child = nullptr;
  417. while ((child = layout->takeAt(0)) != nullptr) {
  418. if (child->widget()) {
  419. child->widget()->deleteLater();
  420. }
  421. delete child;
  422. }
  423. QVariant unversionedIdData = item->data(UNVERSIONED_ID_ROLE);
  424. QVariant deprecatedData = item->data(DEPRECATED_ROLE);
  425. if (unversionedIdData.isNull()) {
  426. setSelectedSource(nullptr);
  427. sourceTypeId.clear();
  428. ui->createNewFrame->setVisible(false);
  429. updateExistingSources(16);
  430. return;
  431. }
  432. QString type = unversionedIdData.toString();
  433. if (type.compare(sourceTypeId) == 0) {
  434. return;
  435. }
  436. ui->createNewFrame->setVisible(true);
  437. bool isDeprecatedType = deprecatedData.toBool();
  438. ui->newSourceName->setVisible(!isDeprecatedType);
  439. ui->createNewSource->setVisible(!isDeprecatedType);
  440. ui->deprecatedCreateLabel->setVisible(isDeprecatedType);
  441. sourceTypeId = type;
  442. QString placeHolderText{getSourceDisplayName(sourceTypeId)};
  443. QString text{placeHolderText};
  444. int i = 2;
  445. OBSSourceAutoRelease source = nullptr;
  446. while ((source = obs_get_source_by_name(QT_TO_UTF8(text)))) {
  447. text = QString("%1 %2").arg(placeHolderText).arg(i++);
  448. }
  449. ui->newSourceName->setText(text);
  450. ui->newSourceName->selectAll();
  451. if (sourceTypeId.compare("scene") == 0) {
  452. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  453. OBSSource curSceneSource = main->GetCurrentSceneSource();
  454. delete sourceButtons;
  455. sourceButtons = new QButtonGroup(this);
  456. int count = main->ui->scenes->count();
  457. QWidget *prevTabItem = ui->sourceTypeList;
  458. for (int i = 0; i < count; i++) {
  459. QListWidgetItem *item = main->ui->scenes->item(i);
  460. OBSScene scene = GetOBSRef<OBSScene>(item);
  461. OBSSource sceneSource = obs_scene_get_source(scene);
  462. if (curSceneSource == sceneSource) {
  463. continue;
  464. }
  465. SourceSelectButton *newButton = new SourceSelectButton(sceneSource, ui->existingListFrame);
  466. existingFlowLayout->addWidget(newButton);
  467. sourceButtons->addButton(newButton->getButton());
  468. setTabOrder(prevTabItem, newButton->getButton());
  469. prevTabItem = newButton->getButton();
  470. }
  471. connect(sourceButtons, &QButtonGroup::buttonToggled, this, &OBSBasicSourceSelect::sourceButtonToggled);
  472. QTimer::singleShot(100, this, [this] { checkSourceVisibility(); });
  473. ui->createNewFrame->setVisible(false);
  474. } else if (sourceTypeId.compare("group") == 0) {
  475. obs_enum_sources(enumGroupsCallback, this);
  476. } else {
  477. updateExistingSources();
  478. }
  479. if (layout->count() == 0) {
  480. QLabel *noExisting = new QLabel();
  481. noExisting->setText(QTStr("Basic.SourceSelect.NoExisting").arg(getSourceDisplayName(sourceTypeId)));
  482. noExisting->setProperty("class", "text-muted");
  483. layout->addWidget(noExisting);
  484. }
  485. }
  486. void OBSBasicSourceSelect::OBSSourceRemoved(void *data, calldata_t *calldata)
  487. {
  488. OBSBasicSourceSelect *window = static_cast<OBSBasicSourceSelect *>(data);
  489. obs_source_t *source = (obs_source_t *)calldata_ptr(calldata, "source");
  490. QMetaObject::invokeMethod(window, "SourceRemoved", Q_ARG(OBSSource, source));
  491. }
  492. void OBSBasicSourceSelect::sourceButtonToggled(QAbstractButton *button, bool checked)
  493. {
  494. SourceSelectButton *buttonParent = dynamic_cast<SourceSelectButton *>(button->parentWidget());
  495. Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
  496. bool ctrlDown = (modifiers & Qt::ControlModifier);
  497. bool shiftDown = (modifiers & Qt::ShiftModifier);
  498. if (!buttonParent) {
  499. clearSelectedItems();
  500. return;
  501. }
  502. int selectedIndex = existingFlowLayout->indexOf(buttonParent);
  503. if (ctrlDown && !shiftDown) {
  504. if (checked) {
  505. addSelectedItem(buttonParent);
  506. } else {
  507. removeSelectedItem(buttonParent);
  508. }
  509. lastSelectedIndex = existingFlowLayout->indexOf(buttonParent);
  510. return;
  511. } else if (shiftDown) {
  512. if (!ctrlDown) {
  513. clearSelectedItems();
  514. }
  515. sourceButtons->blockSignals(true);
  516. int start = std::min(selectedIndex, lastSelectedIndex);
  517. int end = std::max(selectedIndex, lastSelectedIndex);
  518. for (int i = start; i <= end; i++) {
  519. auto item = existingFlowLayout->itemAt(i);
  520. if (!item) {
  521. continue;
  522. }
  523. auto widget = item->widget();
  524. if (!widget) {
  525. continue;
  526. }
  527. auto entry = dynamic_cast<SourceSelectButton *>(widget);
  528. if (entry) {
  529. entry->getButton()->setChecked(true);
  530. addSelectedItem(entry);
  531. }
  532. }
  533. sourceButtons->blockSignals(false);
  534. } else {
  535. lastSelectedIndex = existingFlowLayout->indexOf(buttonParent);
  536. bool reselectItem = selectedItems.size() > 1;
  537. clearSelectedItems();
  538. if (checked) {
  539. addSelectedItem(buttonParent);
  540. } else if (reselectItem) {
  541. button->setChecked(true);
  542. addSelectedItem(buttonParent);
  543. }
  544. }
  545. }
  546. void OBSBasicSourceSelect::sourceDropped(QString uuid)
  547. {
  548. OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.toStdString().c_str());
  549. if (source) {
  550. const char *name = obs_source_get_name(source);
  551. bool visible = ui->sourceVisible->isChecked();
  552. addExistingSource(name, visible);
  553. }
  554. }
  555. void OBSBasicSourceSelect::setSelectedSource(SourceSelectButton *button)
  556. {
  557. clearSelectedItems();
  558. addSelectedItem(button);
  559. }
  560. void OBSBasicSourceSelect::addSelectedItem(SourceSelectButton *button)
  561. {
  562. if (button == nullptr) {
  563. return;
  564. }
  565. auto it = std::find(selectedItems.begin(), selectedItems.end(), button);
  566. if (it == selectedItems.end()) {
  567. selectedItems.push_back(button);
  568. emit selectedItemsChanged();
  569. }
  570. }
  571. void OBSBasicSourceSelect::removeSelectedItem(SourceSelectButton *button)
  572. {
  573. if (button == nullptr) {
  574. return;
  575. }
  576. auto it = std::find(selectedItems.begin(), selectedItems.end(), button);
  577. if (it != selectedItems.end()) {
  578. selectedItems.erase(it);
  579. emit selectedItemsChanged();
  580. }
  581. }
  582. void OBSBasicSourceSelect::clearSelectedItems()
  583. {
  584. if (selectedItems.size() == 0) {
  585. return;
  586. }
  587. sourceButtons->blockSignals(true);
  588. for (auto &item : selectedItems) {
  589. item->getButton()->setChecked(false);
  590. }
  591. sourceButtons->blockSignals(false);
  592. selectedItems.clear();
  593. emit selectedItemsChanged();
  594. }
  595. void OBSBasicSourceSelect::createNewSource()
  596. {
  597. bool visible = ui->sourceVisible->isChecked();
  598. if (ui->newSourceName->text().isEmpty()) {
  599. return;
  600. }
  601. if (sourceTypeId.isNull()) {
  602. return;
  603. }
  604. if (sourceTypeId.compare("scene") == 0) {
  605. return;
  606. }
  607. OBSSceneItem item;
  608. std::string sourceType = sourceTypeId.toStdString();
  609. const char *id = sourceType.c_str();
  610. if (!AddNew(this, id, QT_TO_UTF8(ui->newSourceName->text()), visible, newSource, item)) {
  611. return;
  612. }
  613. if (newSource && strcmp(obs_source_get_id(newSource.Get()), "group") != 0) {
  614. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  615. std::string scene_name = obs_source_get_name(main->GetCurrentSceneSource());
  616. auto undo = [scene_name, main](const std::string &data) {
  617. OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str());
  618. obs_source_remove(source);
  619. OBSSourceAutoRelease scene_source = obs_get_source_by_name(scene_name.c_str());
  620. main->SetCurrentScene(scene_source.Get(), true);
  621. };
  622. OBSDataAutoRelease wrapper = obs_data_create();
  623. obs_data_set_string(wrapper, "id", id);
  624. obs_data_set_int(wrapper, "item_id", obs_sceneitem_get_id(item));
  625. obs_data_set_string(wrapper, "name", ui->newSourceName->text().toUtf8().constData());
  626. obs_data_set_bool(wrapper, "visible", visible);
  627. auto redo = [scene_name, main](const std::string &data) {
  628. OBSSourceAutoRelease scene_source = obs_get_source_by_name(scene_name.c_str());
  629. main->SetCurrentScene(scene_source.Get(), true);
  630. OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str());
  631. OBSSource source;
  632. OBSSceneItem item;
  633. AddNew(NULL, obs_data_get_string(dat, "id"), obs_data_get_string(dat, "name"),
  634. obs_data_get_bool(dat, "visible"), source, item);
  635. obs_sceneitem_set_id(item, (int64_t)obs_data_get_int(dat, "item_id"));
  636. };
  637. undo_s.add_action(QTStr("Undo.Add").arg(ui->newSourceName->text()), undo, redo,
  638. std::string(obs_source_get_name(newSource)), std::string(obs_data_get_json(wrapper)));
  639. main->CreatePropertiesWindow(newSource);
  640. }
  641. close();
  642. }
  643. void OBSBasicSourceSelect::addExistingSource(QString name, bool visible)
  644. {
  645. OBSSourceAutoRelease source = obs_get_source_by_name(name.toStdString().c_str());
  646. if (source) {
  647. AddExisting(source.Get(), visible, false);
  648. OBSBasic *main = OBSBasic::Get();
  649. const char *scene_name = obs_source_get_name(main->GetCurrentSceneSource());
  650. auto undo = [scene_name, main](const std::string &) {
  651. obs_source_t *scene_source = obs_get_source_by_name(scene_name);
  652. main->SetCurrentScene(scene_source, true);
  653. obs_source_release(scene_source);
  654. obs_scene_t *scene = obs_get_scene_by_name(scene_name);
  655. OBSSceneItem item;
  656. auto cb = [](obs_scene_t *, obs_sceneitem_t *sceneitem, void *data) {
  657. OBSSceneItem &last = *static_cast<OBSSceneItem *>(data);
  658. last = sceneitem;
  659. return true;
  660. };
  661. obs_scene_enum_items(scene, cb, &item);
  662. obs_sceneitem_remove(item);
  663. obs_scene_release(scene);
  664. };
  665. auto redo = [scene_name, main, name, visible](const std::string &) {
  666. obs_source_t *scene_source = obs_get_source_by_name(scene_name);
  667. main->SetCurrentScene(scene_source, true);
  668. obs_source_release(scene_source);
  669. AddExisting(QT_TO_UTF8(name), visible, false);
  670. };
  671. undo_s.add_action(QTStr("Undo.Add").arg(name), undo, redo, "", "");
  672. }
  673. }
  674. void OBSBasicSourceSelect::on_createNewSource_clicked(bool)
  675. {
  676. createNewSource();
  677. }
  678. void OBSBasicSourceSelect::addSelectedSources()
  679. {
  680. if (selectedItems.size() == 0) {
  681. return;
  682. }
  683. bool visible = ui->sourceVisible->isChecked();
  684. for (auto &item : selectedItems) {
  685. QString sourceName = item->text();
  686. addExistingSource(sourceName, visible);
  687. }
  688. close();
  689. }
  690. void OBSBasicSourceSelect::sourceTypeSelected(QListWidgetItem *current, QListWidgetItem *)
  691. {
  692. setSelectedSourceType(current);
  693. }
  694. void OBSBasicSourceSelect::SourcePaste(SourceCopyInfo &info, bool dup)
  695. {
  696. OBSSource source = OBSGetStrongRef(info.weak_source);
  697. if (!source) {
  698. return;
  699. }
  700. AddExisting(source, info.visible, dup, &info);
  701. }