window-basic-filters.cpp 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235
  1. /******************************************************************************
  2. Copyright (C) 2015 by Hugh Bailey <[email protected]>
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. ******************************************************************************/
  14. #include "properties-view.hpp"
  15. #include "window-namedialog.hpp"
  16. #include "window-basic-main.hpp"
  17. #include "window-basic-filters.hpp"
  18. #include "display-helpers.hpp"
  19. #include "qt-wrappers.hpp"
  20. #include "visibility-item-widget.hpp"
  21. #include "item-widget-helpers.hpp"
  22. #include "obs-app.hpp"
  23. #include "undo-stack-obs.hpp"
  24. #include <QMessageBox>
  25. #include <QCloseEvent>
  26. #include <obs-data.h>
  27. #include <obs.h>
  28. #include <util/base.h>
  29. #include <vector>
  30. #include <string>
  31. #include <QMenu>
  32. #include <QVariant>
  33. using namespace std;
  34. Q_DECLARE_METATYPE(OBSSource);
  35. OBSBasicFilters::OBSBasicFilters(QWidget *parent, OBSSource source_)
  36. : QDialog(parent),
  37. ui(new Ui::OBSBasicFilters),
  38. source(source_),
  39. addSignal(obs_source_get_signal_handler(source), "filter_add",
  40. OBSBasicFilters::OBSSourceFilterAdded, this),
  41. removeSignal(obs_source_get_signal_handler(source), "filter_remove",
  42. OBSBasicFilters::OBSSourceFilterRemoved, this),
  43. reorderSignal(obs_source_get_signal_handler(source),
  44. "reorder_filters", OBSBasicFilters::OBSSourceReordered,
  45. this),
  46. removeSourceSignal(obs_source_get_signal_handler(source), "remove",
  47. OBSBasicFilters::SourceRemoved, this),
  48. renameSourceSignal(obs_source_get_signal_handler(source), "rename",
  49. OBSBasicFilters::SourceRenamed, this),
  50. noPreviewMargin(13)
  51. {
  52. main = reinterpret_cast<OBSBasic *>(parent);
  53. setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
  54. ui->setupUi(this);
  55. UpdateFilters();
  56. ui->asyncFilters->setItemDelegate(
  57. new VisibilityItemDelegate(ui->asyncFilters));
  58. ui->effectFilters->setItemDelegate(
  59. new VisibilityItemDelegate(ui->effectFilters));
  60. const char *name = obs_source_get_name(source);
  61. setWindowTitle(QTStr("Basic.Filters.Title").arg(QT_UTF8(name)));
  62. #ifndef QT_NO_SHORTCUT
  63. ui->actionRemoveFilter->setShortcut(
  64. QApplication::translate("OBSBasicFilters", "Del", nullptr));
  65. #endif // QT_NO_SHORTCUT
  66. addAction(ui->actionRemoveFilter);
  67. addAction(ui->actionMoveUp);
  68. addAction(ui->actionMoveDown);
  69. installEventFilter(CreateShortcutFilter());
  70. connect(ui->asyncFilters->itemDelegate(),
  71. SIGNAL(closeEditor(QWidget *,
  72. QAbstractItemDelegate::EndEditHint)),
  73. this,
  74. SLOT(AsyncFilterNameEdited(
  75. QWidget *, QAbstractItemDelegate::EndEditHint)));
  76. connect(ui->effectFilters->itemDelegate(),
  77. SIGNAL(closeEditor(QWidget *,
  78. QAbstractItemDelegate::EndEditHint)),
  79. this,
  80. SLOT(EffectFilterNameEdited(
  81. QWidget *, QAbstractItemDelegate::EndEditHint)));
  82. QPushButton *close = ui->buttonBox->button(QDialogButtonBox::Close);
  83. connect(close, SIGNAL(clicked()), this, SLOT(close()));
  84. close->setDefault(true);
  85. ui->buttonBox->button(QDialogButtonBox::Reset)
  86. ->setText(QTStr("Defaults"));
  87. connect(ui->buttonBox->button(QDialogButtonBox::Reset),
  88. SIGNAL(clicked()), this, SLOT(ResetFilters()));
  89. uint32_t caps = obs_source_get_output_flags(source);
  90. bool audio = (caps & OBS_SOURCE_AUDIO) != 0;
  91. bool audioOnly = (caps & OBS_SOURCE_VIDEO) == 0;
  92. bool async = (caps & OBS_SOURCE_ASYNC) != 0;
  93. if (!async && !audio) {
  94. ui->asyncWidget->setVisible(false);
  95. ui->separatorLine->setVisible(false);
  96. }
  97. if (audioOnly) {
  98. ui->effectWidget->setVisible(false);
  99. ui->separatorLine->setVisible(false);
  100. UpdateSplitter(false);
  101. }
  102. if (async && !audioOnly && ui->asyncFilters->count() == 0 &&
  103. ui->effectFilters->count() != 0) {
  104. ui->effectFilters->setFocus();
  105. }
  106. if (audioOnly || (audio && !async))
  107. ui->asyncLabel->setText(QTStr("Basic.Filters.AudioFilters"));
  108. if (async && audio && ui->asyncFilters->count() == 0) {
  109. UpdateSplitter(false);
  110. } else if (!audioOnly) {
  111. UpdateSplitter();
  112. }
  113. obs_source_inc_showing(source);
  114. auto addDrawCallback = [this]() {
  115. obs_display_add_draw_callback(ui->preview->GetDisplay(),
  116. OBSBasicFilters::DrawPreview,
  117. this);
  118. };
  119. enum obs_source_type type = obs_source_get_type(source);
  120. bool drawable_type = type == OBS_SOURCE_TYPE_INPUT ||
  121. type == OBS_SOURCE_TYPE_SCENE;
  122. if ((caps & OBS_SOURCE_VIDEO) != 0) {
  123. ui->rightLayout->setContentsMargins(0, 0, 0, 0);
  124. ui->preview->show();
  125. if (drawable_type)
  126. connect(ui->preview, &OBSQTDisplay::DisplayCreated,
  127. addDrawCallback);
  128. } else {
  129. ui->rightLayout->setContentsMargins(0, noPreviewMargin, 0, 0);
  130. ui->preview->hide();
  131. }
  132. QAction *renameAsync = new QAction(ui->asyncWidget);
  133. renameAsync->setShortcutContext(Qt::WidgetWithChildrenShortcut);
  134. connect(renameAsync, SIGNAL(triggered()), this,
  135. SLOT(RenameAsyncFilter()));
  136. ui->asyncWidget->addAction(renameAsync);
  137. QAction *renameEffect = new QAction(ui->effectWidget);
  138. renameEffect->setShortcutContext(Qt::WidgetWithChildrenShortcut);
  139. connect(renameEffect, SIGNAL(triggered()), this,
  140. SLOT(RenameEffectFilter()));
  141. ui->effectWidget->addAction(renameEffect);
  142. #ifdef __APPLE__
  143. renameAsync->setShortcut({Qt::Key_Return});
  144. renameEffect->setShortcut({Qt::Key_Return});
  145. #else
  146. renameAsync->setShortcut({Qt::Key_F2});
  147. renameEffect->setShortcut({Qt::Key_F2});
  148. #endif
  149. }
  150. OBSBasicFilters::~OBSBasicFilters()
  151. {
  152. obs_source_dec_showing(source);
  153. ClearListItems(ui->asyncFilters);
  154. ClearListItems(ui->effectFilters);
  155. }
  156. void OBSBasicFilters::Init()
  157. {
  158. show();
  159. }
  160. inline OBSSource OBSBasicFilters::GetFilter(int row, bool async)
  161. {
  162. if (row == -1)
  163. return OBSSource();
  164. QListWidget *list = async ? ui->asyncFilters : ui->effectFilters;
  165. QListWidgetItem *item = list->item(row);
  166. if (!item)
  167. return OBSSource();
  168. QVariant v = item->data(Qt::UserRole);
  169. return v.value<OBSSource>();
  170. }
  171. void FilterChangeUndoRedo(void *vp, obs_data_t *nd_old_settings,
  172. obs_data_t *new_settings)
  173. {
  174. obs_source_t *source = reinterpret_cast<obs_source_t *>(vp);
  175. obs_source_t *parent = obs_filter_get_parent(source);
  176. const char *source_name = obs_source_get_name(source);
  177. OBSBasic *main = OBSBasic::Get();
  178. OBSDataAutoRelease redo_wrapper = obs_data_create();
  179. obs_data_set_string(redo_wrapper, "name", source_name);
  180. obs_data_set_string(redo_wrapper, "settings",
  181. obs_data_get_json(new_settings));
  182. obs_data_set_string(redo_wrapper, "parent",
  183. obs_source_get_name(parent));
  184. OBSDataAutoRelease filter_settings = obs_source_get_settings(source);
  185. OBSDataAutoRelease undo_wrapper = obs_data_create();
  186. obs_data_set_string(undo_wrapper, "name", source_name);
  187. obs_data_set_string(undo_wrapper, "settings",
  188. obs_data_get_json(nd_old_settings));
  189. obs_data_set_string(undo_wrapper, "parent",
  190. obs_source_get_name(parent));
  191. auto undo_redo = [](const std::string &data) {
  192. OBSDataAutoRelease dat =
  193. obs_data_create_from_json(data.c_str());
  194. OBSSourceAutoRelease parent_source = obs_get_source_by_name(
  195. obs_data_get_string(dat, "parent"));
  196. const char *filter_name = obs_data_get_string(dat, "name");
  197. OBSSourceAutoRelease filter = obs_source_get_filter_by_name(
  198. parent_source, filter_name);
  199. OBSDataAutoRelease new_settings = obs_data_create_from_json(
  200. obs_data_get_string(dat, "settings"));
  201. OBSDataAutoRelease current_settings =
  202. obs_source_get_settings(filter);
  203. obs_data_clear(current_settings);
  204. obs_source_update(filter, new_settings);
  205. obs_source_update_properties(filter);
  206. };
  207. main->undo_s.enable();
  208. std::string name = std::string(obs_source_get_name(source));
  209. std::string undo_data = obs_data_get_json(undo_wrapper);
  210. std::string redo_data = obs_data_get_json(redo_wrapper);
  211. main->undo_s.add_action(QTStr("Undo.Filters").arg(name.c_str()),
  212. undo_redo, undo_redo, undo_data, redo_data);
  213. obs_source_update(source, new_settings);
  214. }
  215. void OBSBasicFilters::UpdatePropertiesView(int row, bool async)
  216. {
  217. if (view) {
  218. updatePropertiesSignal.Disconnect();
  219. ui->propertiesFrame->setVisible(false);
  220. view->hide();
  221. view->deleteLater();
  222. view = nullptr;
  223. }
  224. OBSSource filter = GetFilter(row, async);
  225. if (!filter)
  226. return;
  227. OBSDataAutoRelease settings = obs_source_get_settings(filter);
  228. auto disabled_undo = [](void *vp, obs_data_t *settings) {
  229. OBSBasic *main =
  230. reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  231. main->undo_s.disable();
  232. obs_source_t *source = reinterpret_cast<obs_source_t *>(vp);
  233. obs_source_update(source, settings);
  234. };
  235. view = new OBSPropertiesView(
  236. settings.Get(), filter,
  237. (PropertiesReloadCallback)obs_source_properties,
  238. (PropertiesUpdateCallback)FilterChangeUndoRedo,
  239. (PropertiesVisualUpdateCb)disabled_undo);
  240. updatePropertiesSignal.Connect(obs_source_get_signal_handler(filter),
  241. "update_properties",
  242. OBSBasicFilters::UpdateProperties, this);
  243. view->setMinimumHeight(150);
  244. UpdateSplitter();
  245. ui->propertiesLayout->addWidget(view);
  246. view->show();
  247. }
  248. void OBSBasicFilters::UpdateProperties(void *data, calldata_t *)
  249. {
  250. QMetaObject::invokeMethod(static_cast<OBSBasicFilters *>(data)->view,
  251. "ReloadProperties");
  252. }
  253. void OBSBasicFilters::AddFilter(OBSSource filter, bool focus)
  254. {
  255. uint32_t flags = obs_source_get_output_flags(filter);
  256. bool async = (flags & OBS_SOURCE_ASYNC) != 0;
  257. QListWidget *list = async ? ui->asyncFilters : ui->effectFilters;
  258. QListWidgetItem *item = new QListWidgetItem();
  259. Qt::ItemFlags itemFlags = item->flags();
  260. item->setFlags(itemFlags | Qt::ItemIsEditable);
  261. item->setData(Qt::UserRole, QVariant::fromValue(filter));
  262. list->addItem(item);
  263. if (focus)
  264. list->setCurrentItem(item);
  265. SetupVisibilityItem(list, item, filter);
  266. }
  267. void OBSBasicFilters::RemoveFilter(OBSSource filter)
  268. {
  269. uint32_t flags = obs_source_get_output_flags(filter);
  270. bool async = (flags & OBS_SOURCE_ASYNC) != 0;
  271. QListWidget *list = async ? ui->asyncFilters : ui->effectFilters;
  272. for (int i = 0; i < list->count(); i++) {
  273. QListWidgetItem *item = list->item(i);
  274. QVariant v = item->data(Qt::UserRole);
  275. OBSSource curFilter = v.value<OBSSource>();
  276. if (filter == curFilter) {
  277. DeleteListItem(list, item);
  278. break;
  279. }
  280. }
  281. const char *filterName = obs_source_get_name(filter);
  282. const char *sourceName = obs_source_get_name(source);
  283. if (!sourceName || !filterName)
  284. return;
  285. const char *filterId = obs_source_get_id(filter);
  286. blog(LOG_INFO, "User removed filter '%s' (%s) from source '%s'",
  287. filterName, filterId, sourceName);
  288. main->SaveProject();
  289. }
  290. struct FilterOrderInfo {
  291. int asyncIdx = 0;
  292. int effectIdx = 0;
  293. OBSBasicFilters *window;
  294. inline FilterOrderInfo(OBSBasicFilters *window_) : window(window_) {}
  295. };
  296. void OBSBasicFilters::ReorderFilter(QListWidget *list, obs_source_t *filter,
  297. size_t idx)
  298. {
  299. int count = list->count();
  300. for (int i = 0; i < count; i++) {
  301. QListWidgetItem *listItem = list->item(i);
  302. QVariant v = listItem->data(Qt::UserRole);
  303. OBSSource filterItem = v.value<OBSSource>();
  304. if (filterItem == filter) {
  305. if ((int)idx != i) {
  306. bool sel = (list->currentRow() == i);
  307. listItem = TakeListItem(list, i);
  308. if (listItem) {
  309. list->insertItem((int)idx, listItem);
  310. SetupVisibilityItem(list, listItem,
  311. filterItem);
  312. if (sel)
  313. list->setCurrentRow((int)idx);
  314. }
  315. }
  316. break;
  317. }
  318. }
  319. }
  320. void OBSBasicFilters::ReorderFilters()
  321. {
  322. FilterOrderInfo info(this);
  323. obs_source_enum_filters(
  324. source,
  325. [](obs_source_t *, obs_source_t *filter, void *p) {
  326. FilterOrderInfo *info =
  327. reinterpret_cast<FilterOrderInfo *>(p);
  328. uint32_t flags;
  329. bool async;
  330. flags = obs_source_get_output_flags(filter);
  331. async = (flags & OBS_SOURCE_ASYNC) != 0;
  332. if (async) {
  333. info->window->ReorderFilter(
  334. info->window->ui->asyncFilters, filter,
  335. info->asyncIdx++);
  336. } else {
  337. info->window->ReorderFilter(
  338. info->window->ui->effectFilters, filter,
  339. info->effectIdx++);
  340. }
  341. },
  342. &info);
  343. }
  344. void OBSBasicFilters::UpdateFilters()
  345. {
  346. if (!source)
  347. return;
  348. ClearListItems(ui->effectFilters);
  349. ClearListItems(ui->asyncFilters);
  350. obs_source_enum_filters(
  351. source,
  352. [](obs_source_t *, obs_source_t *filter, void *p) {
  353. OBSBasicFilters *window =
  354. reinterpret_cast<OBSBasicFilters *>(p);
  355. window->AddFilter(filter, false);
  356. },
  357. this);
  358. if (ui->asyncFilters->count() > 0) {
  359. ui->asyncFilters->setCurrentItem(ui->asyncFilters->item(0));
  360. } else if (ui->effectFilters->count() > 0) {
  361. ui->effectFilters->setCurrentItem(ui->effectFilters->item(0));
  362. }
  363. main->SaveProject();
  364. }
  365. void OBSBasicFilters::UpdateSplitter()
  366. {
  367. bool show_splitter_frame =
  368. ui->asyncFilters->count() + ui->effectFilters->count() > 0;
  369. UpdateSplitter(show_splitter_frame);
  370. }
  371. void OBSBasicFilters::UpdateSplitter(bool show_splitter_frame)
  372. {
  373. bool show_splitter_handle = show_splitter_frame;
  374. uint32_t caps = obs_source_get_output_flags(source);
  375. if ((caps & OBS_SOURCE_VIDEO) == 0)
  376. show_splitter_handle = false;
  377. for (int i = 0; i < ui->rightLayout->count(); i++) {
  378. QSplitterHandle *hndl = ui->rightLayout->handle(i);
  379. hndl->setEnabled(show_splitter_handle);
  380. }
  381. ui->propertiesFrame->setVisible(show_splitter_frame);
  382. }
  383. static bool filter_compatible(bool async, uint32_t sourceFlags,
  384. uint32_t filterFlags)
  385. {
  386. bool filterVideo = (filterFlags & OBS_SOURCE_VIDEO) != 0;
  387. bool filterAsync = (filterFlags & OBS_SOURCE_ASYNC) != 0;
  388. bool filterAudio = (filterFlags & OBS_SOURCE_AUDIO) != 0;
  389. bool audio = (sourceFlags & OBS_SOURCE_AUDIO) != 0;
  390. bool audioOnly = (sourceFlags & OBS_SOURCE_VIDEO) == 0;
  391. bool asyncSource = (sourceFlags & OBS_SOURCE_ASYNC) != 0;
  392. if (async && ((audioOnly && filterVideo) || (!audio && !asyncSource) ||
  393. (filterAudio && !audio)))
  394. return false;
  395. return (async && (filterAudio || filterAsync)) ||
  396. (!async && !filterAudio && !filterAsync);
  397. }
  398. QMenu *OBSBasicFilters::CreateAddFilterPopupMenu(bool async)
  399. {
  400. uint32_t sourceFlags = obs_source_get_output_flags(source);
  401. const char *type_str;
  402. bool foundValues = false;
  403. size_t idx = 0;
  404. struct FilterInfo {
  405. string type;
  406. string name;
  407. inline FilterInfo(const char *type_, const char *name_)
  408. : type(type_), name(name_)
  409. {
  410. }
  411. };
  412. vector<FilterInfo> types;
  413. while (obs_enum_filter_types(idx++, &type_str)) {
  414. const char *name = obs_source_get_display_name(type_str);
  415. uint32_t caps = obs_get_source_output_flags(type_str);
  416. if ((caps & OBS_SOURCE_DEPRECATED) != 0)
  417. continue;
  418. if ((caps & OBS_SOURCE_CAP_DISABLED) != 0)
  419. continue;
  420. if ((caps & OBS_SOURCE_CAP_OBSOLETE) != 0)
  421. continue;
  422. auto it = types.begin();
  423. for (; it != types.end(); ++it) {
  424. if (it->name >= name)
  425. break;
  426. }
  427. types.emplace(it, type_str, name);
  428. }
  429. QMenu *popup = new QMenu(QTStr("Add"), this);
  430. for (FilterInfo &type : types) {
  431. uint32_t filterFlags =
  432. obs_get_source_output_flags(type.type.c_str());
  433. if (!filter_compatible(async, sourceFlags, filterFlags))
  434. continue;
  435. QAction *popupItem =
  436. new QAction(QT_UTF8(type.name.c_str()), this);
  437. popupItem->setData(QT_UTF8(type.type.c_str()));
  438. connect(popupItem, SIGNAL(triggered(bool)), this,
  439. SLOT(AddFilterFromAction()));
  440. popup->addAction(popupItem);
  441. foundValues = true;
  442. }
  443. if (!foundValues) {
  444. delete popup;
  445. popup = nullptr;
  446. }
  447. return popup;
  448. }
  449. void OBSBasicFilters::AddNewFilter(const char *id)
  450. {
  451. if (id && *id) {
  452. OBSSourceAutoRelease existing_filter;
  453. string name = obs_source_get_display_name(id);
  454. QString placeholder = QString::fromStdString(name);
  455. QString text{placeholder};
  456. int i = 2;
  457. while ((existing_filter = obs_source_get_filter_by_name(
  458. source, QT_TO_UTF8(text)))) {
  459. text = QString("%1 %2").arg(placeholder).arg(i++);
  460. }
  461. bool success = NameDialog::AskForName(
  462. this, QTStr("Basic.Filters.AddFilter.Title"),
  463. QTStr("Basic.Filters.AddFilter.Text"), name, text);
  464. if (!success)
  465. return;
  466. if (name.empty()) {
  467. OBSMessageBox::warning(this,
  468. QTStr("NoNameEntered.Title"),
  469. QTStr("NoNameEntered.Text"));
  470. AddNewFilter(id);
  471. return;
  472. }
  473. existing_filter =
  474. obs_source_get_filter_by_name(source, name.c_str());
  475. if (existing_filter) {
  476. OBSMessageBox::warning(this, QTStr("NameExists.Title"),
  477. QTStr("NameExists.Text"));
  478. AddNewFilter(id);
  479. return;
  480. }
  481. OBSDataAutoRelease wrapper = obs_data_create();
  482. obs_data_set_string(wrapper, "sname",
  483. obs_source_get_name(source));
  484. obs_data_set_string(wrapper, "fname", name.c_str());
  485. std::string scene_name = obs_source_get_name(
  486. reinterpret_cast<OBSBasic *>(App()->GetMainWindow())
  487. ->GetCurrentSceneSource());
  488. auto undo = [scene_name](const std::string &data) {
  489. obs_source_t *ssource =
  490. obs_get_source_by_name(scene_name.c_str());
  491. reinterpret_cast<OBSBasic *>(App()->GetMainWindow())
  492. ->SetCurrentScene(ssource, true);
  493. obs_source_release(ssource);
  494. obs_data_t *dat =
  495. obs_data_create_from_json(data.c_str());
  496. obs_source_t *source = obs_get_source_by_name(
  497. obs_data_get_string(dat, "sname"));
  498. obs_source_t *filter = obs_source_get_filter_by_name(
  499. source, obs_data_get_string(dat, "fname"));
  500. obs_source_filter_remove(source, filter);
  501. obs_data_release(dat);
  502. obs_source_release(source);
  503. obs_source_release(filter);
  504. };
  505. OBSDataAutoRelease rwrapper = obs_data_create();
  506. obs_data_set_string(rwrapper, "sname",
  507. obs_source_get_name(source));
  508. auto redo = [scene_name, id = std::string(id),
  509. name](const std::string &data) {
  510. OBSSourceAutoRelease ssource =
  511. obs_get_source_by_name(scene_name.c_str());
  512. reinterpret_cast<OBSBasic *>(App()->GetMainWindow())
  513. ->SetCurrentScene(ssource.Get(), true);
  514. OBSDataAutoRelease dat =
  515. obs_data_create_from_json(data.c_str());
  516. OBSSourceAutoRelease source = obs_get_source_by_name(
  517. obs_data_get_string(dat, "sname"));
  518. OBSSourceAutoRelease filter = obs_source_create(
  519. id.c_str(), name.c_str(), nullptr, nullptr);
  520. if (filter) {
  521. obs_source_filter_add(source, filter);
  522. }
  523. };
  524. std::string undo_data(obs_data_get_json(wrapper));
  525. std::string redo_data(obs_data_get_json(rwrapper));
  526. main->undo_s.add_action(QTStr("Undo.Add").arg(name.c_str()),
  527. undo, redo, undo_data, redo_data);
  528. OBSSourceAutoRelease filter =
  529. obs_source_create(id, name.c_str(), nullptr, nullptr);
  530. if (filter) {
  531. const char *sourceName = obs_source_get_name(source);
  532. blog(LOG_INFO,
  533. "User added filter '%s' (%s) "
  534. "to source '%s'",
  535. name.c_str(), id, sourceName);
  536. obs_source_filter_add(source, filter);
  537. }
  538. }
  539. }
  540. void OBSBasicFilters::AddFilterFromAction()
  541. {
  542. QAction *action = qobject_cast<QAction *>(sender());
  543. if (!action)
  544. return;
  545. AddNewFilter(QT_TO_UTF8(action->data().toString()));
  546. }
  547. void OBSBasicFilters::closeEvent(QCloseEvent *event)
  548. {
  549. QDialog::closeEvent(event);
  550. if (!event->isAccepted())
  551. return;
  552. obs_display_remove_draw_callback(ui->preview->GetDisplay(),
  553. OBSBasicFilters::DrawPreview, this);
  554. main->SaveProject();
  555. }
  556. /* OBS Signals */
  557. void OBSBasicFilters::OBSSourceFilterAdded(void *param, calldata_t *data)
  558. {
  559. OBSBasicFilters *window = reinterpret_cast<OBSBasicFilters *>(param);
  560. obs_source_t *filter = (obs_source_t *)calldata_ptr(data, "filter");
  561. QMetaObject::invokeMethod(window, "AddFilter",
  562. Q_ARG(OBSSource, OBSSource(filter)));
  563. }
  564. void OBSBasicFilters::OBSSourceFilterRemoved(void *param, calldata_t *data)
  565. {
  566. OBSBasicFilters *window = reinterpret_cast<OBSBasicFilters *>(param);
  567. obs_source_t *filter = (obs_source_t *)calldata_ptr(data, "filter");
  568. QMetaObject::invokeMethod(window, "RemoveFilter",
  569. Q_ARG(OBSSource, OBSSource(filter)));
  570. }
  571. void OBSBasicFilters::OBSSourceReordered(void *param, calldata_t *data)
  572. {
  573. QMetaObject::invokeMethod(reinterpret_cast<OBSBasicFilters *>(param),
  574. "ReorderFilters");
  575. UNUSED_PARAMETER(data);
  576. }
  577. void OBSBasicFilters::SourceRemoved(void *param, calldata_t *data)
  578. {
  579. UNUSED_PARAMETER(data);
  580. QMetaObject::invokeMethod(static_cast<OBSBasicFilters *>(param),
  581. "close");
  582. }
  583. void OBSBasicFilters::SourceRenamed(void *param, calldata_t *data)
  584. {
  585. const char *name = calldata_string(data, "new_name");
  586. QString title = QTStr("Basic.Filters.Title").arg(QT_UTF8(name));
  587. QMetaObject::invokeMethod(static_cast<OBSBasicFilters *>(param),
  588. "setWindowTitle", Q_ARG(QString, title));
  589. }
  590. void OBSBasicFilters::DrawPreview(void *data, uint32_t cx, uint32_t cy)
  591. {
  592. OBSBasicFilters *window = static_cast<OBSBasicFilters *>(data);
  593. if (!window->source)
  594. return;
  595. uint32_t sourceCX = max(obs_source_get_width(window->source), 1u);
  596. uint32_t sourceCY = max(obs_source_get_height(window->source), 1u);
  597. int x, y;
  598. int newCX, newCY;
  599. float scale;
  600. GetScaleAndCenterPos(sourceCX, sourceCY, cx, cy, x, y, scale);
  601. newCX = int(scale * float(sourceCX));
  602. newCY = int(scale * float(sourceCY));
  603. gs_viewport_push();
  604. gs_projection_push();
  605. const bool previous = gs_set_linear_srgb(true);
  606. gs_ortho(0.0f, float(sourceCX), 0.0f, float(sourceCY), -100.0f, 100.0f);
  607. gs_set_viewport(x, y, newCX, newCY);
  608. obs_source_video_render(window->source);
  609. gs_set_linear_srgb(previous);
  610. gs_projection_pop();
  611. gs_viewport_pop();
  612. }
  613. /* Qt Slots */
  614. static bool QueryRemove(QWidget *parent, obs_source_t *source)
  615. {
  616. const char *name = obs_source_get_name(source);
  617. QString text = QTStr("ConfirmRemove.Text");
  618. text.replace("$1", QT_UTF8(name));
  619. QMessageBox remove_source(parent);
  620. remove_source.setText(text);
  621. QAbstractButton *Yes =
  622. remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole);
  623. remove_source.addButton(QTStr("No"), QMessageBox::NoRole);
  624. remove_source.setIcon(QMessageBox::Question);
  625. remove_source.setWindowTitle(QTStr("ConfirmRemove.Title"));
  626. remove_source.exec();
  627. return Yes == remove_source.clickedButton();
  628. }
  629. void OBSBasicFilters::on_addAsyncFilter_clicked()
  630. {
  631. ui->asyncFilters->setFocus();
  632. QScopedPointer<QMenu> popup(CreateAddFilterPopupMenu(true));
  633. if (popup)
  634. popup->exec(QCursor::pos());
  635. }
  636. void OBSBasicFilters::on_removeAsyncFilter_clicked()
  637. {
  638. OBSSource filter = GetFilter(ui->asyncFilters->currentRow(), true);
  639. if (filter) {
  640. if (QueryRemove(this, filter))
  641. delete_filter(filter);
  642. }
  643. }
  644. void OBSBasicFilters::on_moveAsyncFilterUp_clicked()
  645. {
  646. OBSSource filter = GetFilter(ui->asyncFilters->currentRow(), true);
  647. if (filter)
  648. obs_source_filter_set_order(source, filter, OBS_ORDER_MOVE_UP);
  649. }
  650. void OBSBasicFilters::on_moveAsyncFilterDown_clicked()
  651. {
  652. OBSSource filter = GetFilter(ui->asyncFilters->currentRow(), true);
  653. if (filter)
  654. obs_source_filter_set_order(source, filter,
  655. OBS_ORDER_MOVE_DOWN);
  656. }
  657. void OBSBasicFilters::on_asyncFilters_GotFocus()
  658. {
  659. UpdatePropertiesView(ui->asyncFilters->currentRow(), true);
  660. isAsync = true;
  661. }
  662. void OBSBasicFilters::on_asyncFilters_currentRowChanged(int row)
  663. {
  664. UpdatePropertiesView(row, true);
  665. }
  666. void OBSBasicFilters::on_addEffectFilter_clicked()
  667. {
  668. ui->effectFilters->setFocus();
  669. QScopedPointer<QMenu> popup(CreateAddFilterPopupMenu(false));
  670. if (popup)
  671. popup->exec(QCursor::pos());
  672. }
  673. void OBSBasicFilters::on_removeEffectFilter_clicked()
  674. {
  675. OBSSource filter = GetFilter(ui->effectFilters->currentRow(), false);
  676. if (filter) {
  677. if (QueryRemove(this, filter)) {
  678. delete_filter(filter);
  679. }
  680. }
  681. }
  682. void OBSBasicFilters::on_moveEffectFilterUp_clicked()
  683. {
  684. OBSSource filter = GetFilter(ui->effectFilters->currentRow(), false);
  685. if (filter)
  686. obs_source_filter_set_order(source, filter, OBS_ORDER_MOVE_UP);
  687. }
  688. void OBSBasicFilters::on_moveEffectFilterDown_clicked()
  689. {
  690. OBSSource filter = GetFilter(ui->effectFilters->currentRow(), false);
  691. if (filter)
  692. obs_source_filter_set_order(source, filter,
  693. OBS_ORDER_MOVE_DOWN);
  694. }
  695. void OBSBasicFilters::on_effectFilters_GotFocus()
  696. {
  697. UpdatePropertiesView(ui->effectFilters->currentRow(), false);
  698. isAsync = false;
  699. }
  700. void OBSBasicFilters::on_effectFilters_currentRowChanged(int row)
  701. {
  702. UpdatePropertiesView(row, false);
  703. }
  704. void OBSBasicFilters::on_actionRemoveFilter_triggered()
  705. {
  706. if (ui->asyncFilters->hasFocus())
  707. on_removeAsyncFilter_clicked();
  708. else if (ui->effectFilters->hasFocus())
  709. on_removeEffectFilter_clicked();
  710. }
  711. void OBSBasicFilters::on_actionMoveUp_triggered()
  712. {
  713. if (ui->asyncFilters->hasFocus())
  714. on_moveAsyncFilterUp_clicked();
  715. else if (ui->effectFilters->hasFocus())
  716. on_moveEffectFilterUp_clicked();
  717. }
  718. void OBSBasicFilters::on_actionMoveDown_triggered()
  719. {
  720. if (ui->asyncFilters->hasFocus())
  721. on_moveAsyncFilterDown_clicked();
  722. else if (ui->effectFilters->hasFocus())
  723. on_moveEffectFilterDown_clicked();
  724. }
  725. void OBSBasicFilters::CustomContextMenu(const QPoint &pos, bool async)
  726. {
  727. QListWidget *list = async ? ui->asyncFilters : ui->effectFilters;
  728. QListWidgetItem *item = list->itemAt(pos);
  729. QMenu popup(window());
  730. QPointer<QMenu> addMenu = CreateAddFilterPopupMenu(async);
  731. if (addMenu)
  732. popup.addMenu(addMenu);
  733. if (item) {
  734. const char *dulpicateSlot =
  735. async ? SLOT(DuplicateAsyncFilter())
  736. : SLOT(DuplicateEffectFilter());
  737. const char *renameSlot = async ? SLOT(RenameAsyncFilter())
  738. : SLOT(RenameEffectFilter());
  739. const char *removeSlot =
  740. async ? SLOT(on_removeAsyncFilter_clicked())
  741. : SLOT(on_removeEffectFilter_clicked());
  742. popup.addSeparator();
  743. popup.addAction(QTStr("Duplicate"), this, dulpicateSlot);
  744. popup.addSeparator();
  745. popup.addAction(QTStr("Rename"), this, renameSlot);
  746. popup.addAction(QTStr("Remove"), this, removeSlot);
  747. popup.addSeparator();
  748. QAction *copyAction = new QAction(QTStr("Copy"));
  749. connect(copyAction, SIGNAL(triggered()), this,
  750. SLOT(CopyFilter()));
  751. copyAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_C));
  752. ui->effectWidget->addAction(copyAction);
  753. ui->asyncWidget->addAction(copyAction);
  754. popup.addAction(copyAction);
  755. }
  756. QAction *pasteAction = new QAction(QTStr("Paste"));
  757. pasteAction->setEnabled(main->copyFilter);
  758. connect(pasteAction, SIGNAL(triggered()), this, SLOT(PasteFilter()));
  759. pasteAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_V));
  760. ui->effectWidget->addAction(pasteAction);
  761. ui->asyncWidget->addAction(pasteAction);
  762. popup.addAction(pasteAction);
  763. popup.exec(QCursor::pos());
  764. }
  765. void OBSBasicFilters::EditItem(QListWidgetItem *item, bool async)
  766. {
  767. if (editActive)
  768. return;
  769. Qt::ItemFlags flags = item->flags();
  770. OBSSource filter = item->data(Qt::UserRole).value<OBSSource>();
  771. const char *name = obs_source_get_name(filter);
  772. QListWidget *list = async ? ui->asyncFilters : ui->effectFilters;
  773. item->setText(QT_UTF8(name));
  774. item->setFlags(flags | Qt::ItemIsEditable);
  775. list->removeItemWidget(item);
  776. list->editItem(item);
  777. item->setFlags(flags);
  778. editActive = true;
  779. }
  780. void OBSBasicFilters::DuplicateItem(QListWidgetItem *item)
  781. {
  782. OBSSource filter = item->data(Qt::UserRole).value<OBSSource>();
  783. string name = obs_source_get_name(filter);
  784. OBSSourceAutoRelease existing_filter;
  785. QString placeholder = QString::fromStdString(name);
  786. QString text{placeholder};
  787. int i = 2;
  788. while ((existing_filter = obs_source_get_filter_by_name(
  789. source, QT_TO_UTF8(text)))) {
  790. text = QString("%1 %2").arg(placeholder).arg(i++);
  791. }
  792. bool success = NameDialog::AskForName(
  793. this, QTStr("Basic.Filters.AddFilter.Title"),
  794. QTStr("Basic.Filters.AddFilter.Text"), name, text);
  795. if (!success)
  796. return;
  797. if (name.empty()) {
  798. OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"),
  799. QTStr("NoNameEntered.Text"));
  800. DuplicateItem(item);
  801. return;
  802. }
  803. existing_filter = obs_source_get_filter_by_name(source, name.c_str());
  804. if (existing_filter) {
  805. OBSMessageBox::warning(this, QTStr("NameExists.Title"),
  806. QTStr("NameExists.Text"));
  807. DuplicateItem(item);
  808. return;
  809. }
  810. bool enabled = obs_source_enabled(filter);
  811. OBSSourceAutoRelease new_filter =
  812. obs_source_duplicate(filter, name.c_str(), false);
  813. if (new_filter) {
  814. const char *sourceName = obs_source_get_name(source);
  815. const char *id = obs_source_get_id(new_filter);
  816. blog(LOG_INFO,
  817. "User duplicated filter '%s' (%s) from '%s' "
  818. "to source '%s'",
  819. name.c_str(), id, name.c_str(), sourceName);
  820. obs_source_set_enabled(new_filter, enabled);
  821. obs_source_filter_add(source, new_filter);
  822. }
  823. }
  824. void OBSBasicFilters::on_asyncFilters_customContextMenuRequested(
  825. const QPoint &pos)
  826. {
  827. CustomContextMenu(pos, true);
  828. }
  829. void OBSBasicFilters::on_effectFilters_customContextMenuRequested(
  830. const QPoint &pos)
  831. {
  832. CustomContextMenu(pos, false);
  833. }
  834. void OBSBasicFilters::RenameAsyncFilter()
  835. {
  836. EditItem(ui->asyncFilters->currentItem(), true);
  837. }
  838. void OBSBasicFilters::RenameEffectFilter()
  839. {
  840. EditItem(ui->effectFilters->currentItem(), false);
  841. }
  842. void OBSBasicFilters::DuplicateAsyncFilter()
  843. {
  844. DuplicateItem(ui->asyncFilters->currentItem());
  845. }
  846. void OBSBasicFilters::DuplicateEffectFilter()
  847. {
  848. DuplicateItem(ui->effectFilters->currentItem());
  849. }
  850. void OBSBasicFilters::FilterNameEdited(QWidget *editor, QListWidget *list)
  851. {
  852. QListWidgetItem *listItem = list->currentItem();
  853. OBSSource filter = listItem->data(Qt::UserRole).value<OBSSource>();
  854. QLineEdit *edit = qobject_cast<QLineEdit *>(editor);
  855. string name = QT_TO_UTF8(edit->text().trimmed());
  856. const char *prevName = obs_source_get_name(filter);
  857. bool sameName = (name == prevName);
  858. OBSSourceAutoRelease foundFilter = nullptr;
  859. if (!sameName)
  860. foundFilter =
  861. obs_source_get_filter_by_name(source, name.c_str());
  862. if (foundFilter || name.empty() || sameName) {
  863. listItem->setText(QT_UTF8(prevName));
  864. if (foundFilter) {
  865. OBSMessageBox::information(window(),
  866. QTStr("NameExists.Title"),
  867. QTStr("NameExists.Text"));
  868. } else if (name.empty()) {
  869. OBSMessageBox::information(window(),
  870. QTStr("NoNameEntered.Title"),
  871. QTStr("NoNameEntered.Text"));
  872. }
  873. } else {
  874. const char *sourceName = obs_source_get_name(source);
  875. blog(LOG_INFO,
  876. "User renamed filter '%s' on source '%s' to '%s'",
  877. prevName, sourceName, name.c_str());
  878. listItem->setText(QT_UTF8(name.c_str()));
  879. obs_source_set_name(filter, name.c_str());
  880. std::string scene_name = obs_source_get_name(
  881. reinterpret_cast<OBSBasic *>(App()->GetMainWindow())
  882. ->GetCurrentSceneSource());
  883. auto undo = [scene_name, prev = std::string(prevName),
  884. name](const std::string &data) {
  885. OBSSourceAutoRelease ssource =
  886. obs_get_source_by_name(scene_name.c_str());
  887. reinterpret_cast<OBSBasic *>(App()->GetMainWindow())
  888. ->SetCurrentScene(ssource.Get(), true);
  889. OBSSourceAutoRelease source =
  890. obs_get_source_by_name(data.c_str());
  891. OBSSourceAutoRelease filter =
  892. obs_source_get_filter_by_name(source,
  893. name.c_str());
  894. obs_source_set_name(filter, prev.c_str());
  895. };
  896. auto redo = [scene_name, prev = std::string(prevName),
  897. name](const std::string &data) {
  898. OBSSourceAutoRelease ssource =
  899. obs_get_source_by_name(scene_name.c_str());
  900. reinterpret_cast<OBSBasic *>(App()->GetMainWindow())
  901. ->SetCurrentScene(ssource.Get(), true);
  902. OBSSourceAutoRelease source =
  903. obs_get_source_by_name(data.c_str());
  904. OBSSourceAutoRelease filter =
  905. obs_source_get_filter_by_name(source,
  906. prev.c_str());
  907. obs_source_set_name(filter, name.c_str());
  908. };
  909. std::string undo_data(sourceName);
  910. std::string redo_data(sourceName);
  911. main->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()),
  912. undo, redo, undo_data, redo_data);
  913. }
  914. listItem->setText(QString());
  915. SetupVisibilityItem(list, listItem, filter);
  916. editActive = false;
  917. }
  918. void OBSBasicFilters::AsyncFilterNameEdited(
  919. QWidget *editor, QAbstractItemDelegate::EndEditHint endHint)
  920. {
  921. FilterNameEdited(editor, ui->asyncFilters);
  922. UNUSED_PARAMETER(endHint);
  923. }
  924. void OBSBasicFilters::EffectFilterNameEdited(
  925. QWidget *editor, QAbstractItemDelegate::EndEditHint endHint)
  926. {
  927. FilterNameEdited(editor, ui->effectFilters);
  928. UNUSED_PARAMETER(endHint);
  929. }
  930. void OBSBasicFilters::ResetFilters()
  931. {
  932. QListWidget *list = isAsync ? ui->asyncFilters : ui->effectFilters;
  933. int row = list->currentRow();
  934. OBSSource filter = GetFilter(row, isAsync);
  935. if (!filter)
  936. return;
  937. OBSDataAutoRelease settings = obs_source_get_settings(filter);
  938. OBSDataAutoRelease empty_settings = obs_data_create();
  939. FilterChangeUndoRedo((void *)filter, settings, empty_settings);
  940. obs_data_clear(settings);
  941. if (!view->DeferUpdate())
  942. obs_source_update(filter, nullptr);
  943. view->RefreshProperties();
  944. }
  945. void OBSBasicFilters::CopyFilter()
  946. {
  947. OBSSource filter = nullptr;
  948. if (isAsync)
  949. filter = GetFilter(ui->asyncFilters->currentRow(), true);
  950. else
  951. filter = GetFilter(ui->effectFilters->currentRow(), false);
  952. main->copyFilter = OBSGetWeakRef(filter);
  953. }
  954. void OBSBasicFilters::PasteFilter()
  955. {
  956. OBSSource filter = OBSGetStrongRef(main->copyFilter);
  957. if (!filter)
  958. return;
  959. OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(source);
  960. obs_source_copy_single_filter(source, filter);
  961. OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(source);
  962. const char *filterName = obs_source_get_name(filter);
  963. const char *sourceName = obs_source_get_name(source);
  964. QString text =
  965. QTStr("Undo.Filters.Paste.Single").arg(filterName, sourceName);
  966. main->CreateFilterPasteUndoRedoAction(text, source, undo_array,
  967. redo_array);
  968. }
  969. void OBSBasicFilters::delete_filter(OBSSource filter)
  970. {
  971. OBSDataAutoRelease wrapper = obs_save_source(filter);
  972. std::string parent_name(obs_source_get_name(source));
  973. obs_data_set_string(wrapper, "undo_name", parent_name.c_str());
  974. std::string scene_name = obs_source_get_name(
  975. reinterpret_cast<OBSBasic *>(App()->GetMainWindow())
  976. ->GetCurrentSceneSource());
  977. auto undo = [scene_name](const std::string &data) {
  978. OBSSourceAutoRelease ssource =
  979. obs_get_source_by_name(scene_name.c_str());
  980. reinterpret_cast<OBSBasic *>(App()->GetMainWindow())
  981. ->SetCurrentScene(ssource.Get(), true);
  982. OBSDataAutoRelease dat =
  983. obs_data_create_from_json(data.c_str());
  984. OBSSourceAutoRelease source = obs_get_source_by_name(
  985. obs_data_get_string(dat, "undo_name"));
  986. OBSSourceAutoRelease filter = obs_load_source(dat);
  987. obs_source_filter_add(source, filter);
  988. };
  989. OBSDataAutoRelease rwrapper = obs_data_create();
  990. obs_data_set_string(rwrapper, "fname", obs_source_get_name(filter));
  991. obs_data_set_string(rwrapper, "sname", parent_name.c_str());
  992. auto redo = [scene_name](const std::string &data) {
  993. OBSSourceAutoRelease ssource =
  994. obs_get_source_by_name(scene_name.c_str());
  995. reinterpret_cast<OBSBasic *>(App()->GetMainWindow())
  996. ->SetCurrentScene(ssource.Get(), true);
  997. OBSDataAutoRelease dat =
  998. obs_data_create_from_json(data.c_str());
  999. OBSSourceAutoRelease source = obs_get_source_by_name(
  1000. obs_data_get_string(dat, "sname"));
  1001. OBSSourceAutoRelease filter = obs_source_get_filter_by_name(
  1002. source, obs_data_get_string(dat, "fname"));
  1003. obs_source_filter_remove(source, filter);
  1004. };
  1005. std::string undo_data(obs_data_get_json(wrapper));
  1006. std::string redo_data(obs_data_get_json(rwrapper));
  1007. main->undo_s.add_action(
  1008. QTStr("Undo.Delete").arg(obs_source_get_name(filter)), undo,
  1009. redo, undo_data, redo_data, false);
  1010. obs_source_filter_remove(source, filter);
  1011. }