| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 | /******************************************************************************    Copyright (C) 2023 by Lain Bailey <[email protected]>    This program is free software: you can redistribute it and/or modify    it under the terms of the GNU General Public License as published by    the Free Software Foundation, either version 2 of the License, or    (at your option) any later version.    This program is distributed in the hope that it will be useful,    but WITHOUT ANY WARRANTY; without even the implied warranty of    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    GNU General Public License for more details.    You should have received a copy of the GNU General Public License    along with this program.  If not, see <http://www.gnu.org/licenses/>.******************************************************************************/#include <QMessageBox>#include "window-basic-main.hpp"#include "window-basic-source-select.hpp"#include "qt-wrappers.hpp"#include "obs-app.hpp"struct AddSourceData {	obs_source_t *source;	bool visible;	obs_transform_info *transform = nullptr;	obs_sceneitem_crop *crop = nullptr;	obs_blending_method *blend_method = nullptr;	obs_blending_type *blend_mode = nullptr;};bool OBSBasicSourceSelect::EnumSources(void *data, obs_source_t *source){	if (obs_source_is_hidden(source))		return true;	OBSBasicSourceSelect *window =		static_cast<OBSBasicSourceSelect *>(data);	const char *name = obs_source_get_name(source);	const char *id = obs_source_get_unversioned_id(source);	if (strcmp(id, window->id) == 0)		window->ui->sourceList->addItem(QT_UTF8(name));	return true;}bool OBSBasicSourceSelect::EnumGroups(void *data, obs_source_t *source){	OBSBasicSourceSelect *window =		static_cast<OBSBasicSourceSelect *>(data);	const char *name = obs_source_get_name(source);	const char *id = obs_source_get_unversioned_id(source);	if (strcmp(id, window->id) == 0) {		OBSBasic *main =			reinterpret_cast<OBSBasic *>(App()->GetMainWindow());		OBSScene scene = main->GetCurrentScene();		obs_sceneitem_t *existing = obs_scene_get_group(scene, name);		if (!existing)			window->ui->sourceList->addItem(QT_UTF8(name));	}	return true;}void OBSBasicSourceSelect::OBSSourceAdded(void *data, calldata_t *calldata){	OBSBasicSourceSelect *window =		static_cast<OBSBasicSourceSelect *>(data);	obs_source_t *source = (obs_source_t *)calldata_ptr(calldata, "source");	QMetaObject::invokeMethod(window, "SourceAdded",				  Q_ARG(OBSSource, source));}void OBSBasicSourceSelect::OBSSourceRemoved(void *data, calldata_t *calldata){	OBSBasicSourceSelect *window =		static_cast<OBSBasicSourceSelect *>(data);	obs_source_t *source = (obs_source_t *)calldata_ptr(calldata, "source");	QMetaObject::invokeMethod(window, "SourceRemoved",				  Q_ARG(OBSSource, source));}void OBSBasicSourceSelect::SourceAdded(OBSSource source){	const char *name = obs_source_get_name(source);	const char *sourceId = obs_source_get_unversioned_id(source);	if (strcmp(sourceId, id) != 0)		return;	ui->sourceList->addItem(name);}void OBSBasicSourceSelect::SourceRemoved(OBSSource source){	const char *name = obs_source_get_name(source);	const char *sourceId = obs_source_get_unversioned_id(source);	if (strcmp(sourceId, id) != 0)		return;	QList<QListWidgetItem *> items =		ui->sourceList->findItems(name, Qt::MatchFixedString);	if (!items.count())		return;	delete items[0];}static void AddSource(void *_data, obs_scene_t *scene){	AddSourceData *data = (AddSourceData *)_data;	obs_sceneitem_t *sceneitem;	sceneitem = obs_scene_add(scene, data->source);	if (data->transform != nullptr)		obs_sceneitem_set_info(sceneitem, data->transform);	if (data->crop != nullptr)		obs_sceneitem_set_crop(sceneitem, data->crop);	if (data->blend_method != nullptr)		obs_sceneitem_set_blending_method(sceneitem,						  *data->blend_method);	if (data->blend_mode != nullptr)		obs_sceneitem_set_blending_mode(sceneitem, *data->blend_mode);	obs_sceneitem_set_visible(sceneitem, data->visible);}char *get_new_source_name(const char *name, const char *format){	struct dstr new_name = {0};	int inc = 0;	dstr_copy(&new_name, name);	for (;;) {		OBSSourceAutoRelease existing_source =			obs_get_source_by_name(new_name.array);		if (!existing_source)			break;		dstr_printf(&new_name, format, name, ++inc + 1);	}	return new_name.array;}static void AddExisting(OBSSource source, bool visible, bool duplicate,			obs_transform_info *transform, obs_sceneitem_crop *crop,			obs_blending_method *blend_method,			obs_blending_type *blend_mode){	OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());	OBSScene scene = main->GetCurrentScene();	if (!scene)		return;	if (duplicate) {		OBSSource from = source;		char *new_name = get_new_source_name(			obs_source_get_name(source), "%s %d");		source = obs_source_duplicate(from, new_name, false);		obs_source_release(source);		bfree(new_name);		if (!source)			return;	}	AddSourceData data;	data.source = source;	data.visible = visible;	data.transform = transform;	data.crop = crop;	data.blend_method = blend_method;	data.blend_mode = blend_mode;	obs_enter_graphics();	obs_scene_atomic_update(scene, AddSource, &data);	obs_leave_graphics();}static void AddExisting(const char *name, bool visible, bool duplicate,			obs_transform_info *transform, obs_sceneitem_crop *crop,			obs_blending_method *blend_method,			obs_blending_type *blend_mode){	OBSSourceAutoRelease source = obs_get_source_by_name(name);	if (source) {		AddExisting(source.Get(), visible, duplicate, transform, crop,			    blend_method, blend_mode);	}}bool AddNew(QWidget *parent, const char *id, const char *name,	    const bool visible, OBSSource &newSource){	OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());	OBSScene scene = main->GetCurrentScene();	bool success = false;	if (!scene)		return false;	OBSSourceAutoRelease source = obs_get_source_by_name(name);	if (source && parent) {		OBSMessageBox::information(parent, QTStr("NameExists.Title"),					   QTStr("NameExists.Text"));	} else {		const char *v_id = obs_get_latest_input_type_id(id);		source = obs_source_create(v_id, name, NULL, nullptr);		if (source) {			AddSourceData data;			data.source = source;			data.visible = visible;			obs_enter_graphics();			obs_scene_atomic_update(scene, AddSource, &data);			obs_leave_graphics();			newSource = source;			/* set monitoring if source monitors by default */			uint32_t flags = obs_source_get_output_flags(source);			if ((flags & OBS_SOURCE_MONITOR_BY_DEFAULT) != 0) {				obs_source_set_monitoring_type(					source,					OBS_MONITORING_TYPE_MONITOR_ONLY);			}			success = true;		}	}	return success;}void OBSBasicSourceSelect::on_buttonBox_accepted(){	bool useExisting = ui->selectExisting->isChecked();	bool visible = ui->sourceVisible->isChecked();	if (useExisting) {		QListWidgetItem *item = ui->sourceList->currentItem();		if (!item)			return;		QString source_name = item->text();		AddExisting(QT_TO_UTF8(source_name), visible, false, nullptr,			    nullptr, nullptr, nullptr);		OBSBasic *main =			reinterpret_cast<OBSBasic *>(App()->GetMainWindow());		const char *scene_name =			obs_source_get_name(main->GetCurrentSceneSource());		auto undo = [scene_name, main](const std::string &) {			obs_source_t *scene_source =				obs_get_source_by_name(scene_name);			main->SetCurrentScene(scene_source, true);			obs_source_release(scene_source);			obs_scene_t *scene = obs_get_scene_by_name(scene_name);			OBSSceneItem item;			auto cb = [](obs_scene_t *, obs_sceneitem_t *sceneitem,				     void *data) {				OBSSceneItem &last =					*reinterpret_cast<OBSSceneItem *>(data);				last = sceneitem;				return true;			};			obs_scene_enum_items(scene, cb, &item);			obs_sceneitem_remove(item);			obs_scene_release(scene);		};		auto redo = [scene_name, main, source_name,			     visible](const std::string &) {			obs_source_t *scene_source =				obs_get_source_by_name(scene_name);			main->SetCurrentScene(scene_source, true);			obs_source_release(scene_source);			AddExisting(QT_TO_UTF8(source_name), visible, false,				    nullptr, nullptr, nullptr, nullptr);		};		undo_s.add_action(QTStr("Undo.Add").arg(source_name), undo,				  redo, "", "");	} else {		if (ui->sourceName->text().isEmpty()) {			OBSMessageBox::warning(this,					       QTStr("NoNameEntered.Title"),					       QTStr("NoNameEntered.Text"));			return;		}		if (!AddNew(this, id, QT_TO_UTF8(ui->sourceName->text()),			    visible, newSource))			return;		OBSBasic *main =			reinterpret_cast<OBSBasic *>(App()->GetMainWindow());		std::string scene_name =			obs_source_get_name(main->GetCurrentSceneSource());		auto undo = [scene_name, main](const std::string &data) {			OBSSourceAutoRelease source =				obs_get_source_by_name(data.c_str());			obs_source_remove(source);			OBSSourceAutoRelease scene_source =				obs_get_source_by_name(scene_name.c_str());			main->SetCurrentScene(scene_source.Get(), true);		};		OBSDataAutoRelease wrapper = obs_data_create();		obs_data_set_string(wrapper, "id", id);		OBSSceneItemAutoRelease item = obs_scene_sceneitem_from_source(			main->GetCurrentScene(), newSource);		obs_data_set_int(wrapper, "item_id",				 obs_sceneitem_get_id(item));		obs_data_set_string(			wrapper, "name",			ui->sourceName->text().toUtf8().constData());		obs_data_set_bool(wrapper, "visible", visible);		auto redo = [scene_name, main](const std::string &data) {			OBSSourceAutoRelease scene_source =				obs_get_source_by_name(scene_name.c_str());			main->SetCurrentScene(scene_source.Get(), true);			OBSDataAutoRelease dat =				obs_data_create_from_json(data.c_str());			OBSSource source;			AddNew(NULL, obs_data_get_string(dat, "id"),			       obs_data_get_string(dat, "name"),			       obs_data_get_bool(dat, "visible"), source);			OBSSceneItemAutoRelease item =				obs_scene_sceneitem_from_source(					main->GetCurrentScene(), source);			obs_sceneitem_set_id(item, (int64_t)obs_data_get_int(							   dat, "item_id"));		};		undo_s.add_action(QTStr("Undo.Add").arg(ui->sourceName->text()),				  undo, redo,				  std::string(obs_source_get_name(newSource)),				  std::string(obs_data_get_json(wrapper)));	}	done(DialogCode::Accepted);}void OBSBasicSourceSelect::on_buttonBox_rejected(){	done(DialogCode::Rejected);}static inline const char *GetSourceDisplayName(const char *id){	if (strcmp(id, "scene") == 0)		return Str("Basic.Scene");	else if (strcmp(id, "group") == 0)		return Str("Group");	const char *v_id = obs_get_latest_input_type_id(id);	return obs_source_get_display_name(v_id);}Q_DECLARE_METATYPE(OBSScene);template<typename T> static inline T GetOBSRef(QListWidgetItem *item){	return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();}OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, const char *id_,					   undo_stack &undo_s)	: QDialog(parent),	  ui(new Ui::OBSBasicSourceSelect),	  id(id_),	  undo_s(undo_s){	setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);	ui->setupUi(this);	ui->sourceList->setAttribute(Qt::WA_MacShowFocusRect, false);	QString placeHolderText{QT_UTF8(GetSourceDisplayName(id))};	QString text{placeHolderText};	int i = 2;	OBSSourceAutoRelease source = nullptr;	while ((source = obs_get_source_by_name(QT_TO_UTF8(text)))) {		text = QString("%1 %2").arg(placeHolderText).arg(i++);	}	ui->sourceName->setText(text);	ui->sourceName->setFocus(); //Fixes deselect of text.	ui->sourceName->selectAll();	installEventFilter(CreateShortcutFilter());	connect(ui->createNew, &QRadioButton::pressed, [&]() {		QPushButton *button =			ui->buttonBox->button(QDialogButtonBox::Ok);		if (!button->isEnabled())			button->setEnabled(true);	});	connect(ui->selectExisting, &QRadioButton::pressed, [&]() {		QPushButton *button =			ui->buttonBox->button(QDialogButtonBox::Ok);		bool enabled = ui->sourceList->selectedItems().size() != 0;		if (button->isEnabled() != enabled)			button->setEnabled(enabled);	});	connect(ui->sourceList, &QListWidget::itemSelectionChanged, [&]() {		QPushButton *button =			ui->buttonBox->button(QDialogButtonBox::Ok);		if (!button->isEnabled())			button->setEnabled(true);	});	if (strcmp(id_, "scene") == 0) {		OBSBasic *main =			reinterpret_cast<OBSBasic *>(App()->GetMainWindow());		OBSSource curSceneSource = main->GetCurrentSceneSource();		ui->selectExisting->setChecked(true);		ui->createNew->setChecked(false);		ui->createNew->setEnabled(false);		ui->sourceName->setEnabled(false);		ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);		int count = main->ui->scenes->count();		for (int i = 0; i < count; i++) {			QListWidgetItem *item = main->ui->scenes->item(i);			OBSScene scene = GetOBSRef<OBSScene>(item);			OBSSource sceneSource = obs_scene_get_source(scene);			if (curSceneSource == sceneSource)				continue;			const char *name = obs_source_get_name(sceneSource);			ui->sourceList->addItem(QT_UTF8(name));		}	} else if (strcmp(id_, "group") == 0) {		obs_enum_sources(EnumGroups, this);	} else {		obs_enum_sources(EnumSources, this);	}}void OBSBasicSourceSelect::SourcePaste(SourceCopyInfo &info, bool dup){	OBSSource source = OBSGetStrongRef(info.weak_source);	if (!source)		return;	AddExisting(source, info.visible, dup, &info.transform, &info.crop,		    &info.blend_method, &info.blend_mode);}
 |