1 /******************************************************************************
2     Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
3 
4     This program is free software: you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation, either version 2 of the License, or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 ******************************************************************************/
17 
18 #include <QMessageBox>
19 #include "window-basic-main.hpp"
20 #include "window-basic-source-select.hpp"
21 #include "qt-wrappers.hpp"
22 #include "obs-app.hpp"
23 
24 struct AddSourceData {
25 	obs_source_t *source;
26 	bool visible;
27 };
28 
EnumSources(void * data,obs_source_t * source)29 bool OBSBasicSourceSelect::EnumSources(void *data, obs_source_t *source)
30 {
31 	if (obs_source_is_hidden(source))
32 		return false;
33 
34 	OBSBasicSourceSelect *window =
35 		static_cast<OBSBasicSourceSelect *>(data);
36 	const char *name = obs_source_get_name(source);
37 	const char *id = obs_source_get_unversioned_id(source);
38 
39 	if (strcmp(id, window->id) == 0)
40 		window->ui->sourceList->addItem(QT_UTF8(name));
41 
42 	return true;
43 }
44 
EnumGroups(void * data,obs_source_t * source)45 bool OBSBasicSourceSelect::EnumGroups(void *data, obs_source_t *source)
46 {
47 	OBSBasicSourceSelect *window =
48 		static_cast<OBSBasicSourceSelect *>(data);
49 	const char *name = obs_source_get_name(source);
50 	const char *id = obs_source_get_unversioned_id(source);
51 
52 	if (strcmp(id, window->id) == 0) {
53 		OBSBasic *main =
54 			reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
55 		OBSScene scene = main->GetCurrentScene();
56 
57 		obs_sceneitem_t *existing = obs_scene_get_group(scene, name);
58 		if (!existing)
59 			window->ui->sourceList->addItem(QT_UTF8(name));
60 	}
61 
62 	return true;
63 }
64 
OBSSourceAdded(void * data,calldata_t * calldata)65 void OBSBasicSourceSelect::OBSSourceAdded(void *data, calldata_t *calldata)
66 {
67 	OBSBasicSourceSelect *window =
68 		static_cast<OBSBasicSourceSelect *>(data);
69 	obs_source_t *source = (obs_source_t *)calldata_ptr(calldata, "source");
70 
71 	QMetaObject::invokeMethod(window, "SourceAdded",
72 				  Q_ARG(OBSSource, source));
73 }
74 
OBSSourceRemoved(void * data,calldata_t * calldata)75 void OBSBasicSourceSelect::OBSSourceRemoved(void *data, calldata_t *calldata)
76 {
77 	OBSBasicSourceSelect *window =
78 		static_cast<OBSBasicSourceSelect *>(data);
79 	obs_source_t *source = (obs_source_t *)calldata_ptr(calldata, "source");
80 
81 	QMetaObject::invokeMethod(window, "SourceRemoved",
82 				  Q_ARG(OBSSource, source));
83 }
84 
SourceAdded(OBSSource source)85 void OBSBasicSourceSelect::SourceAdded(OBSSource source)
86 {
87 	const char *name = obs_source_get_name(source);
88 	const char *sourceId = obs_source_get_unversioned_id(source);
89 
90 	if (strcmp(sourceId, id) != 0)
91 		return;
92 
93 	ui->sourceList->addItem(name);
94 }
95 
SourceRemoved(OBSSource source)96 void OBSBasicSourceSelect::SourceRemoved(OBSSource source)
97 {
98 	const char *name = obs_source_get_name(source);
99 	const char *sourceId = obs_source_get_unversioned_id(source);
100 
101 	if (strcmp(sourceId, id) != 0)
102 		return;
103 
104 	QList<QListWidgetItem *> items =
105 		ui->sourceList->findItems(name, Qt::MatchFixedString);
106 
107 	if (!items.count())
108 		return;
109 
110 	delete items[0];
111 }
112 
AddSource(void * _data,obs_scene_t * scene)113 static void AddSource(void *_data, obs_scene_t *scene)
114 {
115 	AddSourceData *data = (AddSourceData *)_data;
116 	obs_sceneitem_t *sceneitem;
117 
118 	sceneitem = obs_scene_add(scene, data->source);
119 	obs_sceneitem_set_visible(sceneitem, data->visible);
120 }
121 
get_new_source_name(const char * name)122 static char *get_new_source_name(const char *name)
123 {
124 	struct dstr new_name = {0};
125 	int inc = 0;
126 
127 	dstr_copy(&new_name, name);
128 
129 	for (;;) {
130 		obs_source_t *existing_source =
131 			obs_get_source_by_name(new_name.array);
132 		if (!existing_source)
133 			break;
134 
135 		obs_source_release(existing_source);
136 
137 		dstr_printf(&new_name, "%s %d", name, ++inc + 1);
138 	}
139 
140 	return new_name.array;
141 }
142 
AddExisting(const char * name,bool visible,bool duplicate)143 static void AddExisting(const char *name, bool visible, bool duplicate)
144 {
145 	OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
146 	OBSScene scene = main->GetCurrentScene();
147 	if (!scene)
148 		return;
149 
150 	obs_source_t *source = obs_get_source_by_name(name);
151 	if (source) {
152 		if (duplicate) {
153 			obs_source_t *from = source;
154 			char *new_name = get_new_source_name(name);
155 			source = obs_source_duplicate(from, new_name, false);
156 			bfree(new_name);
157 			obs_source_release(from);
158 
159 			if (!source)
160 				return;
161 		}
162 
163 		AddSourceData data;
164 		data.source = source;
165 		data.visible = visible;
166 
167 		obs_enter_graphics();
168 		obs_scene_atomic_update(scene, AddSource, &data);
169 		obs_leave_graphics();
170 
171 		obs_source_release(source);
172 	}
173 }
174 
AddNew(QWidget * parent,const char * id,const char * name,const bool visible,OBSSource & newSource)175 bool AddNew(QWidget *parent, const char *id, const char *name,
176 	    const bool visible, OBSSource &newSource)
177 {
178 	OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
179 	OBSScene scene = main->GetCurrentScene();
180 	bool success = false;
181 	if (!scene)
182 		return false;
183 
184 	obs_source_t *source = obs_get_source_by_name(name);
185 	if (source && parent) {
186 		OBSMessageBox::information(parent, QTStr("NameExists.Title"),
187 					   QTStr("NameExists.Text"));
188 
189 	} else {
190 		const char *v_id = obs_get_latest_input_type_id(id);
191 		source = obs_source_create(v_id, name, NULL, nullptr);
192 
193 		if (source) {
194 			AddSourceData data;
195 			data.source = source;
196 			data.visible = visible;
197 
198 			obs_enter_graphics();
199 			obs_scene_atomic_update(scene, AddSource, &data);
200 			obs_leave_graphics();
201 
202 			newSource = source;
203 
204 			/* set monitoring if source monitors by default */
205 			uint32_t flags = obs_source_get_output_flags(source);
206 			if ((flags & OBS_SOURCE_MONITOR_BY_DEFAULT) != 0) {
207 				obs_source_set_monitoring_type(
208 					source,
209 					OBS_MONITORING_TYPE_MONITOR_ONLY);
210 			}
211 
212 			success = true;
213 		}
214 	}
215 
216 	obs_source_release(source);
217 	return success;
218 }
219 
on_buttonBox_accepted()220 void OBSBasicSourceSelect::on_buttonBox_accepted()
221 {
222 	bool useExisting = ui->selectExisting->isChecked();
223 	bool visible = ui->sourceVisible->isChecked();
224 
225 	if (useExisting) {
226 		QListWidgetItem *item = ui->sourceList->currentItem();
227 		if (!item)
228 			return;
229 
230 		AddExisting(QT_TO_UTF8(item->text()), visible, false);
231 	} else {
232 		if (ui->sourceName->text().isEmpty()) {
233 			OBSMessageBox::warning(this,
234 					       QTStr("NoNameEntered.Title"),
235 					       QTStr("NoNameEntered.Text"));
236 			return;
237 		}
238 
239 		if (!AddNew(this, id, QT_TO_UTF8(ui->sourceName->text()),
240 			    visible, newSource))
241 			return;
242 
243 		OBSBasic *main =
244 			reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
245 		std::string scene_name =
246 			obs_source_get_name(main->GetCurrentSceneSource());
247 		auto undo = [scene_name, main](const std::string &data) {
248 			obs_source_t *source =
249 				obs_get_source_by_name(data.c_str());
250 			obs_source_release(source);
251 			obs_source_remove(source);
252 
253 			obs_source_t *scene_source =
254 				obs_get_source_by_name(scene_name.c_str());
255 			main->SetCurrentScene(scene_source, true);
256 			obs_source_release(scene_source);
257 
258 			main->RefreshSources(main->GetCurrentScene());
259 		};
260 		obs_data_t *wrapper = obs_data_create();
261 		obs_data_set_string(wrapper, "id", id);
262 		obs_sceneitem_t *item = obs_scene_sceneitem_from_source(
263 			main->GetCurrentScene(), newSource);
264 		obs_data_set_int(wrapper, "item_id",
265 				 obs_sceneitem_get_id(item));
266 		obs_data_set_string(
267 			wrapper, "name",
268 			ui->sourceName->text().toUtf8().constData());
269 		obs_data_set_bool(wrapper, "visible", visible);
270 
271 		auto redo = [scene_name, main](const std::string &data) {
272 			obs_source_t *scene_source =
273 				obs_get_source_by_name(scene_name.c_str());
274 			main->SetCurrentScene(scene_source, true);
275 			obs_source_release(scene_source);
276 
277 			obs_data_t *dat =
278 				obs_data_create_from_json(data.c_str());
279 			OBSSource source;
280 			AddNew(NULL, obs_data_get_string(dat, "id"),
281 			       obs_data_get_string(dat, "name"),
282 			       obs_data_get_bool(dat, "visible"), source);
283 			obs_sceneitem_t *item = obs_scene_sceneitem_from_source(
284 				main->GetCurrentScene(), source);
285 			obs_sceneitem_set_id(item, (int64_t)obs_data_get_int(
286 							   dat, "item_id"));
287 
288 			main->RefreshSources(main->GetCurrentScene());
289 			obs_data_release(dat);
290 			obs_sceneitem_release(item);
291 		};
292 		undo_s.add_action(QTStr("Undo.Add").arg(ui->sourceName->text()),
293 				  undo, redo,
294 				  std::string(obs_source_get_name(newSource)),
295 				  std::string(obs_data_get_json(wrapper)));
296 		obs_data_release(wrapper);
297 		obs_sceneitem_release(item);
298 	}
299 
300 	done(DialogCode::Accepted);
301 }
302 
on_buttonBox_rejected()303 void OBSBasicSourceSelect::on_buttonBox_rejected()
304 {
305 	done(DialogCode::Rejected);
306 }
307 
GetSourceDisplayName(const char * id)308 static inline const char *GetSourceDisplayName(const char *id)
309 {
310 	if (strcmp(id, "scene") == 0)
311 		return Str("Basic.Scene");
312 	const char *v_id = obs_get_latest_input_type_id(id);
313 	return obs_source_get_display_name(v_id);
314 }
315 
316 Q_DECLARE_METATYPE(OBSScene);
317 
GetOBSRef(QListWidgetItem * item)318 template<typename T> static inline T GetOBSRef(QListWidgetItem *item)
319 {
320 	return item->data(static_cast<int>(QtDataRole::OBSRef)).value<T>();
321 }
322 
OBSBasicSourceSelect(OBSBasic * parent,const char * id_,undo_stack & undo_s)323 OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, const char *id_,
324 					   undo_stack &undo_s)
325 	: QDialog(parent),
326 	  ui(new Ui::OBSBasicSourceSelect),
327 	  id(id_),
328 	  undo_s(undo_s)
329 {
330 	setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
331 
332 	ui->setupUi(this);
333 
334 	ui->sourceList->setAttribute(Qt::WA_MacShowFocusRect, false);
335 
336 	QString placeHolderText{QT_UTF8(GetSourceDisplayName(id))};
337 
338 	QString text{placeHolderText};
339 	int i = 2;
340 	obs_source_t *source = nullptr;
341 	while ((source = obs_get_source_by_name(QT_TO_UTF8(text)))) {
342 		obs_source_release(source);
343 		text = QString("%1 %2").arg(placeHolderText).arg(i++);
344 	}
345 
346 	ui->sourceName->setText(text);
347 	ui->sourceName->setFocus(); //Fixes deselect of text.
348 	ui->sourceName->selectAll();
349 
350 	installEventFilter(CreateShortcutFilter());
351 
352 	if (strcmp(id_, "scene") == 0) {
353 		OBSBasic *main =
354 			reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
355 		OBSSource curSceneSource = main->GetCurrentSceneSource();
356 
357 		ui->selectExisting->setChecked(true);
358 		ui->createNew->setChecked(false);
359 		ui->createNew->setEnabled(false);
360 		ui->sourceName->setEnabled(false);
361 
362 		int count = main->ui->scenes->count();
363 		for (int i = 0; i < count; i++) {
364 			QListWidgetItem *item = main->ui->scenes->item(i);
365 			OBSScene scene = GetOBSRef<OBSScene>(item);
366 			OBSSource sceneSource = obs_scene_get_source(scene);
367 
368 			if (curSceneSource == sceneSource)
369 				continue;
370 
371 			const char *name = obs_source_get_name(sceneSource);
372 			ui->sourceList->addItem(QT_UTF8(name));
373 		}
374 	} else if (strcmp(id_, "group") == 0) {
375 		obs_enum_sources(EnumGroups, this);
376 	} else {
377 		obs_enum_sources(EnumSources, this);
378 	}
379 }
380 
SourcePaste(const char * name,bool visible,bool dup)381 void OBSBasicSourceSelect::SourcePaste(const char *name, bool visible, bool dup)
382 {
383 	AddExisting(name, visible, dup);
384 }
385