window-basic-filters.cpp 34 KB

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