Browse Source

UI: Add configurable transitions

jp9000 9 years ago
parent
commit
3b1775d97f

+ 7 - 0
obs/data/locale/en-US.ini

@@ -59,10 +59,17 @@ QuickTransitions.EditPropertiesTT="When editing the same scene, allows editing p
 QuickTransitions.HotkeyName="Quick Transition: %1"
 
 # transitions
+Basic.AddTransition="Add Configurable Transition"
+Basic.RemoveTransition="Remove Configurable Transition"
+Basic.TransitionProperties="Transition Properties"
 Basic.SceneTransitions="Scene Transitions"
 Basic.TransitionDuration="Duration"
 Basic.TogglePreviewProgramMode="Studio Mode"
 
+# transition name dialog
+TransitionNameDlg.Text="Please enter the name of the transition"
+TransitionNameDlg.Title="Transition Name"
+
 # title bar strings
 TitleBar.Profile="Profile"
 TitleBar.Scenes="Scenes"

+ 98 - 9
obs/forms/OBSBasic.ui

@@ -581,12 +581,35 @@
            <number>4</number>
           </property>
           <item>
-           <layout class="QHBoxLayout" name="horizontalLayout_2">
+           <widget class="QComboBox" name="transitions">
+            <property name="minimumSize">
+             <size>
+              <width>120</width>
+              <height>0</height>
+             </size>
+            </property>
+           </widget>
+          </item>
+          <item>
+           <layout class="QHBoxLayout" name="horizontalLayout_4">
             <property name="spacing">
-             <number>2</number>
+             <number>4</number>
             </property>
             <item>
-             <widget class="QPushButton" name="transitionProps">
+             <spacer name="horizontalSpacer">
+              <property name="orientation">
+               <enum>Qt::Horizontal</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>40</width>
+                <height>20</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+            <item>
+             <widget class="QPushButton" name="transitionAdd">
               <property name="sizePolicy">
                <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
                 <horstretch>0</horstretch>
@@ -599,29 +622,95 @@
                 <height>22</height>
                </size>
               </property>
+              <property name="toolTip">
+               <string>Basic.AddTransition</string>
+              </property>
+              <property name="accessibleName">
+               <string>Basic.AddTransition</string>
+              </property>
               <property name="text">
                <string notr="true"/>
               </property>
               <property name="icon">
                <iconset resource="obs.qrc">
-                <normaloff>:/res/images/configuration21_16.png</normaloff>:/res/images/configuration21_16.png</iconset>
+                <normaloff>:/res/images/add.png</normaloff>:/res/images/add.png</iconset>
               </property>
               <property name="flat">
                <bool>true</bool>
               </property>
               <property name="themeID" stdset="0">
-               <string notr="true">configIconSmall</string>
+               <string notr="true">addIconSmall</string>
               </property>
              </widget>
             </item>
             <item>
-             <widget class="QComboBox" name="transitions">
-              <property name="minimumSize">
+             <widget class="QPushButton" name="transitionRemove">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="maximumSize">
+               <size>
+                <width>22</width>
+                <height>22</height>
+               </size>
+              </property>
+              <property name="toolTip">
+               <string>Basic.RemoveTransition</string>
+              </property>
+              <property name="accessibleName">
+               <string>Basic.RemoveTransition</string>
+              </property>
+              <property name="text">
+               <string notr="true"/>
+              </property>
+              <property name="icon">
+               <iconset resource="obs.qrc">
+                <normaloff>:/res/images/list_remove.png</normaloff>:/res/images/list_remove.png</iconset>
+              </property>
+              <property name="flat">
+               <bool>true</bool>
+              </property>
+              <property name="themeID" stdset="0">
+               <string notr="true">removeIconSmall</string>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="QPushButton" name="transitionProps">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="maximumSize">
                <size>
-                <width>120</width>
-                <height>0</height>
+                <width>22</width>
+                <height>22</height>
                </size>
               </property>
+              <property name="toolTip">
+               <string>Basic.TransitionProperties</string>
+              </property>
+              <property name="accessibleName">
+               <string>Basic.TransitionProperties</string>
+              </property>
+              <property name="text">
+               <string notr="true"/>
+              </property>
+              <property name="icon">
+               <iconset resource="obs.qrc">
+                <normaloff>:/res/images/configuration21_16.png</normaloff>:/res/images/configuration21_16.png</iconset>
+              </property>
+              <property name="flat">
+               <bool>true</bool>
+              </property>
+              <property name="themeID" stdset="0">
+               <string notr="true">configIconSmall</string>
+              </property>
              </widget>
             </item>
            </layout>

+ 216 - 4
obs/window-basic-main-transitions.cpp

@@ -18,9 +18,11 @@
 #include <QSpinBox>
 #include <QWidgetAction>
 #include <QToolTip>
+#include <QMessageBox>
 #include <util/dstr.hpp>
 #include "window-basic-main.hpp"
 #include "display-helpers.hpp"
+#include "window-namedialog.hpp"
 #include "menu-button.hpp"
 #include "qt-wrappers.hpp"
 
@@ -301,9 +303,6 @@ void OBSBasic::SetTransition(obs_source_t *transition)
 			SetComboTransition(ui->transitions, transition);
 		obs_set_output_source(0, transition);
 		obs_transition_swap_end(transition, oldTransition);
-
-		bool showPropertiesButton = obs_source_configurable(transition);
-		ui->transitionProps->setVisible(showPropertiesButton);
 	} else {
 		obs_set_output_source(0, transition);
 	}
@@ -314,6 +313,10 @@ void OBSBasic::SetTransition(obs_source_t *transition)
 	bool fixed = transition ? obs_transition_fixed(transition) : false;
 	ui->transitionDurationLabel->setVisible(!fixed);
 	ui->transitionDuration->setVisible(!fixed);
+
+	bool configurable = obs_source_configurable(transition);
+	ui->transitionRemove->setEnabled(configurable);
+	ui->transitionProps->setEnabled(configurable);
 }
 
 OBSSource OBSBasic::GetCurrentTransition()
@@ -327,9 +330,170 @@ void OBSBasic::on_transitions_currentIndexChanged(int)
 	SetTransition(transition);
 }
 
+void OBSBasic::AddTransition()
+{
+	QAction *action = reinterpret_cast<QAction*>(sender());
+	QString idStr = action->property("id").toString();
+
+	string name;
+	QString placeHolderText = QT_UTF8(
+			obs_source_get_display_name(QT_TO_UTF8(idStr)));
+	QString format = placeHolderText + " (%1)";
+	obs_source_t *source = nullptr;
+	int i = 1;
+
+	while ((source = FindTransition(QT_TO_UTF8(placeHolderText)))) {
+		placeHolderText = format.arg(++i);
+	}
+
+	bool accepted = NameDialog::AskForName(this,
+			QTStr("TransitionNameDlg.Title"),
+			QTStr("TransitionNameDlg.Text"),
+			name, placeHolderText);
+
+	if (accepted) {
+		if (name.empty()) {
+			QMessageBox::information(this,
+					QTStr("NoNameEntered.Title"),
+					QTStr("NoNameEntered.Text"));
+			AddTransition();
+			return;
+		}
+
+		source = FindTransition(name.c_str());
+		if (source) {
+			QMessageBox::information(this,
+					QTStr("NameExists.Title"),
+					QTStr("NameExists.Text"));
+
+			AddTransition();
+			return;
+		}
+
+		source = obs_source_create_private(QT_TO_UTF8(idStr),
+						name.c_str(), NULL);
+		InitTransition(source);
+		ui->transitions->addItem(QT_UTF8(name.c_str()),
+				QVariant::fromValue(OBSSource(source)));
+		ui->transitions->setCurrentIndex(ui->transitions->count() - 1);
+		CreatePropertiesWindow(source);
+		obs_source_release(source);
+	}
+}
+
+void OBSBasic::on_transitionAdd_clicked()
+{
+	bool foundConfigurableTransitions = false;
+	QMenu menu(this);
+	size_t idx = 0;
+	const char *id;
+
+	while (obs_enum_transition_types(idx++, &id)) {
+		if (obs_is_source_configurable(id)) {
+			const char *name = obs_source_get_display_name(id);
+			QAction *action = new QAction(name, this);
+			action->setProperty("id", id);
+
+			connect(action, SIGNAL(triggered()),
+					this, SLOT(AddTransition()));
+
+			menu.addAction(action);
+			foundConfigurableTransitions = true;
+		}
+	}
+
+	if (foundConfigurableTransitions)
+		menu.exec(QCursor::pos());
+}
+
+void OBSBasic::on_transitionRemove_clicked()
+{
+	OBSSource tr = GetCurrentTransition();
+
+	if (!tr || !obs_source_configurable(tr) || !QueryRemoveSource(tr))
+		return;
+
+	int idx = ui->transitions->findData(QVariant::fromValue<OBSSource>(tr));
+	if (idx == -1)
+		return;
+
+	for (size_t i = quickTransitions.size(); i > 0; i--) {
+		QuickTransition &qt = quickTransitions[i - 1];
+		if (qt.source == tr) {
+			if (qt.button)
+				qt.button->deleteLater();
+			RemoveQuickTransitionHotkey(&qt);
+			quickTransitions.erase(quickTransitions.begin() + i - 1);
+		}
+	}
+
+	ui->transitions->removeItem(idx);
+}
+
+void OBSBasic::RenameTransition()
+{
+	QAction *action = reinterpret_cast<QAction*>(sender());
+	QVariant variant = action->property("transition");
+	obs_source_t *transition = variant.value<OBSSource>();
+
+	string name;
+	QString placeHolderText = QT_UTF8(obs_source_get_name(transition));
+	obs_source_t *source = nullptr;
+
+	bool accepted = NameDialog::AskForName(this,
+			QTStr("TransitionNameDlg.Title"),
+			QTStr("TransitionNameDlg.Text"),
+			name, placeHolderText);
+
+	if (accepted) {
+		if (name.empty()) {
+			QMessageBox::information(this,
+					QTStr("NoNameEntered.Title"),
+					QTStr("NoNameEntered.Text"));
+			RenameTransition();
+			return;
+		}
+
+		source = FindTransition(name.c_str());
+		if (source) {
+			QMessageBox::information(this,
+					QTStr("NameExists.Title"),
+					QTStr("NameExists.Text"));
+
+			RenameTransition();
+			return;
+		}
+
+		obs_source_set_name(transition, name.c_str());
+		int idx = ui->transitions->findData(variant);
+		if (idx != -1)
+			ui->transitions->setItemText(idx, QT_UTF8(name.c_str()));
+	}
+}
+
 void OBSBasic::on_transitionProps_clicked()
 {
-	// TODO
+	OBSSource source = GetCurrentTransition();
+
+	if (!obs_source_configurable(source))
+		return;
+
+	auto properties = [&] () {
+		CreatePropertiesWindow(source);
+	};
+
+	QMenu menu(this);
+
+	QAction *action = new QAction(QTStr("Rename"), this);
+	connect(action, SIGNAL(triggered()), this, SLOT(RenameTransition()));
+	action->setProperty("transition", QVariant::fromValue(source));
+	menu.addAction(action);
+
+	action = new QAction(QTStr("Properties"), this);
+	connect(action, &QAction::triggered, properties);
+	menu.addAction(action);
+
+	menu.exec(QCursor::pos());
 }
 
 QuickTransition *OBSBasic::GetQuickTransition(int id)
@@ -875,3 +1039,51 @@ void OBSBasic::ResizeProgram(uint32_t cx, uint32_t cy)
 	programX += float(PREVIEW_EDGE_SIZE);
 	programY += float(PREVIEW_EDGE_SIZE);
 }
+
+obs_data_array_t *OBSBasic::SaveTransitions()
+{
+	obs_data_array_t *transitions = obs_data_array_create();
+
+	for (int i = 0; i < ui->transitions->count(); i++) {
+		OBSSource tr = ui->transitions->itemData(i).value<OBSSource>();
+		if (!obs_source_configurable(tr))
+			continue;
+
+		obs_data_t *sourceData = obs_data_create();
+		obs_data_t *settings = obs_source_get_settings(tr);
+
+		obs_data_set_string(sourceData, "name", obs_source_get_name(tr));
+		obs_data_set_string(sourceData, "id", obs_obj_get_id(tr));
+		obs_data_set_obj(sourceData, "settings", settings);
+
+		obs_data_array_push_back(transitions, sourceData);
+
+		obs_data_release(settings);
+		obs_data_release(sourceData);
+	}
+
+	return transitions;
+}
+
+void OBSBasic::LoadTransitions(obs_data_array_t *transitions)
+{
+	size_t count = obs_data_array_count(transitions);
+
+	for (size_t i = 0; i < count; i++) {
+		obs_data_t *item = obs_data_array_item(transitions, i);
+		const char *name = obs_data_get_string(item, "name");
+		const char *id = obs_data_get_string(item, "id");
+		obs_data_t *settings = obs_data_get_obj(item, "settings");
+
+		obs_source_t *source = obs_source_create_private(id, name,
+				settings);
+		InitTransition(source);
+		ui->transitions->addItem(QT_UTF8(name),
+				QVariant::fromValue(OBSSource(source)));
+		ui->transitions->setCurrentIndex(ui->transitions->count() - 1);
+		obs_source_release(source);
+
+		obs_data_release(settings);
+		obs_data_release(item);
+	}
+}

+ 10 - 1
obs/window-basic-main.cpp

@@ -247,6 +247,7 @@ static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent,
 
 static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder,
 		obs_data_array_t *quickTransitionData, int transitionDuration,
+		obs_data_array_t *transitions,
 		OBSScene &scene, OBSSource &curProgramScene)
 {
 	obs_data_t *saveData = obs_data_create();
@@ -287,6 +288,7 @@ static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder,
 	obs_data_set_string(saveData, "name", sceneCollection);
 	obs_data_set_array(saveData, "sources", sourcesArray);
 	obs_data_set_array(saveData, "quick_transitions", quickTransitionData);
+	obs_data_set_array(saveData, "transitions", transitions);
 	obs_data_array_release(sourcesArray);
 
 	obs_data_set_string(saveData, "current_transition",
@@ -352,9 +354,10 @@ void OBSBasic::Save(const char *file)
 		curProgramScene = obs_scene_get_source(scene);
 
 	obs_data_array_t *sceneOrder = SaveSceneListOrder();
+	obs_data_array_t *transitions = SaveTransitions();
 	obs_data_array_t *quickTrData = SaveQuickTransitions();
 	obs_data_t *saveData  = GenerateSaveData(sceneOrder, quickTrData,
-			ui->transitionDuration->value(),
+			ui->transitionDuration->value(), transitions,
 			scene, curProgramScene);
 
 	if (!obs_data_save_json_safe(saveData, file, "tmp", "bak"))
@@ -363,6 +366,7 @@ void OBSBasic::Save(const char *file)
 	obs_data_release(saveData);
 	obs_data_array_release(sceneOrder);
 	obs_data_array_release(quickTrData);
+	obs_data_array_release(transitions);
 }
 
 static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent)
@@ -488,6 +492,7 @@ void OBSBasic::Load(const char *file)
 
 	obs_data_array_t *sceneOrder = obs_data_get_array(data, "scene_order");
 	obs_data_array_t *sources    = obs_data_get_array(data, "sources");
+	obs_data_array_t *transitions= obs_data_get_array(data, "transitions");
 	const char       *sceneName = obs_data_get_string(data,
 			"current_scene");
 	const char       *programSceneName = obs_data_get_string(data,
@@ -523,9 +528,13 @@ void OBSBasic::Load(const char *file)
 
 	obs_load_sources(sources);
 
+	if (transitions)
+		LoadTransitions(transitions);
 	if (sceneOrder)
 		LoadSceneListOrder(sceneOrder);
 
+	obs_data_array_release(transitions);
+
 	curTransition = FindTransition(transitionName);
 	if (!curTransition)
 		curTransition = fadeTransition;

+ 6 - 0
obs/window-basic-main.hpp

@@ -220,6 +220,8 @@ private:
 	obs_source_t *FindTransition(const char *name);
 	void SetTransition(obs_source_t *transition);
 	OBSSource GetCurrentTransition();
+	obs_data_array_t *SaveTransitions();
+	void LoadTransitions(obs_data_array_t *transitions);
 
 	obs_source_t *fadeTransition;
 
@@ -314,6 +316,8 @@ private slots:
 
 	void ProcessHotkey(obs_hotkey_id id, bool pressed);
 
+	void AddTransition();
+	void RenameTransition();
 	void TransitionClicked();
 	void TransitionStopped();
 	void TriggerQuickTransition(int id);
@@ -465,6 +469,8 @@ private slots:
 	void on_actionAlwaysOnTop_triggered();
 
 	void on_transitions_currentIndexChanged(int index);
+	void on_transitionAdd_clicked();
+	void on_transitionRemove_clicked();
 	void on_transitionProps_clicked();
 
 	void on_modeSwitch_clicked();